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
|