deterministic 0.8.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/HISTORY.md +17 -0
- data/README.md +121 -153
- data/lib/deterministic/core_ext/object/result.rb +6 -0
- data/lib/deterministic/core_ext/{either.rb → result.rb} +2 -6
- data/lib/deterministic/monad.rb +3 -3
- data/lib/deterministic/result/chain.rb +27 -0
- data/lib/deterministic/result/failure.rb +5 -0
- data/lib/deterministic/{either → result}/match.rb +5 -5
- data/lib/deterministic/result/success.rb +5 -0
- data/lib/deterministic/{either.rb → result.rb} +29 -6
- data/lib/deterministic/version.rb +1 -1
- data/lib/deterministic.rb +5 -6
- data/spec/examples/bookings_spec.rb +3 -5
- data/spec/lib/deterministic/core_ext/object/either_spec.rb +3 -20
- data/spec/lib/deterministic/core_ext/result_spec.rb +22 -0
- data/spec/lib/deterministic/maybe_spec.rb +2 -0
- data/spec/lib/deterministic/monad_spec.rb +3 -3
- data/spec/lib/deterministic/result/chain_spec.rb +150 -0
- data/spec/lib/deterministic/{either → result}/failure_spec.rb +9 -6
- data/spec/lib/deterministic/{either → result}/match_spec.rb +5 -5
- data/spec/lib/deterministic/result/result_shared.rb +23 -0
- data/spec/lib/deterministic/{either → result}/success_spec.rb +10 -6
- data/spec/lib/deterministic/{either_spec.rb → result_spec.rb} +3 -3
- metadata +24 -27
- data/either.rb +0 -97
- data/lib/deterministic/core_ext/object/either.rb +0 -6
- data/lib/deterministic/either/attempt_all.rb +0 -50
- data/lib/deterministic/either/chain.rb +0 -22
- data/lib/deterministic/either/failure.rb +0 -22
- data/lib/deterministic/either/success.rb +0 -22
- data/spec/lib/deterministic/attempt_all_spec.rb +0 -115
- data/spec/lib/deterministic/core_ext/either_spec.rb +0 -50
- data/spec/lib/deterministic/either/chain_spec.rb +0 -80
- data/spec/lib/deterministic/either/either_shared.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6ccf85126958f17b98ac22932b29ed508584250
|
4
|
+
data.tar.gz: 689d36f2f3b71bcc6192129514a8ca14b2b32001
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3c067aab6e1418a829a72505ad33f5822d3f7c749ece67aa903c306e7910ab2158cf5005f439d3dceffe032394f321109fc618c892b4324e85a2a469c8de6eb
|
7
|
+
data.tar.gz: 9500d35aedd9849fd2893534b6a220256ac71696b973e86978b4d714233a17a31acd017efdd55cd0dee18afdd25522ab05a0f357e2023819784067fecce687b4
|
data/HISTORY.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
## v0.10.0
|
2
|
+
** breaking changes **
|
3
|
+
|
4
|
+
- Remove `Either#<<`
|
5
|
+
- Rename `Either` to `Result`
|
6
|
+
- Add `Result#pipe` aka `Result#**`
|
7
|
+
- Add `Result#map` and `Result#map_err`
|
8
|
+
|
9
|
+
## v0.9.0
|
10
|
+
** breaking changes **
|
11
|
+
|
12
|
+
- Remove `Either.attempt_all` in favor of `Either#>>` and `Either#>=`
|
13
|
+
This greatly reduces the complexity and the code necessary.
|
14
|
+
|
15
|
+
## 0.8.0 - v0.8.1
|
16
|
+
|
17
|
+
- Introduce `Either#>>` and `Either#>=`
|
data/README.md
CHANGED
@@ -9,79 +9,153 @@ This is a spiritual successor of the [Monadic gem](http://github.com/pzol/monadi
|
|
9
9
|
|
10
10
|
## Usage
|
11
11
|
|
12
|
-
### Success & Failure
|
12
|
+
### Result: Success & Failure
|
13
13
|
|
14
14
|
```ruby
|
15
|
-
Success(1).to_s
|
16
|
-
Success(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
Failure(1).to_s # => "1"
|
22
|
-
Failure(1) << Failure(2) # => Failure(1)
|
23
|
-
Failure(Failure(1)) # => Failure(1)
|
24
|
-
Failure(1).map { |v| v + 1} # => Failure(2)
|
25
|
-
Failure({a:1}).to_json # => '{"Failure": {"a":1}}'
|
15
|
+
Success(1).to_s # => "1"
|
16
|
+
Success(Success(1)) # => Success(1)
|
17
|
+
|
18
|
+
Failure(1).to_s # => "1"
|
19
|
+
Failure(Failure(1)) # => Failure(1)
|
26
20
|
```
|
27
21
|
|
28
|
-
|
22
|
+
#### `fmap :: R a -> (a -> b) -> R b`
|
23
|
+
|
24
|
+
Maps a `Result` with the value `a` to the same `Result` with the value `b`.
|
29
25
|
|
30
26
|
```ruby
|
31
|
-
Success(1).
|
32
|
-
|
27
|
+
Success(1).fmap { |v| v + 1} # => Success(2)
|
28
|
+
Failure(1).fmap { |v| v - 1} # => Failure(0)
|
29
|
+
```
|
33
30
|
|
34
|
-
|
35
|
-
|
31
|
+
#### `bind :: R a -> (a -> R b) -> R b`
|
32
|
+
|
33
|
+
Maps a `Result` with the value `a` to another `Result` with the value `b`.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
Success(1).bind { |v| Failure(v + 1) } # => Failure(2)
|
37
|
+
Failure(1).fmap { |v| Success(v - 1) } # => Success(0)
|
38
|
+
```
|
39
|
+
|
40
|
+
#### `map :: S a -> (a -> R b) -> R b`
|
41
|
+
|
42
|
+
Maps a `Success` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Success`.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Success(1).map { |n| n + 1 } # => Success(2)
|
46
|
+
Failure(0).map { |n| n + 1 } # => Failure(0)
|
47
|
+
```
|
48
|
+
|
49
|
+
#### `map_err :: F a -> (a -> R b) -> R b`
|
50
|
+
|
51
|
+
Maps a `Failure` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Failure`.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
Failure(1).map_err { |n| n + 1 } # => Success(2)
|
55
|
+
Success(0).map_err { |n| n + 1 } # => Success(0)
|
36
56
|
```
|
37
57
|
|
38
|
-
|
58
|
+
#### `try :: S a -> ( a -> R b) -> R 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
|
+
```ruby
|
63
|
+
Success(0).try { |n| raise "Error" } # => Failure(Error)
|
64
|
+
```
|
65
|
+
|
66
|
+
#### `and :: S a -> R b -> R b`
|
67
|
+
|
68
|
+
Replaces `Success a` with `Result b`. If a `Failure` is passed as argument, it is ignored.
|
39
69
|
|
40
70
|
```ruby
|
71
|
+
Success(1).and Success(2) # => Success(2)
|
41
72
|
Failure(1).and Success(2) # => Failure(1)
|
73
|
+
```
|
74
|
+
|
75
|
+
#### `and_then :: S a -> (a -> R b) -> R b`
|
76
|
+
|
77
|
+
Replaces `Success a` with the result of the block. If a `Failure` is passed as argument, it is ignored.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Success(1).and_then { Success(2) } # => Success(2)
|
42
81
|
Failure(1).and_then { Success(2) } # => Failure(1)
|
82
|
+
```
|
83
|
+
|
84
|
+
#### `or :: F a -> R b -> R b`
|
85
|
+
Replaces `Failure a` with `Result`. If a `Failure` is passed as argument, it is ignored.
|
43
86
|
|
87
|
+
```ruby
|
88
|
+
Success(1).or Success(2) # => Success(1)
|
44
89
|
Failure(1).or Success(1) # => Success(1)
|
45
|
-
Failure(1).or_else { |v| Success(v)} # => Success(1)
|
46
90
|
```
|
47
91
|
|
48
|
-
|
92
|
+
#### `or_else :: F a -> (a -> R b) -> R b`
|
93
|
+
|
94
|
+
Replaces `Failure a` with the result of the block. If a `Success` is passed as argument, it is ignored.
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
Success(1).or_else { Success(2) } # => Success(1)
|
98
|
+
Failure(1).or_else { |n| Success(n)} # => Success(1)
|
99
|
+
```
|
100
|
+
|
101
|
+
#### `pipe :: R a -> (R a -> b) -> R a`
|
102
|
+
|
103
|
+
Executes the block passed, but completely ignores its result. If an error is raised within the block it will **NOT** be catched.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
Success(1).try { |n| log(n.value) } # => Success(1)
|
107
|
+
```
|
108
|
+
|
109
|
+
The value or block result must always be a `Result` i.e. `Success` or `Failure`.
|
110
|
+
|
111
|
+
### Result Chaining
|
49
112
|
|
50
|
-
### Either.chain
|
51
113
|
You can easily chain the execution of several operations. Here we got some nice function composition.
|
52
114
|
The method must be a unary function, i.e. it always takes one parameter - the context, which is passed from call to call.
|
53
115
|
|
116
|
+
The following aliases are defined
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
alias :>> :map
|
120
|
+
alias :>= :try
|
121
|
+
alias :** :pipe # the operator must be right associative
|
122
|
+
```
|
123
|
+
|
124
|
+
This allows the composition of procs or lambdas and thus allow a clear definiton of a pipeline.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
Success(params) >> validate >> build_request ** log >> send ** log >> build_response
|
128
|
+
```
|
129
|
+
|
130
|
+
#### Complex Example in a Builder Class
|
131
|
+
|
54
132
|
```ruby
|
55
133
|
class Foo
|
56
134
|
include Deterministic
|
57
|
-
alias :m :method
|
135
|
+
alias :m :method # method conveniently returns a Proc to a method
|
58
136
|
|
59
|
-
def call
|
60
|
-
|
137
|
+
def call(params)
|
138
|
+
Success(params) >> m(:validate) >> m(:send)
|
61
139
|
end
|
62
140
|
|
63
|
-
def
|
64
|
-
Success(1)
|
65
|
-
end
|
66
|
-
|
67
|
-
def validate(ctx)
|
141
|
+
def validate(params)
|
68
142
|
# do stuff
|
69
|
-
Success(
|
143
|
+
Success(validate_and_cleansed_params)
|
70
144
|
end
|
71
145
|
|
72
|
-
def send(
|
146
|
+
def send(clean_params)
|
73
147
|
# do stuff
|
74
|
-
Success(
|
148
|
+
Success(result)
|
75
149
|
end
|
76
150
|
end
|
77
151
|
|
78
152
|
Foo.new.call # Success(3)
|
79
153
|
```
|
80
154
|
|
81
|
-
Chaining works with blocks (`#
|
155
|
+
Chaining works with blocks (`#map` is an alias for `#>>`)
|
82
156
|
|
83
157
|
```ruby
|
84
|
-
Success(1).
|
158
|
+
Success(1).map {|ctx| Success(ctx + 1)}
|
85
159
|
```
|
86
160
|
|
87
161
|
it also works with lambdas
|
@@ -107,75 +181,21 @@ end
|
|
107
181
|
Success(0) >> method(:works) >> method(:breaks) >> method(:never_executed) # Failure(2)
|
108
182
|
```
|
109
183
|
|
110
|
-
`#
|
184
|
+
`#map` aka `#>>` will not catch any exceptions raised. If you want automatic exception handling, the `#try` aka `#>=` will catch an error and wrap it with a failure
|
111
185
|
|
112
186
|
```ruby
|
113
187
|
def error(ctx)
|
114
|
-
raise "error #{
|
188
|
+
raise "error #{ctx}"
|
115
189
|
end
|
116
190
|
|
117
191
|
Success(1) >= method(:error) # Failure(RuntimeError(error 1))
|
118
192
|
```
|
119
193
|
|
120
|
-
### Either.attempt_all
|
121
|
-
The basic idea is to execute a chain of units of work and make sure all return either `Success` or `Failure`.
|
122
|
-
This remains for compatibility reasons, personally I would use the `>>` chaining.
|
123
|
-
|
124
|
-
|
125
|
-
```ruby
|
126
|
-
Either.attempt_all do
|
127
|
-
try { 1 }
|
128
|
-
try { |prev| prev + 1 }
|
129
|
-
end # => Success(2)
|
130
|
-
```
|
131
|
-
Take notice, that the result of of unit of work will be passed to the next one. So the result of prepare_somehing will be something in the second try.
|
132
|
-
|
133
|
-
If any of the units of work in between fail, the rest will not be executed and the last `Failure` will be returned.
|
134
|
-
|
135
|
-
```ruby
|
136
|
-
Either.attempt_all do
|
137
|
-
try { 1 }
|
138
|
-
try { raise "error" }
|
139
|
-
try { 2 }
|
140
|
-
end # => Failure(RuntimeError("error"))
|
141
|
-
```
|
142
|
-
|
143
|
-
However, the real fun starts if you use it with your own context. You can use this as a state container (meh!) or to pass a dependency locator:
|
144
|
-
|
145
|
-
```ruby
|
146
|
-
class Context
|
147
|
-
attr_accessor :env, :settings
|
148
|
-
def some_service
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# exemplary unit of work
|
153
|
-
module LoadSettings
|
154
|
-
def self.call(env)
|
155
|
-
settings = load(env)
|
156
|
-
settings.nil? ? Failure('could not load settings') : Success(settings)
|
157
|
-
end
|
158
|
-
|
159
|
-
def load(env)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
Either.attempt_all(context) do
|
164
|
-
# this unit of work explicitly returns success or failure
|
165
|
-
# no exceptions are catched and if they occur, well, they behave as expected
|
166
|
-
# methods from the context can be accessed, the use of self for setters is necessary
|
167
|
-
let { self.settings = LoadSettings.call(env) }
|
168
|
-
|
169
|
-
# with #try all exceptions will be transformed into a Failure
|
170
|
-
try { do_something }
|
171
|
-
end
|
172
|
-
```
|
173
|
-
|
174
194
|
### Pattern matching
|
175
195
|
Now that you have some result, you want to control flow by providing patterns.
|
176
196
|
`#match` can match by
|
177
197
|
|
178
|
-
* success, failure,
|
198
|
+
* success, failure, result or any
|
179
199
|
* values
|
180
200
|
* lambdas
|
181
201
|
* classes
|
@@ -184,7 +204,7 @@ Now that you have some result, you want to control flow by providing patterns.
|
|
184
204
|
Success(1).match do
|
185
205
|
success { |v| "success #{v}"}
|
186
206
|
failure { |v| "failure #{v}"}
|
187
|
-
|
207
|
+
result { |v| "result #{v}"}
|
188
208
|
end # => "success 1"
|
189
209
|
```
|
190
210
|
Note1: the inner value has been unwrapped!
|
@@ -217,18 +237,6 @@ Success([1, 2, 3]).match do
|
|
217
237
|
end # => 1
|
218
238
|
```
|
219
239
|
|
220
|
-
Combining `#attempt_all` and `#match` is the ultimate sophistication:
|
221
|
-
|
222
|
-
```ruby
|
223
|
-
Either.attempt_all do
|
224
|
-
try { 1 }
|
225
|
-
try { |v| v + 1 }
|
226
|
-
end.match do
|
227
|
-
success(1) { |v| "We made it to step #{v}" }
|
228
|
-
success(2) { |v| "The correct answer is #{v}"}
|
229
|
-
end # => "The correct answer is 2"
|
230
|
-
```
|
231
|
-
|
232
240
|
If no match was found a `NoMatchError` is raised, so make sure you always cover all possible outcomes.
|
233
241
|
|
234
242
|
```ruby
|
@@ -246,59 +254,19 @@ end # => "catch-all"
|
|
246
254
|
```
|
247
255
|
|
248
256
|
## core_ext
|
249
|
-
You can use a core extension, to include
|
257
|
+
You can use a core extension, to include Result in your own class or in Object, i.e. in all classes.
|
250
258
|
|
251
|
-
|
259
|
+
## Result
|
252
260
|
|
253
261
|
```ruby
|
254
|
-
require
|
255
|
-
class UnderTest
|
256
|
-
include Deterministic::CoreExt::Either
|
257
|
-
def test
|
258
|
-
attempt_all do
|
259
|
-
try { foo }
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def foo
|
264
|
-
1
|
265
|
-
end
|
266
|
-
end
|
262
|
+
require 'deterministic/core_ext/object/result'
|
267
263
|
|
268
|
-
|
269
|
-
|
264
|
+
[1].success? # => false
|
265
|
+
Success(1).failure? # => false
|
266
|
+
Success(1).success? # => true
|
267
|
+
Failure(1).result? # => true
|
270
268
|
```
|
271
269
|
|
272
|
-
To add it to all classes
|
273
|
-
|
274
|
-
```ruby
|
275
|
-
require "deterministic/core_ext/object/either" # this includes Deterministic to Object
|
276
|
-
|
277
|
-
class UnderTest
|
278
|
-
def test
|
279
|
-
attempt_all do
|
280
|
-
try { foo }
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def foo
|
285
|
-
1
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
ut = UnderTest.new
|
290
|
-
ut.test # => Success(1)
|
291
|
-
```
|
292
|
-
|
293
|
-
or use it on built-in classes
|
294
|
-
|
295
|
-
```ruby
|
296
|
-
require "deterministic/core_ext/object/either"
|
297
|
-
h = {a:1}
|
298
|
-
h.attempt_all do
|
299
|
-
try { |s| s[:a] + 1}
|
300
|
-
end # => Success(2)
|
301
|
-
```
|
302
270
|
|
303
271
|
## Maybe
|
304
272
|
The simplest NullObject wrapper there can be. It adds `#some?` and `#none?` to `Object` though.
|
@@ -307,7 +275,7 @@ The simplest NullObject wrapper there can be. It adds `#some?` and `#none?` to `
|
|
307
275
|
require 'deterministic/maybe' # you need to do this explicitly
|
308
276
|
Maybe(nil).foo # => None
|
309
277
|
Maybe(nil).foo.bar # => None
|
310
|
-
|
278
|
+
Maybe({a: 1})[:a] # => 1
|
311
279
|
|
312
280
|
Maybe(nil).none? # => true
|
313
281
|
Maybe({}).none? # => false
|
@@ -332,7 +300,7 @@ null.foo # => NoMethodError
|
|
332
300
|
|
333
301
|
## Inspirations
|
334
302
|
* My [Monadic gem](http://github.com/pzol/monadic) of course
|
335
|
-
* `#attempt_all` was somewhat inspired by [An error monad in Clojure](http://brehaut.net/blog/2011/error_monads)
|
303
|
+
* `#attempt_all` was somewhat inspired by [An error monad in Clojure](http://brehaut.net/blog/2011/error_monads) (attempt all has now been removed)
|
336
304
|
* [Pithyless' rumblings](https://gist.github.com/pithyless/2216519)
|
337
305
|
* [either by rsslldnphy](https://github.com/rsslldnphy/either)
|
338
306
|
* [Functors, Applicatives, And Monads In Pictures](http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html)
|
@@ -2,7 +2,7 @@ include Deterministic
|
|
2
2
|
|
3
3
|
module Deterministic
|
4
4
|
module CoreExt
|
5
|
-
module
|
5
|
+
module Result
|
6
6
|
def success?
|
7
7
|
self.is_a? Success
|
8
8
|
end
|
@@ -11,13 +11,9 @@ module Deterministic
|
|
11
11
|
self.is_a? Failure
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def result?
|
15
15
|
success? || failure?
|
16
16
|
end
|
17
|
-
|
18
|
-
def attempt_all(context=self, &block)
|
19
|
-
Deterministic::Either::AttemptAll.new(context, &block).call(self)
|
20
|
-
end
|
21
17
|
end
|
22
18
|
end
|
23
19
|
end
|
data/lib/deterministic/monad.rb
CHANGED
@@ -16,17 +16,17 @@ module Deterministic
|
|
16
16
|
# The functor: takes a function (a -> b) and applies it to the inner value of the monad (Ma),
|
17
17
|
# boxes it back to the same monad (Mb)
|
18
18
|
# fmap :: (a -> b) -> M a -> M b
|
19
|
-
def
|
19
|
+
def fmap(proc=nil, &block)
|
20
20
|
result = (proc || block).call(value)
|
21
21
|
self.class.new(result)
|
22
22
|
end
|
23
23
|
|
24
24
|
# The monad: takes a function which returns a monad (of the same type), applies the function
|
25
|
-
# bind ::
|
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
28
|
(proc || block).call(value, self.class).tap do |result|
|
29
|
-
raise NotMonadError, "Expected #{result.inspect} to be an
|
29
|
+
raise NotMonadError, "Expected #{result.inspect} to be an Result" unless result.is_a? self.class
|
30
30
|
end
|
31
31
|
end
|
32
32
|
alias :'>>=' :bind
|
@@ -0,0 +1,27 @@
|
|
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
|
+
|
@@ -4,7 +4,7 @@ module Deterministic::PatternMatching
|
|
4
4
|
context ||= block.binding.eval('self')
|
5
5
|
match = Match.new(self, context)
|
6
6
|
match.instance_eval &block
|
7
|
-
match.
|
7
|
+
match.call
|
8
8
|
end
|
9
9
|
|
10
10
|
class NoMatchError < StandardError; end
|
@@ -16,14 +16,14 @@ module Deterministic::PatternMatching
|
|
16
16
|
@collection = []
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def call
|
20
20
|
matcher = @collection.detect { |m| m.matches?(@container.value) }
|
21
|
-
raise NoMatchError, "No could be made for #{@container}" if matcher.nil?
|
21
|
+
raise NoMatchError, "No match could be made for #{@container.inspect}" if matcher.nil?
|
22
22
|
@context.instance_exec(@container.value, &matcher.block)
|
23
23
|
end
|
24
24
|
|
25
|
-
# TODO:
|
26
|
-
%w[Success Failure
|
25
|
+
# TODO: Result specific DSL, will need to move it to Result, later on
|
26
|
+
%w[Success Failure Result].each do |s|
|
27
27
|
define_method s.downcase.to_sym do |value=nil, &block|
|
28
28
|
klas = Module.const_get(s)
|
29
29
|
push(klas, value, block)
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module Deterministic
|
2
2
|
# Abstract parent of Success and Failure
|
3
|
-
class
|
3
|
+
class Result
|
4
4
|
include Monad
|
5
5
|
include Deterministic::PatternMatching
|
6
6
|
include Chain
|
7
7
|
|
8
8
|
def bind(proc=nil, &block)
|
9
|
-
(proc || block).call(value
|
10
|
-
raise NotMonadError, "Expected #{result.inspect} to be an
|
9
|
+
(proc || block).call(value).tap do |result|
|
10
|
+
raise NotMonadError, "Expected #{result.inspect} to be an Result" unless result.is_a? self.class.superclass
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -19,9 +19,33 @@ module Deterministic
|
|
19
19
|
is_a? Failure
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def pipe(proc=nil, &block)
|
23
|
+
(proc || block).call(self)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :** :pipe
|
28
|
+
|
29
|
+
def and(other)
|
30
|
+
return self if failure?
|
31
|
+
raise NotMonadError, "Expected #{other.inspect} to be an Result" unless other.is_a? Result
|
32
|
+
other
|
33
|
+
end
|
34
|
+
|
35
|
+
def and_then(&block)
|
23
36
|
return self if failure?
|
24
|
-
|
37
|
+
bind(&block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def or(other)
|
41
|
+
return self if success?
|
42
|
+
raise NotMonadError, "Expected #{other.inspect} to be an Result" unless other.is_a? Result
|
43
|
+
return other
|
44
|
+
end
|
45
|
+
|
46
|
+
def or_else(&block)
|
47
|
+
return self if success?
|
48
|
+
bind(&block)
|
25
49
|
end
|
26
50
|
|
27
51
|
# This is an abstract class, can't ever instantiate it directly
|
@@ -40,7 +64,6 @@ module Deterministic
|
|
40
64
|
end
|
41
65
|
|
42
66
|
module_function
|
43
|
-
|
44
67
|
def Success(value)
|
45
68
|
Success.new(value)
|
46
69
|
end
|
data/lib/deterministic.rb
CHANGED
@@ -5,10 +5,9 @@ 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/
|
9
|
-
require 'deterministic/
|
10
|
-
require 'deterministic/
|
11
|
-
require 'deterministic/
|
12
|
-
require 'deterministic/
|
13
|
-
require 'deterministic/either/failure'
|
8
|
+
require 'deterministic/result/match'
|
9
|
+
require 'deterministic/result/chain'
|
10
|
+
require 'deterministic/result'
|
11
|
+
require 'deterministic/result/success'
|
12
|
+
require 'deterministic/result/failure'
|
14
13
|
require 'deterministic/none'
|
@@ -33,10 +33,8 @@ module Examples
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def call(dirty_params)
|
36
|
-
|
37
|
-
|
38
|
-
let { |params| read_bookings(params) }
|
39
|
-
end.match(world) do
|
36
|
+
|
37
|
+
( parse_params(dirty_params) >> method(:read_bookings) ).match(world) do
|
40
38
|
success(Array) { |bookings| booking_list(bookings) }
|
41
39
|
success { |booking| booking(booking) }
|
42
40
|
failure(:no_bookings) { empty_booking_list }
|
@@ -49,7 +47,7 @@ module Examples
|
|
49
47
|
attr_reader :bookings_repo, :world
|
50
48
|
|
51
49
|
def parse_params(dirty_params)
|
52
|
-
dirty_params
|
50
|
+
Success(dirty_params)
|
53
51
|
end
|
54
52
|
|
55
53
|
def read_bookings(params)
|
@@ -1,28 +1,11 @@
|
|
1
1
|
require "spec_helper"
|
2
|
-
require "deterministic/core_ext/object/
|
2
|
+
require "deterministic/core_ext/object/result"
|
3
3
|
|
4
|
-
describe Deterministic::CoreExt::
|
4
|
+
describe Deterministic::CoreExt::Result, "object", isolate: true do
|
5
5
|
it "does something" do
|
6
6
|
h = {a: 1}
|
7
7
|
expect(h.success?).to be_falsey
|
8
8
|
expect(h.failure?).to be_falsey
|
9
|
-
expect(h.
|
10
|
-
end
|
11
|
-
|
12
|
-
it "use attempt_all in an instance" do
|
13
|
-
class UnderTest
|
14
|
-
def test
|
15
|
-
attempt_all do
|
16
|
-
try { foo }
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def foo
|
21
|
-
1
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
ut = UnderTest.new
|
26
|
-
expect(ut.test).to eq Success(1)
|
9
|
+
expect(h.result?).to be_falsey
|
27
10
|
end
|
28
11
|
end
|