deterministic 0.8.1 → 0.10.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/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
|