deterministic 0.10.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|