deterministic 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +18 -18
- data/lib/deterministic.rb +3 -4
- data/lib/deterministic/core_ext/result.rb +2 -2
- data/lib/deterministic/match.rb +60 -0
- data/lib/deterministic/maybe.rb +2 -2
- data/lib/deterministic/monad.rb +13 -3
- data/lib/deterministic/{none.rb → null.rb} +7 -7
- data/lib/deterministic/option.rb +95 -0
- data/lib/deterministic/result.rb +22 -14
- data/lib/deterministic/version.rb +1 -1
- data/spec/examples/config_spec.rb +73 -0
- data/spec/examples/validate_address_spec.rb +2 -2
- data/spec/lib/deterministic/maybe_spec.rb +5 -5
- data/spec/lib/deterministic/monad_spec.rb +8 -12
- data/spec/lib/deterministic/null_spec.rb +49 -0
- data/spec/lib/deterministic/option_spec.rb +92 -0
- data/spec/lib/deterministic/result/chain_spec.rb +3 -3
- data/spec/lib/deterministic/result/failure_spec.rb +3 -1
- data/spec/lib/deterministic/result/match_spec.rb +1 -1
- data/spec/lib/deterministic/result/success_spec.rb +3 -1
- metadata +11 -8
- data/lib/deterministic/result/failure.rb +0 -5
- data/lib/deterministic/result/match.rb +0 -66
- data/lib/deterministic/result/success.rb +0 -5
- data/spec/lib/deterministic/none_spec.rb +0 -49
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 02bcfb726015c3bfd566bb7ac4fa2ec7827f9916
         | 
| 4 | 
            +
              data.tar.gz: 41febde66849c34ad23f4d031b0ca4cdd91b26fd
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 04113508f25059be7d25c184acedd3dfe20b9d2f2e267c5d7e5a62ee658f5049f11650da4d8a8565c4b1fccf9084b700ea6477fef32a3a36cf6b87face2136f8
         | 
| 7 | 
            +
              data.tar.gz: 3c3bf38b691b39a7fd8f3a510738a9dbe8662796ccf509b20666f2d422f26452c70159e3291303eac44a9db62f142bba8ef08dd4e2cee1f65db6bf4184362f36
         | 
    
        data/README.md
    CHANGED
    
    | @@ -19,7 +19,7 @@ Failure(1).to_s                        # => "1" | |
| 19 19 | 
             
            Failure(Failure(1))                    # => Failure(1)
         | 
| 20 20 | 
             
            ```
         | 
| 21 21 |  | 
| 22 | 
            -
            #### `fmap  | 
| 22 | 
            +
            #### `fmap(self: Result(a), op: |a| -> b) -> Result(b)`
         | 
| 23 23 |  | 
| 24 24 | 
             
            Maps a `Result` with the value `a` to the same `Result` with the value `b`.
         | 
| 25 25 |  | 
| @@ -28,7 +28,7 @@ Success(1).fmap { |v| v + 1}           # => Success(2) | |
| 28 28 | 
             
            Failure(1).fmap { |v| v - 1}           # => Failure(0)
         | 
| 29 29 | 
             
            ```
         | 
| 30 30 |  | 
| 31 | 
            -
            #### `bind  | 
| 31 | 
            +
            #### `bind(self: Result(a), op: |a| -> Result(b)) -> Result(b)`
         | 
| 32 32 |  | 
| 33 33 | 
             
            Maps a `Result` with the value `a` to another `Result` with the value `b`.
         | 
| 34 34 |  | 
| @@ -37,7 +37,7 @@ Success(1).bind { |v| Failure(v + 1) } # => Failure(2) | |
| 37 37 | 
             
            Failure(1).fmap { |v| Success(v - 1) } # => Success(0)
         | 
| 38 38 | 
             
            ```
         | 
| 39 39 |  | 
| 40 | 
            -
            #### `map  | 
| 40 | 
            +
            #### `map(self: Success(a), op: |a| -> Result(b)) -> Result(b)`
         | 
| 41 41 |  | 
| 42 42 | 
             
            Maps a `Success` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Success`.
         | 
| 43 43 |  | 
| @@ -46,7 +46,7 @@ Success(1).map { |n| n + 1 }           # => Success(2) | |
| 46 46 | 
             
            Failure(0).map { |n| n + 1 }           # => Failure(0)
         | 
| 47 47 | 
             
            ```
         | 
| 48 48 |  | 
| 49 | 
            -
            #### `map_err  | 
| 49 | 
            +
            #### `map_err(self: Failure(a), op: |a| -> Result(b)) -> Result(b)`
         | 
| 50 50 |  | 
| 51 51 | 
             
            Maps a `Failure` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Failure`.
         | 
| 52 52 |  | 
| @@ -55,7 +55,7 @@ Failure(1).map_err { |n| n + 1 } # => Success(2) | |
| 55 55 | 
             
            Success(0).map_err { |n| n + 1 } # => Success(0)
         | 
| 56 56 | 
             
            ```
         | 
| 57 57 |  | 
| 58 | 
            -
            #### `try  | 
| 58 | 
            +
            #### `try(self: Success(a),  op: |a| -> Result(b)) -> Result(b)`
         | 
| 59 59 |  | 
| 60 60 | 
             
            Just like `#map`, transforms `a` to another `Result`, but it will also catch raised exceptions and wrap them with a `Failure`.
         | 
| 61 61 |  | 
| @@ -63,7 +63,7 @@ Just like `#map`, transforms `a` to another `Result`, but it will also catch rai | |
| 63 63 | 
             
            Success(0).try { |n| raise "Error" }   # => Failure(Error)
         | 
| 64 64 | 
             
            ```
         | 
| 65 65 |  | 
| 66 | 
            -
            #### `and  | 
| 66 | 
            +
            #### `and(self: Success(a), other: Result(b)) -> Result(b)`
         | 
| 67 67 |  | 
| 68 68 | 
             
            Replaces `Success a` with `Result b`. If a `Failure` is passed as argument, it is ignored.
         | 
| 69 69 |  | 
| @@ -72,7 +72,7 @@ Success(1).and Success(2)            # => Success(2) | |
| 72 72 | 
             
            Failure(1).and Success(2)            # => Failure(1)
         | 
| 73 73 | 
             
            ```
         | 
| 74 74 |  | 
| 75 | 
            -
            #### `and_then  | 
| 75 | 
            +
            #### `and_then(self: Success(a), op: |a| -> Result(b)) -> Result(b)`
         | 
| 76 76 |  | 
| 77 77 | 
             
            Replaces `Success a` with the result of the block. If a `Failure` is passed as argument, it is ignored.
         | 
| 78 78 |  | 
| @@ -81,7 +81,7 @@ Success(1).and_then { Success(2) }   # => Success(2) | |
| 81 81 | 
             
            Failure(1).and_then { Success(2) }   # => Failure(1)
         | 
| 82 82 | 
             
            ```
         | 
| 83 83 |  | 
| 84 | 
            -
            #### `or  | 
| 84 | 
            +
            #### `or(self: Failure(a), other: Result(b)) -> Result(b)` 
         | 
| 85 85 | 
             
            Replaces `Failure a` with `Result`. If a `Failure` is passed as argument, it is ignored.
         | 
| 86 86 |  | 
| 87 87 | 
             
            ```ruby
         | 
| @@ -89,7 +89,7 @@ Success(1).or Success(2)             # => Success(1) | |
| 89 89 | 
             
            Failure(1).or Success(1)             # => Success(1)
         | 
| 90 90 | 
             
            ```
         | 
| 91 91 |  | 
| 92 | 
            -
            #### `or_else  | 
| 92 | 
            +
            #### `or_else(self: Failure(a),  op: |a| -> Result(b)) -> Result(b)`
         | 
| 93 93 |  | 
| 94 94 | 
             
            Replaces `Failure a` with the result of the block. If a `Success` is passed as argument, it is ignored.
         | 
| 95 95 |  | 
| @@ -98,7 +98,7 @@ Success(1).or_else { Success(2) }    # => Success(1) | |
| 98 98 | 
             
            Failure(1).or_else { |n| Success(n)} # => Success(1)
         | 
| 99 99 | 
             
            ```
         | 
| 100 100 |  | 
| 101 | 
            -
            #### `pipe  | 
| 101 | 
            +
            #### `pipe(self: Result(a), op: |Result(a)| -> b) -> Result(a)`
         | 
| 102 102 |  | 
| 103 103 | 
             
            Executes the block passed, but completely ignores its result. If an error is raised within the block it will **NOT** be catched.
         | 
| 104 104 |  | 
| @@ -269,16 +269,16 @@ Failure(1).result?  # => true | |
| 269 269 |  | 
| 270 270 |  | 
| 271 271 | 
             
            ## Maybe
         | 
| 272 | 
            -
            The simplest NullObject wrapper there can be. It adds `#some?` and `# | 
| 272 | 
            +
            The simplest NullObject wrapper there can be. It adds `#some?` and `#null?` to `Object` though.
         | 
| 273 273 |  | 
| 274 274 | 
             
            ```ruby
         | 
| 275 275 | 
             
            require 'deterministic/maybe' # you need to do this explicitly
         | 
| 276 | 
            -
            Maybe(nil).foo        # =>  | 
| 277 | 
            -
            Maybe(nil).foo.bar    # =>  | 
| 276 | 
            +
            Maybe(nil).foo        # => Null
         | 
| 277 | 
            +
            Maybe(nil).foo.bar    # => Null
         | 
| 278 278 | 
             
            Maybe({a: 1})[:a]     # => 1
         | 
| 279 279 |  | 
| 280 | 
            -
            Maybe(nil). | 
| 281 | 
            -
            Maybe({}). | 
| 280 | 
            +
            Maybe(nil).null?      # => true
         | 
| 281 | 
            +
            Maybe({}).null?       # => false
         | 
| 282 282 |  | 
| 283 283 | 
             
            Maybe(nil).some?      # => false
         | 
| 284 284 | 
             
            Maybe({}).some?       # => true
         | 
| @@ -293,9 +293,9 @@ class Mimick | |
| 293 293 | 
             
              def test; end
         | 
| 294 294 | 
             
            end
         | 
| 295 295 |  | 
| 296 | 
            -
             | 
| 297 | 
            -
             | 
| 298 | 
            -
             | 
| 296 | 
            +
            naught = Maybe.mimick(Mimick)
         | 
| 297 | 
            +
            naught.test             # => Null
         | 
| 298 | 
            +
            naught.foo              # => NoMethodError
         | 
| 299 299 | 
             
            ```
         | 
| 300 300 |  | 
| 301 301 | 
             
            ## Inspirations
         | 
    
        data/lib/deterministic.rb
    CHANGED
    
    | @@ -5,9 +5,8 @@ warn "WARN: Deterministic is meant to run on Ruby 2+" if RUBY_VERSION.to_f < 2 | |
| 5 5 | 
             
            module Deterministic; end
         | 
| 6 6 |  | 
| 7 7 | 
             
            require 'deterministic/monad'
         | 
| 8 | 
            -
            require 'deterministic/ | 
| 8 | 
            +
            require 'deterministic/match'
         | 
| 9 9 | 
             
            require 'deterministic/result/chain'
         | 
| 10 10 | 
             
            require 'deterministic/result'
         | 
| 11 | 
            -
            require 'deterministic/ | 
| 12 | 
            -
            require 'deterministic/ | 
| 13 | 
            -
            require 'deterministic/none'
         | 
| 11 | 
            +
            require 'deterministic/option'
         | 
| 12 | 
            +
            require 'deterministic/null'
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            module Deterministic
         | 
| 2 | 
            +
              module PatternMatching
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def match(context=nil, &block)
         | 
| 5 | 
            +
                  context ||= block.binding.eval('self') # the instance containing the match block
         | 
| 6 | 
            +
                  match = binding.eval('self.class::Match.new(self, context)') # the class defining the Match
         | 
| 7 | 
            +
                  match.instance_eval &block
         | 
| 8 | 
            +
                  match.call
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                class NoMatchError < StandardError; end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                module Match
         | 
| 14 | 
            +
                  def initialize(container, context)
         | 
| 15 | 
            +
                    @container  = container
         | 
| 16 | 
            +
                    @context    = context
         | 
| 17 | 
            +
                    @collection = []
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def call
         | 
| 21 | 
            +
                    matcher = @collection.detect { |m| m.matches?(@container.value) }
         | 
| 22 | 
            +
                    raise NoMatchError, "No match could be made for #{@container.inspect}" if matcher.nil?
         | 
| 23 | 
            +
                    @context.instance_exec(@container.value, &matcher.block)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  # catch-all
         | 
| 27 | 
            +
                  def any(value=nil, &result_block)
         | 
| 28 | 
            +
                    push(Object, value, result_block)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                private
         | 
| 32 | 
            +
                  Matcher = Struct.new(:condition, :block) do
         | 
| 33 | 
            +
                    def matches?(value)
         | 
| 34 | 
            +
                      condition.call(value)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def push(type, condition, result_block)
         | 
| 39 | 
            +
                    condition_pred = case
         | 
| 40 | 
            +
                    when condition.nil?;          ->(v) { true }
         | 
| 41 | 
            +
                    when condition.is_a?(Proc);   condition
         | 
| 42 | 
            +
                    when condition.is_a?(Class);  ->(v) { condition === @container.value }
         | 
| 43 | 
            +
                    else                          ->(v) { @container.value == condition }
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    matcher_pred = compose_predicates(type_pred[type], condition_pred)
         | 
| 47 | 
            +
                    @collection << Matcher.new(matcher_pred, result_block)
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def compose_predicates(f, g)
         | 
| 51 | 
            +
                    ->(*args) { f[*args] && g[*args] }
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  # return a partial function for matching a matcher's type
         | 
| 55 | 
            +
                  def type_pred
         | 
| 56 | 
            +
                    (->(type, x) { @container.is_a? type }).curry
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
    
        data/lib/deterministic/maybe.rb
    CHANGED
    
    
    
        data/lib/deterministic/monad.rb
    CHANGED
    
    | @@ -25,8 +25,9 @@ module Deterministic | |
| 25 25 | 
             
                # bind :: (a -> Mb) -> M a  -> M b
         | 
| 26 26 | 
             
                # the self.class, i.e. the containing monad is passed as a second (optional) arg to the function
         | 
| 27 27 | 
             
                def bind(proc=nil, &block)
         | 
| 28 | 
            -
                  (proc || block).call(value | 
| 29 | 
            -
                     | 
| 28 | 
            +
                  (proc || block).call(value).tap do |result|
         | 
| 29 | 
            +
                    parent = self.class.superclass === Object ? self.class : self.class.superclass
         | 
| 30 | 
            +
                    raise NotMonadError, "Expected #{result.inspect} to be an #{parent}" unless result.is_a? parent
         | 
| 30 31 | 
             
                  end
         | 
| 31 32 | 
             
                end
         | 
| 32 33 | 
             
                alias :'>>=' :bind
         | 
| @@ -37,6 +38,15 @@ module Deterministic | |
| 37 38 | 
             
                  @value
         | 
| 38 39 | 
             
                end
         | 
| 39 40 |  | 
| 41 | 
            +
                def to_s
         | 
| 42 | 
            +
                  value.to_s
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def inspect
         | 
| 46 | 
            +
                  name = self.class.name.split("::")[-1]
         | 
| 47 | 
            +
                  "#{name}(#{value})"
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 40 50 | 
             
                # Two monads are equivalent if they are of the same type and when their values are equal
         | 
| 41 51 | 
             
                def ==(other)
         | 
| 42 52 | 
             
                  return false unless other.is_a? self.class
         | 
| @@ -44,7 +54,7 @@ module Deterministic | |
| 44 54 | 
             
                end
         | 
| 45 55 |  | 
| 46 56 | 
             
                # Return the string representation of the Monad
         | 
| 47 | 
            -
                def  | 
| 57 | 
            +
                def inspect
         | 
| 48 58 | 
             
                  pretty_class_name = self.class.name.split('::')[-1]
         | 
| 49 59 | 
             
                  "#{pretty_class_name}(#{self.value.inspect})"
         | 
| 50 60 | 
             
                end
         | 
| @@ -1,11 +1,11 @@ | |
| 1 1 | 
             
            # The simplest NullObject there can be
         | 
| 2 | 
            -
            class  | 
| 2 | 
            +
            class Null
         | 
| 3 3 | 
             
              class << self
         | 
| 4 4 | 
             
                def method_missing(m, *args)
         | 
| 5 5 | 
             
                  if m == :new
         | 
| 6 6 | 
             
                    super
         | 
| 7 7 | 
             
                  else
         | 
| 8 | 
            -
                     | 
| 8 | 
            +
                    Null.instance
         | 
| 9 9 | 
             
                  end
         | 
| 10 10 | 
             
                end
         | 
| 11 11 |  | 
| @@ -13,7 +13,7 @@ class None | |
| 13 13 | 
             
                  @instance ||= new([])
         | 
| 14 14 | 
             
                end
         | 
| 15 15 |  | 
| 16 | 
            -
                def  | 
| 16 | 
            +
                def null?
         | 
| 17 17 | 
             
                  true
         | 
| 18 18 | 
             
                end
         | 
| 19 19 |  | 
| @@ -26,7 +26,7 @@ class None | |
| 26 26 | 
             
                end
         | 
| 27 27 |  | 
| 28 28 | 
             
                def ==(other)
         | 
| 29 | 
            -
                  other.respond_to?(: | 
| 29 | 
            +
                  other.respond_to?(:null?) && other.null?
         | 
| 30 30 | 
             
                end
         | 
| 31 31 | 
             
              end
         | 
| 32 32 | 
             
              private_class_method :new
         | 
| @@ -49,7 +49,7 @@ class None | |
| 49 49 | 
             
                super
         | 
| 50 50 | 
             
              end
         | 
| 51 51 |  | 
| 52 | 
            -
              def  | 
| 52 | 
            +
              def null?
         | 
| 53 53 | 
             
                true
         | 
| 54 54 | 
             
              end
         | 
| 55 55 |  | 
| @@ -63,10 +63,10 @@ class None | |
| 63 63 | 
             
              end
         | 
| 64 64 |  | 
| 65 65 | 
             
              def inspect
         | 
| 66 | 
            -
                ' | 
| 66 | 
            +
                'Null'
         | 
| 67 67 | 
             
              end
         | 
| 68 68 |  | 
| 69 69 | 
             
              def ==(other)
         | 
| 70 | 
            -
                other.respond_to?(: | 
| 70 | 
            +
                other.respond_to?(:null?) && other.null?
         | 
| 71 71 | 
             
              end
         | 
| 72 72 | 
             
            end
         | 
| @@ -0,0 +1,95 @@ | |
| 1 | 
            +
            module Deterministic
         | 
| 2 | 
            +
              # Abstract parent of Some and None 
         | 
| 3 | 
            +
              class Option
         | 
| 4 | 
            +
                include Monad
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                module PatternMatching
         | 
| 7 | 
            +
                  include Deterministic::PatternMatching
         | 
| 8 | 
            +
                  class Match
         | 
| 9 | 
            +
                    include Deterministic::PatternMatching::Match
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    %w[Some None Option].each do |s|
         | 
| 12 | 
            +
                      define_method s.downcase.to_sym do |value=nil, &block|
         | 
| 13 | 
            +
                        klas = Module.const_get("Deterministic::Option::#{s}")
         | 
| 14 | 
            +
                        push(klas, value, block)
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                include PatternMatching
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # This is an abstract class, can't ever instantiate it directly
         | 
| 23 | 
            +
                class << self
         | 
| 24 | 
            +
                  protected :new
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def some?(expr)
         | 
| 27 | 
            +
                    to_option(expr) { expr.nil? }
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def any?(expr)
         | 
| 31 | 
            +
                    to_option(expr) { expr.nil? || not(expr.respond_to?(:empty?)) || expr.empty? }
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def to_option(expr, &predicate)
         | 
| 35 | 
            +
                    predicate.call(expr) ? None.new : Some.new(expr)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def try!
         | 
| 39 | 
            +
                    yield rescue None.new
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def map(proc=nil, &block)
         | 
| 44 | 
            +
                  return self if none?
         | 
| 45 | 
            +
                  bind(proc || block)
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def some?
         | 
| 49 | 
            +
                  is_a? Some
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def none?
         | 
| 53 | 
            +
                  is_a? None
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                class Some < Option
         | 
| 57 | 
            +
                  class << self; public :new; end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                class None < Option
         | 
| 61 | 
            +
                  class << self; public :new; end
         | 
| 62 | 
            +
                  def initialize(*args); end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def inspect
         | 
| 65 | 
            +
                    "None"
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  # def value
         | 
| 69 | 
            +
                  #   self
         | 
| 70 | 
            +
                  # end
         | 
| 71 | 
            +
                  def none(*args)
         | 
| 72 | 
            +
                    self
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  alias :fmap :none
         | 
| 76 | 
            +
                  alias :map :none
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  def ==(other)
         | 
| 79 | 
            +
                    other.class == self.class
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  # def value
         | 
| 83 | 
            +
                  #   self # raise "value called on a None"
         | 
| 84 | 
            +
                  # end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            module_function
         | 
| 89 | 
            +
              def Some(value)
         | 
| 90 | 
            +
                Option::Some.new(value)
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              None = Deterministic::Option::None.new
         | 
| 94 | 
            +
            end
         | 
| 95 | 
            +
            # p Deterministic::Option::Some::Match.new(.methods
         | 
    
        data/lib/deterministic/result.rb
    CHANGED
    
    | @@ -2,15 +2,24 @@ module Deterministic | |
| 2 2 | 
             
              # Abstract parent of Success and Failure
         | 
| 3 3 | 
             
              class Result
         | 
| 4 4 | 
             
                include Monad
         | 
| 5 | 
            -
                include Deterministic::PatternMatching
         | 
| 6 | 
            -
                include Chain
         | 
| 7 5 |  | 
| 8 | 
            -
                 | 
| 9 | 
            -
                   | 
| 10 | 
            -
             | 
| 6 | 
            +
                module PatternMatching
         | 
| 7 | 
            +
                  include Deterministic::PatternMatching
         | 
| 8 | 
            +
                  class Match
         | 
| 9 | 
            +
                    include Deterministic::PatternMatching::Match
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    %w[Success Failure Result].each do |s|
         | 
| 12 | 
            +
                      define_method s.downcase.to_sym do |value=nil, &block|
         | 
| 13 | 
            +
                        klas = Module.const_get("Deterministic::Result::#{s}")
         | 
| 14 | 
            +
                        push(klas, value, block)
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
                    end
         | 
| 11 17 | 
             
                  end
         | 
| 12 18 | 
             
                end
         | 
| 13 19 |  | 
| 20 | 
            +
                include PatternMatching
         | 
| 21 | 
            +
                include Chain
         | 
| 22 | 
            +
             | 
| 14 23 | 
             
                def success?
         | 
| 15 24 | 
             
                  is_a? Success
         | 
| 16 25 | 
             
                end
         | 
| @@ -52,23 +61,22 @@ module Deterministic | |
| 52 61 | 
             
                class << self
         | 
| 53 62 | 
             
                  protected :new
         | 
| 54 63 | 
             
                end
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                 | 
| 57 | 
            -
                   | 
| 64 | 
            +
                
         | 
| 65 | 
            +
                class Failure < Result
         | 
| 66 | 
            +
                  class << self; public :new; end
         | 
| 58 67 | 
             
                end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                 | 
| 61 | 
            -
                   | 
| 62 | 
            -
                  "#{name}(#{value})"
         | 
| 68 | 
            +
                
         | 
| 69 | 
            +
                class Success < Result
         | 
| 70 | 
            +
                  class << self; public :new; end
         | 
| 63 71 | 
             
                end
         | 
| 64 72 | 
             
              end
         | 
| 65 73 |  | 
| 66 74 | 
             
            module_function
         | 
| 67 75 | 
             
              def Success(value)
         | 
| 68 | 
            -
                Success.new(value)
         | 
| 76 | 
            +
                Result::Success.new(value)
         | 
| 69 77 | 
             
              end
         | 
| 70 78 |  | 
| 71 79 | 
             
              def Failure(value)
         | 
| 72 | 
            -
                Failure.new(value)
         | 
| 80 | 
            +
                Result::Failure.new(value)
         | 
| 73 81 | 
             
              end
         | 
| 74 82 | 
             
            end
         | 
| @@ -0,0 +1,73 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            include Deterministic
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class ElasticSearchConfig
         | 
| 6 | 
            +
              def initialize(env="development", proc_env=ENV)
         | 
| 7 | 
            +
                @env, @proc_env = env, proc_env
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              attr_reader :env
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def hosts
         | 
| 13 | 
            +
                Option.any?(proc_env["RESFINITY_LOG_CLIENT_ES_HOST"]).match {
         | 
| 14 | 
            +
                  some { |s| { hosts: s.split(/, */) } }
         | 
| 15 | 
            +
                  none { default_hosts }
         | 
| 16 | 
            +
                }
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            private
         | 
| 20 | 
            +
              attr_reader :proc_env
         | 
| 21 | 
            +
              def default_hosts
         | 
| 22 | 
            +
                case env
         | 
| 23 | 
            +
                when "production"
         | 
| 24 | 
            +
                  { hosts: ["resfinity.net:9200"] }
         | 
| 25 | 
            +
                when "acceptance" ||  "development"
         | 
| 26 | 
            +
                  { hosts: ["acc.resfinity.net:9200"] }
         | 
| 27 | 
            +
                else
         | 
| 28 | 
            +
                  { hosts: ["localhost:9200"] }
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            describe ElasticSearchConfig do
         | 
| 34 | 
            +
              let(:cfg) { ElasticSearchConfig.new(environment, env) }
         | 
| 35 | 
            +
              context "test" do
         | 
| 36 | 
            +
                let(:environment) { "test" }
         | 
| 37 | 
            +
                context "env empty" do
         | 
| 38 | 
            +
                  let(:env) { {} }
         | 
| 39 | 
            +
                  specify { expect(cfg.hosts).to eq({ hosts: ["localhost:9200"] }) }
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                context "env empty" do
         | 
| 43 | 
            +
                  let(:env) { { "RESFINITY_LOG_CLIENT_ES_HOST" => "" } }
         | 
| 44 | 
            +
                  specify { expect(cfg.hosts).to eq({ hosts: ["localhost:9200"] }) }
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                context "env contains one" do
         | 
| 48 | 
            +
                  let(:env) { { "RESFINITY_LOG_CLIENT_ES_HOST" => "foo:9999"} }
         | 
| 49 | 
            +
                  specify { expect(cfg.hosts).to eq({ hosts: ["foo:9999"] }) }
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                context "env contains two" do
         | 
| 53 | 
            +
                  let(:env) { { "RESFINITY_LOG_CLIENT_ES_HOST" => "foo:9999,bar:9200"} }
         | 
| 54 | 
            +
                  specify { expect(cfg.hosts).to eq({ hosts: ["foo:9999", "bar:9200"] }) }
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              context "production" do
         | 
| 59 | 
            +
                let(:environment) { "production" }
         | 
| 60 | 
            +
                context "env empty" do
         | 
| 61 | 
            +
                  let(:env) { {} }
         | 
| 62 | 
            +
                  specify { expect(cfg.hosts).to eq({ hosts: ["resfinity.net:9200"] }) }
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              context "acceptance" do
         | 
| 67 | 
            +
                let(:environment) { "acceptance" }
         | 
| 68 | 
            +
                context "env empty" do
         | 
| 69 | 
            +
                  let(:env) { {} }
         | 
| 70 | 
            +
                  specify { expect(cfg.hosts).to eq({ hosts: ["acc.resfinity.net:9200"] }) }
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
            end
         | 
| @@ -19,13 +19,13 @@ describe ValidateAddress do | |
| 19 19 | 
             
              subject { ValidateAddress.call(candidate)  }
         | 
| 20 20 | 
             
              context 'sunny day' do
         | 
| 21 21 | 
             
                let(:candidate) { {title: "Hobbiton", street: "501 Buckland Rd", city: "Matamata", postal: "3472", country: "nz"} }
         | 
| 22 | 
            -
                specify { expect(subject).to be_a Success }
         | 
| 22 | 
            +
                specify { expect(subject).to be_a Result::Success }
         | 
| 23 23 | 
             
                specify { expect(subject.value).to eq candidate }
         | 
| 24 24 | 
             
              end
         | 
| 25 25 |  | 
| 26 26 | 
             
              context 'empty data' do
         | 
| 27 27 | 
             
                let(:candidate) { {} }
         | 
| 28 | 
            -
                specify { expect(subject).to be_a Failure }
         | 
| 28 | 
            +
                specify { expect(subject).to be_a Result::Failure }
         | 
| 29 29 | 
             
                specify { expect(subject.value).to include(:street, :city, :postal) }
         | 
| 30 30 | 
             
              end
         | 
| 31 31 | 
             
            end
         | 
| @@ -3,16 +3,16 @@ require 'deterministic/maybe' | |
| 3 3 |  | 
| 4 4 | 
             
            describe 'maybe' do
         | 
| 5 5 | 
             
              it "does something" do
         | 
| 6 | 
            -
                expect(Maybe(nil).foo).to  | 
| 7 | 
            -
                expect(Maybe(nil).foo.bar.baz).to  | 
| 8 | 
            -
                expect(Maybe(nil).fetch(:a)).to  | 
| 6 | 
            +
                expect(Maybe(nil).foo).to be_null
         | 
| 7 | 
            +
                expect(Maybe(nil).foo.bar.baz).to be_null
         | 
| 8 | 
            +
                expect(Maybe(nil).fetch(:a)).to be_null
         | 
| 9 9 | 
             
                expect(Maybe(1)).to be_some
         | 
| 10 10 | 
             
                expect(Maybe({a: 1}).fetch(:a)).to eq 1
         | 
| 11 11 | 
             
                expect(Maybe({a: 1})[:a]).to eq 1
         | 
| 12 12 | 
             
                expect(Maybe("a").upcase).to eq "A"
         | 
| 13 | 
            -
                expect(Maybe("a")).not_to  | 
| 13 | 
            +
                expect(Maybe("a")).not_to be_null
         | 
| 14 14 |  | 
| 15 | 
            -
                # expect(Maybe[[]]).to eq  | 
| 15 | 
            +
                # expect(Maybe[[]]).to eq be_null
         | 
| 16 16 | 
             
              end
         | 
| 17 17 |  | 
| 18 18 | 
             
            end
         | 
| @@ -7,12 +7,15 @@ describe Deterministic::Monad do | |
| 7 7 | 
             
                include Deterministic::Monad
         | 
| 8 8 | 
             
              end
         | 
| 9 9 |  | 
| 10 | 
            +
              let(:monad) { Identity }
         | 
| 10 11 | 
             
              it_behaves_like 'a Monad' do 
         | 
| 11 | 
            -
                let(:monad) {  | 
| 12 | 
            +
                # let(:monad) { monad }
         | 
| 12 13 | 
             
              end
         | 
| 13 14 |  | 
| 14 | 
            -
              specify { expect(Identity.new(1). | 
| 15 | 
            -
              specify { expect(Identity.new( | 
| 15 | 
            +
              specify { expect(Identity.new(1).inspect).to  eq 'Identity(1)' }
         | 
| 16 | 
            +
              specify { expect(Identity.new(1).to_s).to  eq '1' }
         | 
| 17 | 
            +
              specify { expect(Identity.new(nil).inspect).to  eq 'Identity(nil)' }
         | 
| 18 | 
            +
              specify { expect(Identity.new(nil).to_s).to  eq '' }
         | 
| 16 19 | 
             
              specify { expect(Identity.new([1, 2]).fmap(&:to_s)).to eq Identity.new("[1, 2]") }
         | 
| 17 20 | 
             
              specify { expect(Identity.new(1).fmap {|v| v + 2}).to eq Identity.new(3) }
         | 
| 18 21 | 
             
              specify { expect(Identity.new('foo').fmap(&:upcase)).to eq Identity.new('FOO')}
         | 
| @@ -25,24 +28,17 @@ describe Deterministic::Monad do | |
| 25 28 |  | 
| 26 29 | 
             
                it "passes the monad class, this is ruby-fu?!" do
         | 
| 27 30 | 
             
                 Identity.new(1)
         | 
| 28 | 
            -
                  .bind do |_ | 
| 31 | 
            +
                  .bind do |_|
         | 
| 29 32 | 
             
                    expect(monad).to eq Identity
         | 
| 30 33 | 
             
                    monad.new(_)
         | 
| 31 34 | 
             
                  end
         | 
| 32 35 | 
             
                end
         | 
| 33 36 |  | 
| 34 37 | 
             
                specify { expect(
         | 
| 35 | 
            -
                   | 
| 38 | 
            +
                  monad.new(1).bind { |value| monad.new(value + 1) }
         | 
| 36 39 | 
             
                  ).to eq Identity.new(2)
         | 
| 37 40 | 
             
                }
         | 
| 38 41 |  | 
| 39 42 | 
             
              end
         | 
| 40 43 | 
             
              specify { expect(Identity.new(Identity.new(1))).to eq Identity.new(1) }
         | 
| 41 | 
            -
             | 
| 42 | 
            -
              # it 'delegates #flat_map to an underlying collection and wraps the resulting collection' do
         | 
| 43 | 
            -
              #   Identity.unit([1,2]).flat_map {|v| v + 1}.should == Identity.unit([2, 3])
         | 
| 44 | 
            -
              #   Identity.unit(['foo', 'bar']).flat_map(&:upcase).should == Identity.unit(['FOO', 'BAR'])
         | 
| 45 | 
            -
              #   expect { Identity.unit(1).flat_map {|v| v + 1 } }.to raise_error(RuntimeError)
         | 
| 46 | 
            -
              # end
         | 
| 47 | 
            -
             | 
| 48 44 | 
             
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
            require "deterministic/null"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Null do
         | 
| 5 | 
            +
              it "Null is a Singleton" do
         | 
| 6 | 
            +
                expect(Null.instance).to be_a Null
         | 
| 7 | 
            +
                expect { Null.new }.to raise_error(NoMethodError, "private method `new' called for Null:Class")
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              it "explicit conversions" do
         | 
| 11 | 
            +
                expect(Null.to_s).to eq 'Null'
         | 
| 12 | 
            +
                expect(Null.inspect).to eq 'Null'
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              it "compares to Null" do
         | 
| 16 | 
            +
                expect(Null === Null.instance).to be_truthy
         | 
| 17 | 
            +
                expect(Null.instance === Null).to be_truthy
         | 
| 18 | 
            +
                expect(Null.instance).to eq Null
         | 
| 19 | 
            +
                expect(Null).to eq Null.instance
         | 
| 20 | 
            +
                expect(1).not_to be Null
         | 
| 21 | 
            +
                expect(1).not_to be Null.instance
         | 
| 22 | 
            +
                expect(Null.instance).not_to be 1
         | 
| 23 | 
            +
                expect(Null).not_to be 1
         | 
| 24 | 
            +
                expect(Null.instance).not_to be_nil
         | 
| 25 | 
            +
                expect(Null).not_to be_nil
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              it "implicit conversions" do
         | 
| 29 | 
            +
                null = Null.instance
         | 
| 30 | 
            +
                expect(null.to_str).to eq ""
         | 
| 31 | 
            +
                expect(null.to_ary).to eq []
         | 
| 32 | 
            +
                expect("" + null).to eq ""
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                a, b, c = null
         | 
| 35 | 
            +
                expect(a).to be_nil
         | 
| 36 | 
            +
                expect(b).to be_nil
         | 
| 37 | 
            +
                expect(c).to be_nil
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              it "mimicks other classes and returns Null for their public methods" do
         | 
| 41 | 
            +
                class UnderMimickTest
         | 
| 42 | 
            +
                  def test; end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                mimick = Null.mimic(UnderMimickTest)
         | 
| 46 | 
            +
                expect(mimick.test).to be_null
         | 
| 47 | 
            +
                expect { mimick.i_dont_exist}.to raise_error(NoMethodError)
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,92 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require_relative 'monad_axioms'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            include Deterministic
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe Deterministic::Option do
         | 
| 7 | 
            +
              # nil?
         | 
| 8 | 
            +
              specify { expect(described_class.some?(nil)).to eq None }
         | 
| 9 | 
            +
              specify { expect(described_class.some?(1)).to be_some }
         | 
| 10 | 
            +
              specify { expect(described_class.some?(1)).to eq Some(1) }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              # any?
         | 
| 13 | 
            +
              specify { expect(described_class.any?(nil)).to be_none }
         | 
| 14 | 
            +
              specify { expect(described_class.any?("")).to  be_none }
         | 
| 15 | 
            +
              specify { expect(described_class.any?([])).to  be_none }
         | 
| 16 | 
            +
              specify { expect(described_class.any?({})).to  be_none }
         | 
| 17 | 
            +
              specify { expect(described_class.any?([1])).to eq Some([1]) }
         | 
| 18 | 
            +
              specify { expect(described_class.any?({foo: 1})).to eq Some({foo: 1}) }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              # try!
         | 
| 21 | 
            +
              specify { expect(described_class.try! { raise "error" }).to be_none }
         | 
| 22 | 
            +
            end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            describe Deterministic::Option::Some do
         | 
| 25 | 
            +
               it_behaves_like 'a Monad' do
         | 
| 26 | 
            +
                let(:monad) { described_class }
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              specify { expect(described_class.new(0)).to be_a Option::Some }
         | 
| 30 | 
            +
              specify { expect(described_class.new(0)).to eq Some(0) }
         | 
| 31 | 
            +
              specify { expect(described_class.new(0).some?).to be_truthy }
         | 
| 32 | 
            +
              specify { expect(described_class.new(0).none?).to be_falsey }
         | 
| 33 | 
            +
              specify { expect(described_class.new(0).value).to eq 0 }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              specify { expect(described_class.new(1).fmap { |n| n + 1}).to eq Some(2) }
         | 
| 36 | 
            +
              specify { expect(described_class.new(1).map { |n| Some(n + 1)}).to eq Some(2) }
         | 
| 37 | 
            +
              specify { expect(described_class.new(1).map { |n| None }).to eq None }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              specify {
         | 
| 40 | 
            +
                expect(
         | 
| 41 | 
            +
                  Some(0).match {
         | 
| 42 | 
            +
                    some(1) { |n| 99 }
         | 
| 43 | 
            +
                    some(0) { |n| n + 1 }
         | 
| 44 | 
            +
                    none(1) {}
         | 
| 45 | 
            +
                  }
         | 
| 46 | 
            +
                ).to eq 1
         | 
| 47 | 
            +
              }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              specify {
         | 
| 50 | 
            +
                expect(
         | 
| 51 | 
            +
                  Some(nil).match {
         | 
| 52 | 
            +
                    none { 0 }
         | 
| 53 | 
            +
                    some { 1 }
         | 
| 54 | 
            +
                  }
         | 
| 55 | 
            +
                ).to eq 1
         | 
| 56 | 
            +
              }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              specify {
         | 
| 59 | 
            +
                expect(
         | 
| 60 | 
            +
                  Some(1).match {
         | 
| 61 | 
            +
                    none { 0 }
         | 
| 62 | 
            +
                    some(Fixnum) { 1 }
         | 
| 63 | 
            +
                  }
         | 
| 64 | 
            +
                ).to eq 1
         | 
| 65 | 
            +
              }
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              specify {
         | 
| 68 | 
            +
                expect(
         | 
| 69 | 
            +
                  None.match {
         | 
| 70 | 
            +
                    none { 0 }
         | 
| 71 | 
            +
                    some { 1 }
         | 
| 72 | 
            +
                  }
         | 
| 73 | 
            +
                ).to eq 0
         | 
| 74 | 
            +
              }
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            describe Deterministic::Option::None do
         | 
| 79 | 
            +
              #  it_behaves_like 'a Monad' do
         | 
| 80 | 
            +
              #   let(:monad) { described_class }
         | 
| 81 | 
            +
              # end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              specify { expect(described_class.new).to eq None }
         | 
| 84 | 
            +
              specify { expect(described_class.new.some?).to be_falsey }
         | 
| 85 | 
            +
              specify { expect(described_class.new.none?).to be_truthy }
         | 
| 86 | 
            +
              # specify { expect { described_class.new.value }.to raise_error RuntimeError }
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              specify { expect(described_class.new.fmap { |n| n + 1}).to eq None }
         | 
| 89 | 
            +
              specify { expect(described_class.new.map { |n| nil }).to eq None }
         | 
| 90 | 
            +
              specify { expect(described_class.new).to eq Option::None.new }
         | 
| 91 | 
            +
              specify { expect(described_class.new).to eq None }
         | 
| 92 | 
            +
            end
         | 
| @@ -95,7 +95,7 @@ describe Deterministic::Result do | |
| 95 95 | 
             
                    Success(self)
         | 
| 96 96 | 
             
                  end
         | 
| 97 97 |  | 
| 98 | 
            -
                  def  | 
| 98 | 
            +
                  def inspect
         | 
| 99 99 | 
             
                    "Step #{@step}"
         | 
| 100 100 | 
             
                  end
         | 
| 101 101 |  | 
| @@ -114,7 +114,7 @@ describe Deterministic::Result do | |
| 114 114 |  | 
| 115 115 | 
             
                it "works" do
         | 
| 116 116 | 
             
                  test = SelfContextUnderTest.new.call
         | 
| 117 | 
            -
                  expect(test).to be_a Success
         | 
| 117 | 
            +
                  expect(test).to be_a described_class::Success
         | 
| 118 118 | 
             
                  expect(test.inspect).to eq "Success(Step 3)"
         | 
| 119 119 | 
             
                end
         | 
| 120 120 | 
             
              end
         | 
| @@ -144,7 +144,7 @@ describe Deterministic::Result do | |
| 144 144 | 
             
                  end
         | 
| 145 145 |  | 
| 146 146 | 
             
                  actual = Success(1) >= method(:error)
         | 
| 147 | 
            -
                  expect(actual.inspect).to eq "Failure(error 1)"
         | 
| 147 | 
            +
                  expect(actual.inspect).to eq "Failure(#<RuntimeError: error 1>)"
         | 
| 148 148 | 
             
                end
         | 
| 149 149 | 
             
              end
         | 
| 150 150 | 
             
            end
         | 
| @@ -4,7 +4,7 @@ require_relative 'result_shared' | |
| 4 4 |  | 
| 5 5 | 
             
            include Deterministic
         | 
| 6 6 |  | 
| 7 | 
            -
            describe Deterministic::Failure do
         | 
| 7 | 
            +
            describe Deterministic::Result::Failure do
         | 
| 8 8 |  | 
| 9 9 | 
             
              it_behaves_like 'a Monad' do
         | 
| 10 10 | 
             
                let(:monad) { described_class }
         | 
| @@ -15,6 +15,8 @@ describe Deterministic::Failure do | |
| 15 15 | 
             
              specify { expect(subject).to be_an_instance_of described_class }
         | 
| 16 16 | 
             
              specify { expect(subject).to be_failure }
         | 
| 17 17 | 
             
              specify { expect(subject).not_to be_success }
         | 
| 18 | 
            +
              specify { expect(subject.success?).to be_falsey }
         | 
| 19 | 
            +
              specify { expect(subject.failure?).to be_truthy }
         | 
| 18 20 |  | 
| 19 21 | 
             
              specify { expect(subject).to be_an_instance_of described_class }
         | 
| 20 22 | 
             
              specify { expect(subject).to eq(described_class.new(1)) }
         | 
| @@ -104,7 +104,7 @@ describe Deterministic::Result::Match do | |
| 104 104 |  | 
| 105 105 | 
             
                expect(
         | 
| 106 106 | 
             
                  Failure(error_hash).match do
         | 
| 107 | 
            -
                    failure(: | 
| 107 | 
            +
                    failure(:null) {|v| raise "We should not get to this point" }
         | 
| 108 108 | 
             
                    failure(:needs_more_salt) do |error|
         | 
| 109 109 | 
             
                      "Successfully failed with #{error[:arbitrary]}!"
         | 
| 110 110 | 
             
                    end
         | 
| @@ -4,7 +4,7 @@ require_relative 'result_shared' | |
| 4 4 |  | 
| 5 5 | 
             
            include Deterministic
         | 
| 6 6 |  | 
| 7 | 
            -
            describe Deterministic::Success do
         | 
| 7 | 
            +
            describe Deterministic::Result::Success do
         | 
| 8 8 |  | 
| 9 9 | 
             
              it_behaves_like 'a Monad' do
         | 
| 10 10 | 
             
                let(:monad) { described_class }
         | 
| @@ -15,6 +15,8 @@ describe Deterministic::Success do | |
| 15 15 | 
             
              specify { expect(subject).to be_an_instance_of described_class }
         | 
| 16 16 | 
             
              specify { expect(subject).to be_success }
         | 
| 17 17 | 
             
              specify { expect(subject).not_to be_failure }
         | 
| 18 | 
            +
              specify { expect(subject.success?).to be_truthy }
         | 
| 19 | 
            +
              specify { expect(subject.failure?).to be_falsey }
         | 
| 18 20 |  | 
| 19 21 | 
             
              specify { expect(subject).to be_an_instance_of described_class }
         | 
| 20 22 | 
             
              specify { expect(subject).to eq(described_class.new(1)) }
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: deterministic
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.12.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Piotr Zolnierek
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2014-08- | 
| 11 | 
            +
            date: 2014-08-26 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -127,23 +127,24 @@ files: | |
| 127 127 | 
             
            - lib/deterministic.rb
         | 
| 128 128 | 
             
            - lib/deterministic/core_ext/object/result.rb
         | 
| 129 129 | 
             
            - lib/deterministic/core_ext/result.rb
         | 
| 130 | 
            +
            - lib/deterministic/match.rb
         | 
| 130 131 | 
             
            - lib/deterministic/maybe.rb
         | 
| 131 132 | 
             
            - lib/deterministic/monad.rb
         | 
| 132 | 
            -
            - lib/deterministic/ | 
| 133 | 
            +
            - lib/deterministic/null.rb
         | 
| 134 | 
            +
            - lib/deterministic/option.rb
         | 
| 133 135 | 
             
            - lib/deterministic/result.rb
         | 
| 134 136 | 
             
            - lib/deterministic/result/chain.rb
         | 
| 135 | 
            -
            - lib/deterministic/result/failure.rb
         | 
| 136 | 
            -
            - lib/deterministic/result/match.rb
         | 
| 137 | 
            -
            - lib/deterministic/result/success.rb
         | 
| 138 137 | 
             
            - lib/deterministic/version.rb
         | 
| 139 138 | 
             
            - spec/examples/bookings_spec.rb
         | 
| 139 | 
            +
            - spec/examples/config_spec.rb
         | 
| 140 140 | 
             
            - spec/examples/validate_address_spec.rb
         | 
| 141 141 | 
             
            - spec/lib/deterministic/core_ext/object/either_spec.rb
         | 
| 142 142 | 
             
            - spec/lib/deterministic/core_ext/result_spec.rb
         | 
| 143 143 | 
             
            - spec/lib/deterministic/maybe_spec.rb
         | 
| 144 144 | 
             
            - spec/lib/deterministic/monad_axioms.rb
         | 
| 145 145 | 
             
            - spec/lib/deterministic/monad_spec.rb
         | 
| 146 | 
            -
            - spec/lib/deterministic/ | 
| 146 | 
            +
            - spec/lib/deterministic/null_spec.rb
         | 
| 147 | 
            +
            - spec/lib/deterministic/option_spec.rb
         | 
| 147 148 | 
             
            - spec/lib/deterministic/result/chain_spec.rb
         | 
| 148 149 | 
             
            - spec/lib/deterministic/result/failure_spec.rb
         | 
| 149 150 | 
             
            - spec/lib/deterministic/result/match_spec.rb
         | 
| @@ -177,13 +178,15 @@ specification_version: 4 | |
| 177 178 | 
             
            summary: see above
         | 
| 178 179 | 
             
            test_files:
         | 
| 179 180 | 
             
            - spec/examples/bookings_spec.rb
         | 
| 181 | 
            +
            - spec/examples/config_spec.rb
         | 
| 180 182 | 
             
            - spec/examples/validate_address_spec.rb
         | 
| 181 183 | 
             
            - spec/lib/deterministic/core_ext/object/either_spec.rb
         | 
| 182 184 | 
             
            - spec/lib/deterministic/core_ext/result_spec.rb
         | 
| 183 185 | 
             
            - spec/lib/deterministic/maybe_spec.rb
         | 
| 184 186 | 
             
            - spec/lib/deterministic/monad_axioms.rb
         | 
| 185 187 | 
             
            - spec/lib/deterministic/monad_spec.rb
         | 
| 186 | 
            -
            - spec/lib/deterministic/ | 
| 188 | 
            +
            - spec/lib/deterministic/null_spec.rb
         | 
| 189 | 
            +
            - spec/lib/deterministic/option_spec.rb
         | 
| 187 190 | 
             
            - spec/lib/deterministic/result/chain_spec.rb
         | 
| 188 191 | 
             
            - spec/lib/deterministic/result/failure_spec.rb
         | 
| 189 192 | 
             
            - spec/lib/deterministic/result/match_spec.rb
         | 
| @@ -1,66 +0,0 @@ | |
| 1 | 
            -
            module Deterministic::PatternMatching
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              def match(context=nil, &block)
         | 
| 4 | 
            -
                context ||= block.binding.eval('self')
         | 
| 5 | 
            -
                match = Match.new(self, context)
         | 
| 6 | 
            -
                match.instance_eval &block
         | 
| 7 | 
            -
                match.call
         | 
| 8 | 
            -
              end
         | 
| 9 | 
            -
             | 
| 10 | 
            -
              class NoMatchError < StandardError; end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
              class Match
         | 
| 13 | 
            -
                def initialize(container, context)
         | 
| 14 | 
            -
                  @container  = container
         | 
| 15 | 
            -
                  @context    = context
         | 
| 16 | 
            -
                  @collection = []
         | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                def call
         | 
| 20 | 
            -
                  matcher = @collection.detect { |m| m.matches?(@container.value) }
         | 
| 21 | 
            -
                  raise NoMatchError, "No match could be made for #{@container.inspect}" if matcher.nil?
         | 
| 22 | 
            -
                  @context.instance_exec(@container.value, &matcher.block)
         | 
| 23 | 
            -
                end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                # TODO: Result specific DSL, will need to move it to Result, later on
         | 
| 26 | 
            -
                %w[Success Failure Result].each do |s|
         | 
| 27 | 
            -
                  define_method s.downcase.to_sym do |value=nil, &block|
         | 
| 28 | 
            -
                    klas = Module.const_get(s)
         | 
| 29 | 
            -
                    push(klas, value, block)
         | 
| 30 | 
            -
                  end
         | 
| 31 | 
            -
                end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                # catch-all
         | 
| 34 | 
            -
                def any(value=nil, &result_block)
         | 
| 35 | 
            -
                  push(Object, value, result_block)
         | 
| 36 | 
            -
                end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
              private
         | 
| 39 | 
            -
                Matcher = Struct.new(:condition, :block) do
         | 
| 40 | 
            -
                  def matches?(value)
         | 
| 41 | 
            -
                    condition.call(value)
         | 
| 42 | 
            -
                  end
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                def push(type, condition, result_block)
         | 
| 46 | 
            -
                  condition_pred = case
         | 
| 47 | 
            -
                  when condition.nil?;          ->(v) { true }
         | 
| 48 | 
            -
                  when condition.is_a?(Proc);   condition
         | 
| 49 | 
            -
                  when condition.is_a?(Class);  ->(v) { condition === @container.value }
         | 
| 50 | 
            -
                  else                          ->(v) { @container.value == condition }
         | 
| 51 | 
            -
                  end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                  matcher_pred = compose_predicates(type_pred[type], condition_pred)
         | 
| 54 | 
            -
                  @collection << Matcher.new(matcher_pred, result_block)
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                def compose_predicates(f, g)
         | 
| 58 | 
            -
                  ->(*args) { f[*args] && g[*args] }
         | 
| 59 | 
            -
                end
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                # return a partial function for matching a matcher's type
         | 
| 62 | 
            -
                def type_pred
         | 
| 63 | 
            -
                  (->(type, x) { @container.is_a? type }).curry
         | 
| 64 | 
            -
                end
         | 
| 65 | 
            -
              end
         | 
| 66 | 
            -
            end
         | 
| @@ -1,49 +0,0 @@ | |
| 1 | 
            -
            require "spec_helper"
         | 
| 2 | 
            -
            require "deterministic/none"
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            describe None do
         | 
| 5 | 
            -
              it "None is a Singleton" do
         | 
| 6 | 
            -
                expect(None.instance).to be_a None
         | 
| 7 | 
            -
                expect { None.new }.to raise_error(NoMethodError, "private method `new' called for None:Class")
         | 
| 8 | 
            -
              end
         | 
| 9 | 
            -
             | 
| 10 | 
            -
              it "explicit conversions" do
         | 
| 11 | 
            -
                expect(None.to_s).to eq 'None'
         | 
| 12 | 
            -
                expect(None.inspect).to eq 'None'
         | 
| 13 | 
            -
              end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
              it "compares to None" do
         | 
| 16 | 
            -
                expect(None === None.instance).to be_truthy
         | 
| 17 | 
            -
                expect(None.instance === None).to be_truthy
         | 
| 18 | 
            -
                expect(None.instance).to eq None
         | 
| 19 | 
            -
                expect(None).to eq None.instance
         | 
| 20 | 
            -
                expect(1).not_to be None
         | 
| 21 | 
            -
                expect(1).not_to be None.instance
         | 
| 22 | 
            -
                expect(None.instance).not_to be 1
         | 
| 23 | 
            -
                expect(None).not_to be 1
         | 
| 24 | 
            -
                expect(None.instance).not_to be_nil
         | 
| 25 | 
            -
                expect(None).not_to be_nil
         | 
| 26 | 
            -
              end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
              it "implicit conversions" do
         | 
| 29 | 
            -
                none = None.instance
         | 
| 30 | 
            -
                expect(none.to_str).to eq ""
         | 
| 31 | 
            -
                expect(none.to_ary).to eq []
         | 
| 32 | 
            -
                expect("" + none).to eq ""
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                a, b, c = none
         | 
| 35 | 
            -
                expect(a).to be_nil
         | 
| 36 | 
            -
                expect(b).to be_nil
         | 
| 37 | 
            -
                expect(c).to be_nil
         | 
| 38 | 
            -
              end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
              it "mimicks other classes and returns None for their public methods" do
         | 
| 41 | 
            -
                class UnderMimickTest
         | 
| 42 | 
            -
                  def test; end
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                mimick = None.mimic(UnderMimickTest)
         | 
| 46 | 
            -
                expect(mimick.test).to be_none
         | 
| 47 | 
            -
                expect { mimick.i_dont_exist}.to raise_error(NoMethodError)
         | 
| 48 | 
            -
              end
         | 
| 49 | 
            -
            end
         |