consequence 0.1.1 → 0.2.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 +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
|