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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d6ccf85126958f17b98ac22932b29ed508584250
4
- data.tar.gz: 689d36f2f3b71bcc6192129514a8ca14b2b32001
3
+ metadata.gz: 02bcfb726015c3bfd566bb7ac4fa2ec7827f9916
4
+ data.tar.gz: 41febde66849c34ad23f4d031b0ca4cdd91b26fd
5
5
  SHA512:
6
- metadata.gz: c3c067aab6e1418a829a72505ad33f5822d3f7c749ece67aa903c306e7910ab2158cf5005f439d3dceffe032394f321109fc618c892b4324e85a2a469c8de6eb
7
- data.tar.gz: 9500d35aedd9849fd2893534b6a220256ac71696b973e86978b4d714233a17a31acd017efdd55cd0dee18afdd25522ab05a0f357e2023819784067fecce687b4
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 :: R a -> (a -> b) -> R b`
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 :: R a -> (a -> R b) -> R b`
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 :: S a -> (a -> R b) -> R b`
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 :: F a -> (a -> R b) -> R b`
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 :: S a -> ( a -> R b) -> R b`
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 :: S a -> R b -> R b`
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 :: S a -> (a -> R b) -> R b`
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 :: F a -> R b -> R b`
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 :: F a -> (a -> R b) -> R b`
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 :: R a -> (R a -> b) -> R a`
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 `#none?` to `Object` though.
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 # => None
277
- Maybe(nil).foo.bar # => None
276
+ Maybe(nil).foo # => Null
277
+ Maybe(nil).foo.bar # => Null
278
278
  Maybe({a: 1})[:a] # => 1
279
279
 
280
- Maybe(nil).none? # => true
281
- Maybe({}).none? # => false
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
- null = Maybe.mimick(Mimick)
297
- null.test # => None
298
- null.foo # => NoMethodError
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/result/match'
8
+ require 'deterministic/match'
9
9
  require 'deterministic/result/chain'
10
10
  require 'deterministic/result'
11
- require 'deterministic/result/success'
12
- require 'deterministic/result/failure'
13
- require 'deterministic/none'
11
+ require 'deterministic/option'
12
+ require 'deterministic/null'
@@ -4,11 +4,11 @@ module Deterministic
4
4
  module CoreExt
5
5
  module Result
6
6
  def success?
7
- self.is_a? Success
7
+ self.is_a? Deterministic::Result::Success
8
8
  end
9
9
 
10
10
  def failure?
11
- self.is_a? Failure
11
+ self.is_a? Deterministic::Result::Failure
12
12
  end
13
13
 
14
14
  def result?
@@ -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
@@ -1,5 +1,5 @@
1
1
  class Object
2
- def none?
2
+ def null?
3
3
  false
4
4
  end
5
5
 
@@ -9,5 +9,5 @@ class Object
9
9
  end
10
10
 
11
11
  def Maybe(obj)
12
- obj.nil? ? None.instance : obj
12
+ obj.nil? ? Null.instance : obj
13
13
  end
@@ -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, self.class).tap do |result|
29
- raise NotMonadError, "Expected #{result.inspect} to be an Result" unless result.is_a? self.class
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 to_s
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 None
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
- None.instance
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 none?
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?(:none?) && other.none?
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 none?
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
- 'None'
66
+ 'Null'
67
67
  end
68
68
 
69
69
  def ==(other)
70
- other.respond_to?(:none?) && other.none?
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
@@ -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
- def bind(proc=nil, &block)
9
- (proc || block).call(value).tap do |result|
10
- raise NotMonadError, "Expected #{result.inspect} to be an Result" unless result.is_a? self.class.superclass
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
- def to_s
57
- value.to_s
64
+
65
+ class Failure < Result
66
+ class << self; public :new; end
58
67
  end
59
-
60
- def inspect
61
- name = self.class.name.split("::")[-1]
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
@@ -1,3 +1,3 @@
1
1
  module Deterministic
2
- VERSION = "0.10.0"
2
+ VERSION = "0.12.0"
3
3
  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 be_none
7
- expect(Maybe(nil).foo.bar.baz).to be_none
8
- expect(Maybe(nil).fetch(:a)).to be_none
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 be_none
13
+ expect(Maybe("a")).not_to be_null
14
14
 
15
- # expect(Maybe[[]]).to eq be_none
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) { Identity }
12
+ # let(:monad) { monad }
12
13
  end
13
14
 
14
- specify { expect(Identity.new(1).to_s).to eq 'Identity(1)' }
15
- specify { expect(Identity.new(nil).to_s).to eq 'Identity(nil)' }
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 |_, monad|
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
- Identity.new(1).bind { |value, monad| monad.new(value + 1) }
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 to_s
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(:nothing) {|v| raise "We should not get to this point" }
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.10.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-25 00:00:00.000000000 Z
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/none.rb
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/none_spec.rb
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/none_spec.rb
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,5 +0,0 @@
1
- module Deterministic
2
- class Failure < Result
3
- class << self; public :new; end
4
- end
5
- end
@@ -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,5 +0,0 @@
1
- module Deterministic
2
- class Success < Result
3
- class << self; public :new; end
4
- end
5
- 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