deterministic 0.12.1 → 0.13.1
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/HISTORY.md +18 -0
- data/README.md +92 -35
- data/lib/deterministic/either.rb +35 -0
- data/lib/deterministic/match.rb +3 -2
- data/lib/deterministic/monad.rb +1 -0
- data/lib/deterministic/option.rb +28 -11
- data/lib/deterministic/result.rb +39 -4
- data/lib/deterministic/version.rb +1 -1
- data/lib/deterministic.rb +1 -1
- data/spec/lib/deterministic/either_spec.rb +29 -0
- data/spec/lib/deterministic/option_spec.rb +54 -25
- metadata +7 -5
- data/lib/deterministic/result/chain.rb +0 -27
- /data/spec/lib/deterministic/result/{chain_spec.rb → result_map.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 120b61f2caf3653ca63fc4bca59908c8c3add91b
|
|
4
|
+
data.tar.gz: 2813dd0b90f301a5afcbc7861e446c3bdc507f15
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3195c0fbd1f114843e7003e84b9e24ba129933986ef948f44f19be83202719148b44554d27146acd0addd728aab98bb265356ea1d14e8338b8eaee705bdd91e
|
|
7
|
+
data.tar.gz: 721194e06a0618864efffcf079f6cc4220c58b3e0a962e7f2c43186d01e2bcc8825d34999c7c8a4abe8270bd6153901d30dd46393f7e8327a14bdb640a95f094
|
data/HISTORY.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## v0.13.0
|
|
2
|
+
|
|
3
|
+
- Add `Option#value_to_a`
|
|
4
|
+
- Add `Option#+`
|
|
5
|
+
- Make `None` a real monad (little impact on the real world)
|
|
6
|
+
- Add `Either`
|
|
7
|
+
- Add `Option#value_or`
|
|
8
|
+
- `None#value` is now private
|
|
9
|
+
|
|
10
|
+
## v0.12.1
|
|
11
|
+
|
|
12
|
+
- Fix backwards compatibility with Ruby < 2.0.0
|
|
13
|
+
|
|
14
|
+
## v0.12.0
|
|
15
|
+
|
|
16
|
+
- Add Option
|
|
17
|
+
- Nest `Success` and `Failure` under `Result`
|
|
18
|
+
|
|
1
19
|
## v0.10.0
|
|
2
20
|
** breaking changes **
|
|
3
21
|
|
data/README.md
CHANGED
|
@@ -6,6 +6,29 @@ Deterministic is to help your code to be more confident, it's specialty is flow
|
|
|
6
6
|
|
|
7
7
|
This is a spiritual successor of the [Monadic gem](http://github.com/pzol/monadic). The goal of the rewrite is to get away from a bit to forceful aproach I took in Monadic, especially when it comes to coercing monads, but also a more practical but at the same time more strict adherence to monad laws.
|
|
8
8
|
|
|
9
|
+
## Patterns
|
|
10
|
+
|
|
11
|
+
Deterministic provides different monads, here is a short guide, when to use which
|
|
12
|
+
|
|
13
|
+
#### Result: Success & Failure
|
|
14
|
+
- an operation which can succeed or fail
|
|
15
|
+
- the result (content) of of the success or failure is important
|
|
16
|
+
- you are building one thing
|
|
17
|
+
- chaining: if one fails (Failure), don't execute the rest
|
|
18
|
+
|
|
19
|
+
#### Option: Some & None
|
|
20
|
+
- an operation which returns either some result or nothing
|
|
21
|
+
- in case it returns nothing it is not important to know why
|
|
22
|
+
- you are working rather with a collection of things
|
|
23
|
+
- chaining: execute all and then select the successful ones (Some)
|
|
24
|
+
|
|
25
|
+
#### Either: Left & Right
|
|
26
|
+
- an operation which returns several good and bad results
|
|
27
|
+
- the results of both are important
|
|
28
|
+
- chaining: if one fails, continue, the content of the failed and successful are important
|
|
29
|
+
|
|
30
|
+
#### Maybe
|
|
31
|
+
- an object may be nil, you want to avoid endless nil? checks
|
|
9
32
|
|
|
10
33
|
## Usage
|
|
11
34
|
|
|
@@ -19,8 +42,6 @@ Failure(1).to_s # => "1"
|
|
|
19
42
|
Failure(Failure(1)) # => Failure(1)
|
|
20
43
|
```
|
|
21
44
|
|
|
22
|
-
#### `fmap(self: Result(a), op: |a| -> b) -> Result(b)`
|
|
23
|
-
|
|
24
45
|
Maps a `Result` with the value `a` to the same `Result` with the value `b`.
|
|
25
46
|
|
|
26
47
|
```ruby
|
|
@@ -28,82 +49,62 @@ Success(1).fmap { |v| v + 1} # => Success(2)
|
|
|
28
49
|
Failure(1).fmap { |v| v - 1} # => Failure(0)
|
|
29
50
|
```
|
|
30
51
|
|
|
31
|
-
#### `bind(self: Result(a), op: |a| -> Result(b)) -> Result(b)`
|
|
32
|
-
|
|
33
52
|
Maps a `Result` with the value `a` to another `Result` with the value `b`.
|
|
34
53
|
|
|
35
54
|
```ruby
|
|
36
55
|
Success(1).bind { |v| Failure(v + 1) } # => Failure(2)
|
|
37
|
-
Failure(1).
|
|
56
|
+
Failure(1).bind { |v| Success(v - 1) } # => Success(0)
|
|
38
57
|
```
|
|
39
58
|
|
|
40
|
-
#### `map(self: Success(a), op: |a| -> Result(b)) -> Result(b)`
|
|
41
|
-
|
|
42
59
|
Maps a `Success` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Success`.
|
|
43
60
|
|
|
44
61
|
```ruby
|
|
45
62
|
Success(1).map { |n| n + 1 } # => Success(2)
|
|
46
63
|
Failure(0).map { |n| n + 1 } # => Failure(0)
|
|
47
64
|
```
|
|
48
|
-
|
|
49
|
-
#### `map_err(self: Failure(a), op: |a| -> Result(b)) -> Result(b)`
|
|
50
|
-
|
|
51
65
|
Maps a `Failure` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Failure`.
|
|
52
66
|
|
|
53
67
|
```ruby
|
|
54
|
-
Failure(1).map_err { |n| n + 1 }
|
|
55
|
-
Success(0).map_err { |n| n + 1 }
|
|
68
|
+
Failure(1).map_err { |n| n + 1 } # => Success(2)
|
|
69
|
+
Success(0).map_err { |n| n + 1 } # => Success(0)
|
|
56
70
|
```
|
|
57
71
|
|
|
58
|
-
#### `try(self: Success(a), op: |a| -> Result(b)) -> Result(b)`
|
|
59
|
-
|
|
60
|
-
Just like `#map`, transforms `a` to another `Result`, but it will also catch raised exceptions and wrap them with a `Failure`.
|
|
61
|
-
|
|
62
72
|
```ruby
|
|
63
73
|
Success(0).try { |n| raise "Error" } # => Failure(Error)
|
|
64
74
|
```
|
|
65
75
|
|
|
66
|
-
#### `and(self: Success(a), other: Result(b)) -> Result(b)`
|
|
67
|
-
|
|
68
76
|
Replaces `Success a` with `Result b`. If a `Failure` is passed as argument, it is ignored.
|
|
69
77
|
|
|
70
78
|
```ruby
|
|
71
|
-
Success(1).and Success(2)
|
|
72
|
-
Failure(1).and Success(2)
|
|
79
|
+
Success(1).and Success(2) # => Success(2)
|
|
80
|
+
Failure(1).and Success(2) # => Failure(1)
|
|
73
81
|
```
|
|
74
82
|
|
|
75
|
-
#### `and_then(self: Success(a), op: |a| -> Result(b)) -> Result(b)`
|
|
76
|
-
|
|
77
83
|
Replaces `Success a` with the result of the block. If a `Failure` is passed as argument, it is ignored.
|
|
78
84
|
|
|
79
85
|
```ruby
|
|
80
|
-
Success(1).and_then { Success(2) }
|
|
81
|
-
Failure(1).and_then { Success(2) }
|
|
86
|
+
Success(1).and_then { Success(2) } # => Success(2)
|
|
87
|
+
Failure(1).and_then { Success(2) } # => Failure(1)
|
|
82
88
|
```
|
|
83
89
|
|
|
84
|
-
#### `or(self: Failure(a), other: Result(b)) -> Result(b)`
|
|
85
90
|
Replaces `Failure a` with `Result`. If a `Failure` is passed as argument, it is ignored.
|
|
86
91
|
|
|
87
92
|
```ruby
|
|
88
|
-
Success(1).or Success(2)
|
|
89
|
-
Failure(1).or Success(1)
|
|
93
|
+
Success(1).or Success(2) # => Success(1)
|
|
94
|
+
Failure(1).or Success(1) # => Success(1)
|
|
90
95
|
```
|
|
91
96
|
|
|
92
|
-
#### `or_else(self: Failure(a), op: |a| -> Result(b)) -> Result(b)`
|
|
93
|
-
|
|
94
97
|
Replaces `Failure a` with the result of the block. If a `Success` is passed as argument, it is ignored.
|
|
95
98
|
|
|
96
99
|
```ruby
|
|
97
|
-
Success(1).or_else { Success(2) }
|
|
98
|
-
Failure(1).or_else { |n| Success(n)}
|
|
100
|
+
Success(1).or_else { Success(2) } # => Success(1)
|
|
101
|
+
Failure(1).or_else { |n| Success(n)} # => Success(1)
|
|
99
102
|
```
|
|
100
103
|
|
|
101
|
-
#### `pipe(self: Result(a), op: |Result(a)| -> b) -> Result(a)`
|
|
102
|
-
|
|
103
104
|
Executes the block passed, but completely ignores its result. If an error is raised within the block it will **NOT** be catched.
|
|
104
105
|
|
|
105
106
|
```ruby
|
|
106
|
-
Success(1).try { |n| log(n.value) }
|
|
107
|
+
Success(1).try { |n| log(n.value) } # => Success(1)
|
|
107
108
|
```
|
|
108
109
|
|
|
109
110
|
The value or block result must always be a `Result` i.e. `Success` or `Failure`.
|
|
@@ -256,7 +257,7 @@ end # => "catch-all"
|
|
|
256
257
|
## core_ext
|
|
257
258
|
You can use a core extension, to include Result in your own class or in Object, i.e. in all classes.
|
|
258
259
|
|
|
259
|
-
|
|
260
|
+
|
|
260
261
|
|
|
261
262
|
```ruby
|
|
262
263
|
require 'deterministic/core_ext/object/result'
|
|
@@ -267,6 +268,62 @@ Success(1).success? # => true
|
|
|
267
268
|
Failure(1).result? # => true
|
|
268
269
|
```
|
|
269
270
|
|
|
271
|
+
## Option
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
Some(1).some? # #=> true
|
|
275
|
+
Some(1).none? # #=> false
|
|
276
|
+
None.some? # #=> false
|
|
277
|
+
None.none? # #=> true
|
|
278
|
+
|
|
279
|
+
Some(1).fmap { |n| n + 1 } # => Some(2)
|
|
280
|
+
None.fmap { |n| n + 1 } # => None
|
|
281
|
+
|
|
282
|
+
Some(1).map { |n| Some(n + 1) } # => Some(2)
|
|
283
|
+
Some(1).map { |n| None } # => None
|
|
284
|
+
None.map { |n| Some(n + 1) } # => None
|
|
285
|
+
|
|
286
|
+
Some(1).value # => 1
|
|
287
|
+
Some(1).value_or(2) # => 1
|
|
288
|
+
None.value # => NoMethodError
|
|
289
|
+
None.value_or(0) # => 0
|
|
290
|
+
|
|
291
|
+
Some(1).value_to_a # => Some([1])
|
|
292
|
+
Some([1]).value_to_a # => Some([1])
|
|
293
|
+
None.value_to_a # => None
|
|
294
|
+
|
|
295
|
+
Some(1) + Some(1) # => Some(2)
|
|
296
|
+
Some([1]) + Some(1) # => TypeError: No implicit conversion
|
|
297
|
+
None + Some(1) # => Some(1)
|
|
298
|
+
Some(1) + None # => Some(1)
|
|
299
|
+
Some([1]) + None + Some([2]) # => Some([1, 2])
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Coercion
|
|
303
|
+
```ruby
|
|
304
|
+
Option.any?(nil) # => None
|
|
305
|
+
Option.any?([]) # => None
|
|
306
|
+
Option.any?({}) # => None
|
|
307
|
+
Option.any?(1) # => Some(1)
|
|
308
|
+
|
|
309
|
+
Option.some?(nil) # => None
|
|
310
|
+
Option.some?([]) # => Some([])
|
|
311
|
+
Option.some?({}) # => Some({})
|
|
312
|
+
Option.some?(1) # => Some(1)
|
|
313
|
+
|
|
314
|
+
Option.try! { 1 } # => Some(1)
|
|
315
|
+
Option.try! { raise "error"} # => None
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Pattern Matching
|
|
319
|
+
```ruby
|
|
320
|
+
Some(1).match {
|
|
321
|
+
some(1) { |n| n + 1 }
|
|
322
|
+
some { 1 }
|
|
323
|
+
none { 0 }
|
|
324
|
+
} # => 2
|
|
325
|
+
```
|
|
326
|
+
|
|
270
327
|
|
|
271
328
|
## Maybe
|
|
272
329
|
The simplest NullObject wrapper there can be. It adds `#some?` and `#null?` to `Object` though.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Deterministic
|
|
2
|
+
class Either
|
|
3
|
+
include Monad
|
|
4
|
+
class << self
|
|
5
|
+
public :new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def initialize(left=[], right=[])
|
|
9
|
+
@left, @right = left, right
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :left, :right
|
|
13
|
+
|
|
14
|
+
def +(other)
|
|
15
|
+
raise Deterministic::Monad::NotMonadError, "Expected an Either, got #{other.class}" unless other.is_a? Either
|
|
16
|
+
|
|
17
|
+
Either.new(left + other.left, right + other.right)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
undef :value
|
|
21
|
+
|
|
22
|
+
def inspect
|
|
23
|
+
"Either(left: #{left.inspect}, right: #{right.inspect})"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
module_function
|
|
28
|
+
def Left(value)
|
|
29
|
+
Either.new(Array[value], [])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def Right(value)
|
|
33
|
+
Either.new([], Array[value])
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/deterministic/match.rb
CHANGED
|
@@ -18,9 +18,10 @@ module Deterministic
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def call
|
|
21
|
-
|
|
21
|
+
value = @container.respond_to?(:value) ? @container.value : nil
|
|
22
|
+
matcher = @collection.detect { |m| m.matches?(value) }
|
|
22
23
|
raise NoMatchError, "No match could be made for #{@container.inspect}" if matcher.nil?
|
|
23
|
-
@context.instance_exec(
|
|
24
|
+
@context.instance_exec(value, &matcher.block)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# catch-all
|
data/lib/deterministic/monad.rb
CHANGED
data/lib/deterministic/option.rb
CHANGED
|
@@ -45,6 +45,22 @@ module Deterministic
|
|
|
45
45
|
bind(proc || block)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
## Convert the inner value to an Array
|
|
49
|
+
def value_to_a
|
|
50
|
+
map { self.class.new(Array(value)) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
## Add the inner values of two Some
|
|
54
|
+
def +(other)
|
|
55
|
+
return other if none?
|
|
56
|
+
fmap { |v|
|
|
57
|
+
other.match {
|
|
58
|
+
some { v + other.value }
|
|
59
|
+
none { self }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
48
64
|
def some?
|
|
49
65
|
is_a? Some
|
|
50
66
|
end
|
|
@@ -53,35 +69,36 @@ module Deterministic
|
|
|
53
69
|
is_a? None
|
|
54
70
|
end
|
|
55
71
|
|
|
72
|
+
def value_or(default)
|
|
73
|
+
return default if none?
|
|
74
|
+
return value
|
|
75
|
+
end
|
|
76
|
+
|
|
56
77
|
class Some < Option
|
|
57
78
|
class << self; public :new; end
|
|
58
79
|
end
|
|
59
80
|
|
|
60
81
|
class None < Option
|
|
61
82
|
class << self; public :new; end
|
|
62
|
-
def initialize(*args)
|
|
83
|
+
def initialize(*args)
|
|
84
|
+
@value = self
|
|
85
|
+
end
|
|
63
86
|
|
|
64
87
|
def inspect
|
|
65
88
|
"None"
|
|
66
89
|
end
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def none(*args)
|
|
91
|
+
private :value
|
|
92
|
+
|
|
93
|
+
def fmap(*args)
|
|
72
94
|
self
|
|
73
95
|
end
|
|
74
96
|
|
|
75
|
-
alias :
|
|
76
|
-
alias :map :none
|
|
97
|
+
alias :map :fmap
|
|
77
98
|
|
|
78
99
|
def ==(other)
|
|
79
100
|
other.class == self.class
|
|
80
101
|
end
|
|
81
|
-
|
|
82
|
-
# def value
|
|
83
|
-
# self # raise "value called on a None"
|
|
84
|
-
# end
|
|
85
102
|
end
|
|
86
103
|
end
|
|
87
104
|
|
data/lib/deterministic/result.rb
CHANGED
|
@@ -18,7 +18,11 @@ module Deterministic
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
include PatternMatching
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
# This is an abstract class, can't ever instantiate it directly
|
|
23
|
+
class << self
|
|
24
|
+
protected :new
|
|
25
|
+
end
|
|
22
26
|
|
|
23
27
|
def success?
|
|
24
28
|
is_a? Success
|
|
@@ -28,6 +32,8 @@ module Deterministic
|
|
|
28
32
|
is_a? Failure
|
|
29
33
|
end
|
|
30
34
|
|
|
35
|
+
# `pipe(self: Result(a), op: |Result(a)| -> b) -> Result(a)`
|
|
36
|
+
# Executes the block passed, but completely ignores its result. If an error is raised within the block it will **NOT** be catched.
|
|
31
37
|
def pipe(proc=nil, &block)
|
|
32
38
|
(proc || block).call(self)
|
|
33
39
|
self
|
|
@@ -35,32 +41,61 @@ module Deterministic
|
|
|
35
41
|
|
|
36
42
|
alias :** :pipe
|
|
37
43
|
|
|
44
|
+
# `pipe(self: Result(a), op: |Result(a)| -> b) -> Result(a)`
|
|
45
|
+
# Replaces `Success a` with `Result b`. If a `Failure` is passed as argument, it is ignored.
|
|
38
46
|
def and(other)
|
|
39
47
|
return self if failure?
|
|
40
48
|
raise NotMonadError, "Expected #{other.inspect} to be an Result" unless other.is_a? Result
|
|
41
49
|
other
|
|
42
50
|
end
|
|
43
51
|
|
|
52
|
+
# `and_then(self: Success(a), op: |a| -> Result(b)) -> Result(b)`
|
|
53
|
+
# Replaces `Success a` with the result of the block. If a `Failure` is passed as argument, it is ignored.
|
|
44
54
|
def and_then(&block)
|
|
45
55
|
return self if failure?
|
|
46
56
|
bind(&block)
|
|
47
57
|
end
|
|
48
58
|
|
|
59
|
+
# `or(self: Failure(a), other: Result(b)) -> Result(b)`
|
|
60
|
+
# Replaces `Failure a` with `Result`. If a `Failure` is passed as argument, it is ignored.
|
|
49
61
|
def or(other)
|
|
50
62
|
return self if success?
|
|
51
63
|
raise NotMonadError, "Expected #{other.inspect} to be an Result" unless other.is_a? Result
|
|
52
64
|
return other
|
|
53
65
|
end
|
|
54
66
|
|
|
67
|
+
# `or_else(self: Failure(a), op: |a| -> Result(b)) -> Result(b)`
|
|
68
|
+
# Replaces `Failure a` with the result of the block. If a `Success` is passed as argument, it is ignored.
|
|
55
69
|
def or_else(&block)
|
|
56
70
|
return self if success?
|
|
57
71
|
bind(&block)
|
|
58
72
|
end
|
|
59
73
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
# `map(self: Success(a), op: |a| -> Result(b)) -> Result(b)`
|
|
75
|
+
# Maps a `Success` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Success`.
|
|
76
|
+
def map(proc=nil, &block)
|
|
77
|
+
return self if failure?
|
|
78
|
+
bind(proc || block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
alias :>> :map
|
|
82
|
+
|
|
83
|
+
# `map_err(self: Failure(a), op: |a| -> Result(b)) -> Result(b)`
|
|
84
|
+
# Maps a `Failure` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Failure`.
|
|
85
|
+
def map_err(proc=nil, &block)
|
|
86
|
+
return self if success?
|
|
87
|
+
bind(proc || block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# `pipe(self: Result(a), op: |Result(a)| -> b) -> Result(a)`
|
|
91
|
+
# Executes the block passed, but completely ignores its result. If an error is raised within the block it will **NOT** be catched.
|
|
92
|
+
def try(proc=nil, &block)
|
|
93
|
+
map(proc, &block)
|
|
94
|
+
rescue => err
|
|
95
|
+
Failure(err)
|
|
63
96
|
end
|
|
97
|
+
|
|
98
|
+
alias :>= :try
|
|
64
99
|
|
|
65
100
|
class Failure < Result
|
|
66
101
|
class << self; public :new; end
|
data/lib/deterministic.rb
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
include Deterministic
|
|
4
|
+
|
|
5
|
+
describe Deterministic::Either do
|
|
6
|
+
it "+ does not change operands" do
|
|
7
|
+
l = Left(1)
|
|
8
|
+
r = Right(2)
|
|
9
|
+
|
|
10
|
+
either = l + r
|
|
11
|
+
expect(l).to eq Left(1)
|
|
12
|
+
expect(r).to eq Right(2)
|
|
13
|
+
expect(either).to eq Either.new([1], [2])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "allows adding multiple Eithers" do
|
|
17
|
+
either = Left(1) + Left(2) + Right(:a) + Right(:b)
|
|
18
|
+
expect(either.left).to eq [1, 2]
|
|
19
|
+
expect(either.right).to eq [:a, :b]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "works" do
|
|
23
|
+
actual = [1, 2, 3, 4].inject(Either.new) { |acc, value|
|
|
24
|
+
acc + (value % 2 == 0 ? Right(value) : Left(value))
|
|
25
|
+
}
|
|
26
|
+
expect(actual).to eq Either.new([1, 3], [2, 4])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -11,6 +11,7 @@ describe Deterministic::Option do
|
|
|
11
11
|
|
|
12
12
|
# any?
|
|
13
13
|
specify { expect(described_class.any?(nil)).to be_none }
|
|
14
|
+
specify { expect(described_class.any?(None)).to be_none }
|
|
14
15
|
specify { expect(described_class.any?("")).to be_none }
|
|
15
16
|
specify { expect(described_class.any?([])).to be_none }
|
|
16
17
|
specify { expect(described_class.any?({})).to be_none }
|
|
@@ -21,21 +22,53 @@ describe Deterministic::Option do
|
|
|
21
22
|
specify { expect(described_class.try! { raise "error" }).to be_none }
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
describe Deterministic::Option
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
25
|
+
describe Deterministic::Option do
|
|
26
|
+
# it_behaves_like 'a Monad' do
|
|
27
|
+
# let(:monad) { described_class }
|
|
28
|
+
# end
|
|
29
|
+
|
|
30
|
+
specify { expect(Option::Some.new(0)).to be_a Option::Some }
|
|
31
|
+
specify { expect(Option::Some.new(0)).to eq Some(0) }
|
|
32
|
+
|
|
33
|
+
specify { expect(Option::None.new).to eq Option::None.new }
|
|
34
|
+
specify { expect(Option::None.new).to eq None }
|
|
35
|
+
|
|
36
|
+
# some?, none?
|
|
37
|
+
specify { expect(None.some?).to be_falsey }
|
|
38
|
+
specify { expect(None.none?).to be_truthy }
|
|
39
|
+
specify { expect(Some(0).some?).to be_truthy }
|
|
40
|
+
specify { expect(Some(0).none?).to be_falsey }
|
|
41
|
+
|
|
42
|
+
# value, value_or
|
|
43
|
+
specify { expect(Some(0).value).to eq 0 }
|
|
44
|
+
specify { expect(Some(1).value_or(2)).to eq 1}
|
|
45
|
+
specify { expect { None.value }.to raise_error NoMethodError }
|
|
46
|
+
specify { expect(None.value_or(2)).to eq 2}
|
|
47
|
+
|
|
48
|
+
# fmap
|
|
49
|
+
specify { expect(Some(1).fmap { |n| n + 1}).to eq Some(2) }
|
|
50
|
+
specify { expect(None.fmap { |n| n + 1}).to eq None }
|
|
51
|
+
|
|
52
|
+
# map
|
|
53
|
+
specify { expect(Some(1).map { |n| Some(n + 1)}).to eq Some(2) }
|
|
54
|
+
specify { expect(Some(1).map { |n| None }).to eq None }
|
|
55
|
+
specify { expect(None.map { |n| nil }).to eq None }
|
|
28
56
|
|
|
29
|
-
|
|
30
|
-
specify { expect(
|
|
31
|
-
specify { expect(
|
|
32
|
-
specify { expect(
|
|
33
|
-
specify { expect(described_class.new(0).value).to eq 0 }
|
|
57
|
+
# to_a
|
|
58
|
+
specify { expect(Some(1).value_to_a). to eq Some([1])}
|
|
59
|
+
specify { expect(Some([1]).value_to_a). to eq Some([1])}
|
|
60
|
+
specify { expect(None.value_to_a). to eq None}
|
|
34
61
|
|
|
35
|
-
|
|
36
|
-
specify { expect(
|
|
37
|
-
specify { expect(
|
|
62
|
+
# +
|
|
63
|
+
specify { expect(Some(1) + None).to eq Some(1) }
|
|
64
|
+
specify { expect(Some(1) + None + None).to eq Some(1) }
|
|
65
|
+
specify { expect(Some(1) + Some(1)).to eq Some(2) }
|
|
66
|
+
specify { expect(None + Some(1)).to eq Some(1) }
|
|
67
|
+
specify { expect(None + None + Some(1)).to eq Some(1) }
|
|
68
|
+
specify { expect(None + None + Some(1) + None).to eq Some(1) }
|
|
69
|
+
specify { expect { Some([1]) + Some(1)}.to raise_error TypeError}
|
|
38
70
|
|
|
71
|
+
# match
|
|
39
72
|
specify {
|
|
40
73
|
expect(
|
|
41
74
|
Some(0).match {
|
|
@@ -75,18 +108,14 @@ describe Deterministic::Option::Some do
|
|
|
75
108
|
|
|
76
109
|
end
|
|
77
110
|
|
|
78
|
-
describe Deterministic::Option::
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 }
|
|
111
|
+
describe Deterministic::Option::Some do
|
|
112
|
+
it_behaves_like 'a Monad' do
|
|
113
|
+
let(:monad) { described_class }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
87
116
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
117
|
+
describe Deterministic::Option::None do
|
|
118
|
+
it_behaves_like 'a Monad' do
|
|
119
|
+
let(:monad) { described_class }
|
|
120
|
+
end
|
|
92
121
|
end
|
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.13.1
|
|
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-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -127,27 +127,28 @@ 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/either.rb
|
|
130
131
|
- lib/deterministic/match.rb
|
|
131
132
|
- lib/deterministic/maybe.rb
|
|
132
133
|
- lib/deterministic/monad.rb
|
|
133
134
|
- lib/deterministic/null.rb
|
|
134
135
|
- lib/deterministic/option.rb
|
|
135
136
|
- lib/deterministic/result.rb
|
|
136
|
-
- lib/deterministic/result/chain.rb
|
|
137
137
|
- lib/deterministic/version.rb
|
|
138
138
|
- spec/examples/bookings_spec.rb
|
|
139
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
|
+
- spec/lib/deterministic/either_spec.rb
|
|
143
144
|
- spec/lib/deterministic/maybe_spec.rb
|
|
144
145
|
- spec/lib/deterministic/monad_axioms.rb
|
|
145
146
|
- spec/lib/deterministic/monad_spec.rb
|
|
146
147
|
- spec/lib/deterministic/null_spec.rb
|
|
147
148
|
- spec/lib/deterministic/option_spec.rb
|
|
148
|
-
- spec/lib/deterministic/result/chain_spec.rb
|
|
149
149
|
- spec/lib/deterministic/result/failure_spec.rb
|
|
150
150
|
- spec/lib/deterministic/result/match_spec.rb
|
|
151
|
+
- spec/lib/deterministic/result/result_map.rb
|
|
151
152
|
- spec/lib/deterministic/result/result_shared.rb
|
|
152
153
|
- spec/lib/deterministic/result/success_spec.rb
|
|
153
154
|
- spec/lib/deterministic/result_spec.rb
|
|
@@ -182,14 +183,15 @@ test_files:
|
|
|
182
183
|
- spec/examples/validate_address_spec.rb
|
|
183
184
|
- spec/lib/deterministic/core_ext/object/either_spec.rb
|
|
184
185
|
- spec/lib/deterministic/core_ext/result_spec.rb
|
|
186
|
+
- spec/lib/deterministic/either_spec.rb
|
|
185
187
|
- spec/lib/deterministic/maybe_spec.rb
|
|
186
188
|
- spec/lib/deterministic/monad_axioms.rb
|
|
187
189
|
- spec/lib/deterministic/monad_spec.rb
|
|
188
190
|
- spec/lib/deterministic/null_spec.rb
|
|
189
191
|
- spec/lib/deterministic/option_spec.rb
|
|
190
|
-
- spec/lib/deterministic/result/chain_spec.rb
|
|
191
192
|
- spec/lib/deterministic/result/failure_spec.rb
|
|
192
193
|
- spec/lib/deterministic/result/match_spec.rb
|
|
194
|
+
- spec/lib/deterministic/result/result_map.rb
|
|
193
195
|
- spec/lib/deterministic/result/result_shared.rb
|
|
194
196
|
- spec/lib/deterministic/result/success_spec.rb
|
|
195
197
|
- spec/lib/deterministic/result_spec.rb
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
module Deterministic
|
|
2
|
-
class Result
|
|
3
|
-
module Chain
|
|
4
|
-
def map(proc=nil, &block)
|
|
5
|
-
return self if failure?
|
|
6
|
-
bind(proc || block)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
alias :>> :map
|
|
10
|
-
|
|
11
|
-
def map_err(proc=nil, &block)
|
|
12
|
-
return self if success?
|
|
13
|
-
bind(proc || block)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def try(proc=nil, &block)
|
|
17
|
-
map(proc, &block)
|
|
18
|
-
rescue => err
|
|
19
|
-
Failure(err)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
alias :>= :try
|
|
23
|
-
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
File without changes
|