consequence 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +154 -62
- data/lib/consequence.rb +2 -1
- data/lib/consequence/delegates_to_value.rb +16 -0
- data/lib/consequence/utils.rb +2 -0
- metadata +3 -4
- data/lib/consequence/core_ext/m_alias.rb +0 -3
- data/lib/consequence/core_ext/private_symbol_proc.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2213b692e96e63757d2f58234f110e448be7a1ef
|
4
|
+
data.tar.gz: 38ddbf1267f4e1d68d5d3e87e1967a7aeef4ff9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da489d84fb8b756fa681a34f9fcade73d7af4b1aace229b95ad627d1682484ebdfd52d95948df7ccec6356273c294c03712718cd019320200f19fa477bb16f63
|
7
|
+
data.tar.gz: 3a3d53e75a35fb4baf47acf9723713edb91f9618a4d6664e0408b2e9065ee191e1c76fd5c01f12c13aa4018e09f0b65b9636d35ba64df5f118ef7df607b17867
|
data/README.md
CHANGED
@@ -6,121 +6,213 @@ Simple monad implementation with clear and consistent syntax.
|
|
6
6
|
|
7
7
|
## Usage
|
8
8
|
|
9
|
+
A monad has a value that it is wrapped around. Its value can be anything: String, Module, Proc etc...
|
10
|
+
It takes its value as its only argument and can be initialized using the element reference syntax:
|
11
|
+
|
9
12
|
``` ruby
|
10
13
|
require 'consequence'
|
14
|
+
include Consequence
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
end
|
16
|
+
my_monad = Monad[4]
|
17
|
+
```
|
15
18
|
|
16
|
-
|
17
|
-
->(v) { puts v }
|
18
|
-
end
|
19
|
+
Its value can be retrieved with the `#value` getter method.
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
``` ruby
|
22
|
+
my_monad.value # 4
|
23
|
+
```
|
23
24
|
|
24
|
-
|
25
|
-
Bar = Class.new(Consequence::Monad)
|
25
|
+
To create new monad types, simply inherit from `Monad`.
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
``` ruby
|
28
|
+
Foo = Class.new(Monad)
|
29
|
+
Bar = Class.new(Monad)
|
29
30
|
```
|
30
31
|
|
31
|
-
|
32
|
+
A monad is equal to another monad only if both it's type and value are equal.
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
``` ruby
|
35
|
+
Foo[0] == Foo[1] # false
|
36
|
+
Foo[0] == Bar[0] # false
|
37
|
+
Foo[0] == Foo[0] # true
|
38
|
+
```
|
36
39
|
|
37
|
-
|
38
|
-
<dd>The supplied proc is applied with the result being ignored and the unchanged monad is passed down the chain.</dd>
|
39
|
-
</dl>
|
40
|
+
### Operations
|
40
41
|
|
41
|
-
|
42
|
+
Monads have two main operations `>>` and `<<`.
|
42
43
|
|
43
|
-
|
44
|
+
Both take a proc for their only argument. The supplied proc can take 0-2 arguments and the monad will match them, even if it's a lamda or method object with arity checking.
|
44
45
|
|
45
|
-
|
46
|
+
It's first argument will be passed the monads value:
|
46
47
|
|
47
|
-
|
48
|
+
``` ruby
|
49
|
+
Foo[4] << ->(v) { puts v }
|
50
|
+
# $ 4
|
51
|
+
```
|
48
52
|
|
49
|
-
|
53
|
+
It's second argument will be passed the monad itself. This is useful for making decisions based on the monads type.
|
50
54
|
|
51
55
|
``` ruby
|
52
|
-
|
56
|
+
Foo[0] << ->(v, m) { puts v if m.is_a?(Foo) }
|
57
|
+
# $ 0
|
58
|
+
```
|
59
|
+
|
60
|
+
### `>>`
|
61
|
+
|
62
|
+
The `>>` operation may be variously thought of as the 'map', 'bind' or 'join' operations, but has been left unnamed to avoid any specific connotations.
|
63
|
+
|
64
|
+
It takes the result of the proc given to it and passes it on:
|
65
|
+
|
66
|
+
``` ruby
|
67
|
+
Foo[0] >> ->(v) { Bar[v] } # Bar[0]
|
68
|
+
```
|
69
|
+
|
70
|
+
If it return's a result that is not a monad, it is wrapped up in one so the chain can continue:
|
71
|
+
|
72
|
+
``` ruby
|
73
|
+
Foo[0] >> ->(v) { v + 1 } # Foo[1]
|
74
|
+
```
|
75
|
+
|
76
|
+
### `<<`
|
77
|
+
|
78
|
+
The `<<` operation may be thought of as the 'pipe' operation.
|
79
|
+
|
80
|
+
It calls the proc given to it, but ignores it's return value:
|
81
|
+
|
82
|
+
``` ruby
|
83
|
+
Foo[0] << ->(v) { v + 1 } # Foo[0]
|
84
|
+
```
|
85
|
+
|
86
|
+
This is useful for creating 'side effects', such as logging or assigning instance variables:
|
87
|
+
|
88
|
+
``` ruby
|
89
|
+
Foo[0] << ->(v) { @side_effect = v + 1 } # Foo[0]
|
90
|
+
puts @side_effect
|
91
|
+
# $ 1
|
92
|
+
```
|
53
93
|
|
54
|
-
|
55
|
-
class << self
|
56
|
-
include Consequence
|
57
|
-
alias_method :m, :method
|
94
|
+
### `#to_proc`
|
58
95
|
|
59
|
-
|
60
|
-
Success[attributes] >> m(:build) >> m(:validate) >> m(:persist)
|
61
|
-
end
|
96
|
+
Before either operation calls a proc, the `#to_proc` method is called on it. This can be useful if the operation is given an object that is no a proc, but has a `#to_proc` method that returns one.
|
62
97
|
|
63
|
-
|
98
|
+
A good example of this is the Symbol object, whose `#to_proc` method supplies a proc that sends the symbol as a message to its first argument. In this case this means calling that method on the value:
|
64
99
|
|
65
|
-
|
66
|
-
|
67
|
-
|
100
|
+
``` ruby
|
101
|
+
Foo[[1, 4, 7]] >> :pop # Foo[7]
|
102
|
+
```
|
68
103
|
|
69
|
-
|
70
|
-
validator = UserValidator.new(user)
|
71
|
-
validator.valid? ? Success[user] : Failure[validator.errors]
|
72
|
-
end
|
104
|
+
This also can be used to make a composition syntax, by writing a `#to_proc` method for a monad:
|
73
105
|
|
74
|
-
|
75
|
-
|
76
|
-
|
106
|
+
``` ruby
|
107
|
+
class Add < Monad
|
108
|
+
def to_proc
|
109
|
+
->(v) { v + value }
|
77
110
|
end
|
78
111
|
end
|
112
|
+
|
113
|
+
Add[1] >> Add[4] >> Add[6] # Add[11]
|
79
114
|
```
|
80
115
|
|
81
|
-
|
116
|
+
## Built-In Types
|
82
117
|
|
83
|
-
|
118
|
+
### NullMonad
|
84
119
|
|
85
|
-
|
120
|
+
The `NullMonad` is a monad whose `>>` and `<<` operations have been overriden to ignore all input:
|
121
|
+
|
122
|
+
``` ruby
|
123
|
+
NullMonad[0] >> ->(v) { v + 1 } # Consequence::NullMonad[0]
|
124
|
+
```
|
86
125
|
|
87
|
-
|
126
|
+
This is different from the `<<` operation, because the proc isn't even run, so can cause no side effects:
|
88
127
|
|
89
|
-
|
128
|
+
``` ruby
|
129
|
+
NullMonad[0] << ->(v) { @side_effect = v + 1 }
|
130
|
+
puts @side_effect.inspect
|
131
|
+
# $ nil
|
132
|
+
```
|
133
|
+
|
134
|
+
For both operations, the `NullMonad` returns itself, so a chain of operations can be called on it without causing an error. This can be useful for stoping a chain midway through safely:
|
135
|
+
|
136
|
+
``` ruby
|
137
|
+
Continue = Class.new(Monad)
|
138
|
+
Stop = Class.new(NullMonad)
|
139
|
+
|
140
|
+
drop_first = ->(v) { v[1..-1] }
|
141
|
+
check_empty = ->(v, m) { v.empty? ? Stop[v] : m }
|
142
|
+
|
143
|
+
Continue[[1, 3, 4]] >> drop_first >> check_empty >> # Continue[[3, 4]]
|
144
|
+
drop_first >> check_empty >> # Continue[[4]]
|
145
|
+
drop_first >> check_empty >> # Stop[[]]
|
146
|
+
drop_first >> check_empty # Stop[[]]
|
147
|
+
```
|
90
148
|
|
91
|
-
###
|
149
|
+
### Success & Failure
|
92
150
|
|
93
|
-
|
151
|
+
A `Success` monad wraps up all exceptions in a `Failure` monad:
|
94
152
|
|
95
153
|
``` ruby
|
96
|
-
|
154
|
+
Success[0] >> ->(v) { 5 / v }
|
155
|
+
# Consequence::Failure[#<ZeroDivisionError: divided by 0>]
|
156
|
+
```
|
157
|
+
|
158
|
+
A `Failure` monad is a subclass of the `NullMonad` so all successive chained procs are ignored.
|
97
159
|
|
98
|
-
|
160
|
+
Both `Success` and `Failure` respond to the `#succeeded?` and `#failed?` query methods in the way you'd expect:
|
161
|
+
|
162
|
+
``` ruby
|
163
|
+
Success[0].succeeded? # true
|
164
|
+
Success[0].failed? # false
|
165
|
+
Failure[0].succeeded? # false
|
166
|
+
Failure[0].failed? # true
|
99
167
|
```
|
100
168
|
|
101
|
-
|
169
|
+
For an example, check out the [Success & Failure example](https://github.com/mushishi78/consequence/wiki/Success-&-Failure-Example) on the wiki.
|
170
|
+
|
171
|
+
### Something & Nothing
|
172
|
+
|
173
|
+
A `Something` monad wraps up a nil result in a `Nothing` monad:
|
102
174
|
|
103
175
|
``` ruby
|
104
|
-
|
176
|
+
Something[[1, 3, 5]] >> ->(v) { v[4] } # Consequence::Nothing[nil]
|
105
177
|
```
|
106
178
|
|
107
|
-
|
179
|
+
A `Nothing` monad is also a subclass of the `NullMonad` so all successive chained procs are ignored. This prevents `MissingMethod` errors from trying to call a method on a `nil`.
|
108
180
|
|
109
|
-
|
181
|
+
A `Nothing` responds positively to the `#nil?` method:
|
110
182
|
|
111
183
|
``` ruby
|
112
|
-
|
184
|
+
Nothing[nil].nil? # true
|
113
185
|
```
|
114
186
|
|
115
|
-
|
187
|
+
## DelegatesToValue
|
116
188
|
|
117
|
-
`
|
189
|
+
`DelegatesToValue` is a module that can be included into Monad that adds a `#method_missing` method to delegate calls to its value:
|
118
190
|
|
119
191
|
``` ruby
|
120
|
-
|
192
|
+
Foo.include DelegatesToValue
|
193
|
+
Foo[[1, 4, 6]].map {|n| n + 1} # Foo[[2, 5, 7]]
|
121
194
|
```
|
122
195
|
|
123
|
-
|
196
|
+
It delegates via the `>>` operation, so subclasses of the NullMonad will respond to delegated method calls, but still take no action:
|
197
|
+
|
198
|
+
``` ruby
|
199
|
+
Something.include DelegatesToValue
|
200
|
+
Nothing.include DelegatesToValue
|
201
|
+
|
202
|
+
dangrous_hash = {user: {orders: {1 => {price: 3.99} } } }
|
203
|
+
|
204
|
+
Something[dangrous_hash][:user][:orders][1][:price] # Consequence::Something[3.99]
|
205
|
+
Something[dangrous_hash][:user][:orders][2][:price] # Consequence::Nothing[nil]
|
206
|
+
```
|
207
|
+
|
208
|
+
## Wiki
|
209
|
+
|
210
|
+
To find some examples and information about utils, [check out the wiki](https://github.com/mushishi78/consequence/wiki/Consequence) and feel free to contribute to it.
|
211
|
+
|
212
|
+
## Inspirations
|
213
|
+
|
214
|
+
* [Deterministic](https://github.com/pzol/deterministic)
|
215
|
+
* [Kleisli](https://github.com/txus/kleisli)
|
124
216
|
|
125
217
|
## Installation
|
126
218
|
|
data/lib/consequence.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'consequence/monad'
|
2
|
+
|
3
|
+
module Consequence
|
4
|
+
module DelegatesToValue
|
5
|
+
def method_missing(method_name, *args, &b)
|
6
|
+
return super unless value.respond_to?(method_name)
|
7
|
+
self >> -> { value.send(method_name, *args, &b) }
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def respond_to_missing?(method_name, include_private = false)
|
13
|
+
value.respond_to?(method_name, include_private) || super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/consequence/utils.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: consequence
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Max White
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -39,9 +39,8 @@ files:
|
|
39
39
|
- LICENSE.txt
|
40
40
|
- README.md
|
41
41
|
- lib/consequence.rb
|
42
|
-
- lib/consequence/core_ext/m_alias.rb
|
43
|
-
- lib/consequence/core_ext/private_symbol_proc.rb
|
44
42
|
- lib/consequence/core_ext/utils.rb
|
43
|
+
- lib/consequence/delegates_to_value.rb
|
45
44
|
- lib/consequence/monad.rb
|
46
45
|
- lib/consequence/something.rb
|
47
46
|
- lib/consequence/success.rb
|