monads 0.0.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 +7 -0
- data/.gitignore +1 -0
- data/.rspec +3 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/lib/monads.rb +4 -0
- data/lib/monads/eventually.rb +31 -0
- data/lib/monads/many.rb +17 -0
- data/lib/monads/monad.rb +29 -0
- data/lib/monads/optional.rb +21 -0
- data/lib/monads/version.rb +3 -0
- data/monads.gemspec +19 -0
- data/spec/monads/eventually_spec.rb +112 -0
- data/spec/monads/many_spec.rb +95 -0
- data/spec/monads/optional_spec.rb +97 -0
- data/spec/spec_helper.rb +3 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 992bed3108c9bd2b84cef78ba21bd99f56f52b88
|
4
|
+
data.tar.gz: 58c6f72fcf8c4c29caf1311462bc448c84f510aa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 784a2a5bf9c923501d4f20c41c955ebd6d0b0252574c02714e1c0811d197a5fc0d51d87bef0e731982a56c26277eb7f180230714e8779164162b93b2922b477c
|
7
|
+
data.tar.gz: 83c250b59b392cb1f13613cac33dd4a80a5307a65be72f0251ed021c3b7205f8b6f81f3cb9d816ec300dff60c68df0b60af91e506a77f388df22b13cdcefa289
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Tom Stuart
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/monads.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'monads/monad'
|
2
|
+
|
3
|
+
module Monads
|
4
|
+
Eventually = Struct.new(:block) do
|
5
|
+
include Monad
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
super(block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(&success)
|
12
|
+
block.call(success)
|
13
|
+
end
|
14
|
+
|
15
|
+
def and_then(&block)
|
16
|
+
block = ensure_monadic_result(&block)
|
17
|
+
|
18
|
+
Eventually.new do |success|
|
19
|
+
run do |value|
|
20
|
+
block.call(value).run(&success)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_value(value)
|
26
|
+
Eventually.new do |success|
|
27
|
+
success.call(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/monads/many.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'monads/monad'
|
2
|
+
|
3
|
+
module Monads
|
4
|
+
Many = Struct.new(:values) do
|
5
|
+
include Monad
|
6
|
+
|
7
|
+
def and_then(&block)
|
8
|
+
block = ensure_monadic_result(&block)
|
9
|
+
|
10
|
+
Many.new(values.map(&block).flat_map(&:values))
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_value(value)
|
14
|
+
Many.new([value])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/monads/monad.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Monads
|
2
|
+
module Monad
|
3
|
+
def within(&block)
|
4
|
+
and_then do |value|
|
5
|
+
self.class.from_value(block.call(value))
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(*args, &block)
|
10
|
+
within do |value|
|
11
|
+
value.public_send(*args, &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def ensure_monadic_result(&block)
|
18
|
+
acceptable_result_type = self.class
|
19
|
+
|
20
|
+
-> *a, &b do
|
21
|
+
block.call(*a, &b).tap do |result|
|
22
|
+
unless result.is_a?(acceptable_result_type)
|
23
|
+
raise TypeError, "block must return #{acceptable_result_type.name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'monads/monad'
|
2
|
+
|
3
|
+
module Monads
|
4
|
+
Optional = Struct.new(:value) do
|
5
|
+
include Monad
|
6
|
+
|
7
|
+
def and_then(&block)
|
8
|
+
block = ensure_monadic_result(&block)
|
9
|
+
|
10
|
+
if value.nil?
|
11
|
+
self
|
12
|
+
else
|
13
|
+
block.call(value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_value(value)
|
18
|
+
Optional.new(value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/monads.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'monads/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'monads'
|
7
|
+
spec.version = Monads::VERSION
|
8
|
+
spec.author = 'Tom Stuart'
|
9
|
+
spec.email = 'tom@codon.com'
|
10
|
+
spec.summary = 'Simple Ruby implementations of some common monads.'
|
11
|
+
spec.homepage = 'https://github.com/tomstuart/monads'
|
12
|
+
spec.license = 'MIT'
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ['lib']
|
17
|
+
|
18
|
+
spec.add_development_dependency 'rspec', '~> 3.0', '>= 3.0.0'
|
19
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'monads/eventually'
|
2
|
+
|
3
|
+
module Monads
|
4
|
+
RSpec.describe 'the Eventually monad' do
|
5
|
+
describe '#run' do
|
6
|
+
it 'runs the block from an Eventually' do
|
7
|
+
expect { |block| Eventually.new(&block).run }.to yield_control
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'uses its block argument as a success callback' do
|
11
|
+
@result, result = nil, double
|
12
|
+
Eventually.new { |success| success.call(result) }.run { |value| @result = value }
|
13
|
+
expect(@result).to eq result
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#and_then' do
|
18
|
+
context 'when the Eventually succeeds synchronously' do
|
19
|
+
it 'arranges for the block to run when the Eventually succeeds' do
|
20
|
+
@result, intermediate_result, final_result = nil, double, double
|
21
|
+
Eventually.new { |success| success.call(intermediate_result) }.
|
22
|
+
and_then do |value|
|
23
|
+
if value == intermediate_result
|
24
|
+
Eventually.new { |success| success.call(final_result) }
|
25
|
+
end
|
26
|
+
end.
|
27
|
+
run { |value| @result = value }
|
28
|
+
expect(@result).to eq final_result
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'raises an error if the block doesn’t return another Eventually' do
|
32
|
+
expect { Eventually.new { |success| success.call }.and_then { double }.run }.to raise_error(TypeError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when the Eventually succeds asynchronously' do
|
37
|
+
it 'arranges for the block to run when the Eventually succeeds' do
|
38
|
+
@result, intermediate_result, final_result = nil, double, double
|
39
|
+
Eventually.new { |success| @job = -> { success.call(intermediate_result) } }.
|
40
|
+
and_then do |value|
|
41
|
+
if value == intermediate_result
|
42
|
+
Eventually.new { |success| success.call(final_result) }
|
43
|
+
end
|
44
|
+
end.
|
45
|
+
run { |value| @result = value }
|
46
|
+
@job.call
|
47
|
+
expect(@result).to eq final_result
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'raises an error if the block doesn’t return another Eventually' do
|
51
|
+
expect {
|
52
|
+
Eventually.new { |success| @job = -> { success.call } }.and_then { double }.run
|
53
|
+
@job.call
|
54
|
+
}.to raise_error(TypeError)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '.from_value' do
|
60
|
+
let(:value) { double }
|
61
|
+
|
62
|
+
it 'wraps a value in an Eventually' do
|
63
|
+
@result = nil
|
64
|
+
Eventually.from_value(value).run { |value| @result = value }
|
65
|
+
expect(@result).to eq value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#within' do
|
70
|
+
context 'when the Eventually succeeds synchronously' do
|
71
|
+
it 'arranges for the block to run when the Eventually succeeds' do
|
72
|
+
@result, intermediate_result, final_result = nil, double, double
|
73
|
+
Eventually.new { |success| success.call(intermediate_result) }.
|
74
|
+
within { |value| final_result if value == intermediate_result }.
|
75
|
+
run { |value| @result = value }
|
76
|
+
expect(@result).to eq final_result
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when the Eventually succeds asynchronously' do
|
81
|
+
it 'arranges for the block to run when the Eventually succeeds' do
|
82
|
+
@result, intermediate_result, final_result = nil, double, double
|
83
|
+
Eventually.new { |success| @job = -> { success.call(intermediate_result) } }.
|
84
|
+
within { |value| final_result if value == intermediate_result }.
|
85
|
+
run { |value| @result = value }
|
86
|
+
@job.call
|
87
|
+
expect(@result).to eq final_result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'handling unrecognised messages' do
|
93
|
+
let(:value) { double }
|
94
|
+
let(:response) { double }
|
95
|
+
|
96
|
+
before(:example) do
|
97
|
+
allow(value).to receive(:challenge).and_return(response)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'forwards any unrecognised message to the block’s value' do
|
101
|
+
expect(value).to receive(:challenge)
|
102
|
+
Eventually.new { |success| success.call(value) }.challenge.run {}
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'returns the message’s result wrapped in an Eventually' do
|
106
|
+
@result = nil
|
107
|
+
Eventually.new { |success| success.call(value) }.challenge.run { |result| @result = result }
|
108
|
+
expect(@result).to eq response
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'monads/many'
|
2
|
+
|
3
|
+
module Monads
|
4
|
+
RSpec.describe 'the Many monad' do
|
5
|
+
let(:many) { Many.new(values) }
|
6
|
+
|
7
|
+
describe '#values' do
|
8
|
+
let(:values) { double }
|
9
|
+
|
10
|
+
it 'retrieves the values from a Many' do
|
11
|
+
expect(many.values).to eq values
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#and_then' do
|
16
|
+
context 'when there aren’t any values' do
|
17
|
+
let(:values) { [] }
|
18
|
+
|
19
|
+
it 'doesn’t call the block' do
|
20
|
+
expect { |block| many.and_then(&block) }.not_to yield_control
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when there are values' do
|
25
|
+
let(:values) { [1, 2, 3] }
|
26
|
+
|
27
|
+
it 'calls the block with each value' do
|
28
|
+
@values = []
|
29
|
+
many.and_then { |value| @values << value; Many.new(double) }
|
30
|
+
expect(@values).to eq values
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns a flattened version of the block’s results' do
|
34
|
+
expect(many.and_then { |value| Many.new(value * 2) }.values).to eq [2, 4, 6]
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'raises an error if the block doesn’t return another Many' do
|
38
|
+
expect { many.and_then { double } }.to raise_error(TypeError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '.from_value' do
|
44
|
+
let(:value) { double }
|
45
|
+
|
46
|
+
it 'wraps a value in a Many' do
|
47
|
+
expect(Many.from_value(value).values).to eq [value]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#within' do
|
52
|
+
context 'when there aren’t any values' do
|
53
|
+
let(:values) { [] }
|
54
|
+
|
55
|
+
it 'doesn’t call the block' do
|
56
|
+
expect { |block| many.within(&block) }.not_to yield_control
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when there are values' do
|
61
|
+
let(:values) { [1, 2, 3] }
|
62
|
+
|
63
|
+
it 'calls the block with each value' do
|
64
|
+
expect { |block| many.within(&block) }.to yield_successive_args(*values)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns a flattened version of the block’s results wrapped in a Many' do
|
68
|
+
expect(many.within { |value| value * 2 }.values).to eq [2, 4, 6]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'handling unrecognised messages' do
|
74
|
+
let(:values) { [double, double, double] }
|
75
|
+
let(:responses) { [double, double, double] }
|
76
|
+
|
77
|
+
before(:example) do
|
78
|
+
values.zip(responses) do |value, response|
|
79
|
+
allow(value).to receive(:challenge).and_return(response)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'forwards any unrecognised message to each value' do
|
84
|
+
values.each do |value|
|
85
|
+
expect(value).to receive(:challenge)
|
86
|
+
end
|
87
|
+
many.challenge
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'returns the messages’ results wrapped in a Many' do
|
91
|
+
expect(many.challenge.values).to eq responses
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'monads/optional'
|
2
|
+
|
3
|
+
module Monads
|
4
|
+
RSpec.describe 'the Optional monad' do
|
5
|
+
let(:value) { double }
|
6
|
+
let(:optional) { Optional.new(value) }
|
7
|
+
|
8
|
+
describe '#value' do
|
9
|
+
it 'retrieves the value from an Optional' do
|
10
|
+
expect(optional.value).to eq value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#and_then' do
|
15
|
+
context 'when the value is nil' do
|
16
|
+
before(:example) do
|
17
|
+
allow(value).to receive(:nil?).and_return(true)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'doesn’t call the block' do
|
21
|
+
expect { |block| optional.and_then(&block) }.not_to yield_control
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when the value isn’t nil' do
|
26
|
+
before(:example) do
|
27
|
+
allow(value).to receive(:nil?).and_return(false)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'calls the block with the value' do
|
31
|
+
@value = nil
|
32
|
+
optional.and_then { |value| @value = value; Optional.new(double) }
|
33
|
+
expect(@value).to eq value
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns the block’s result' do
|
37
|
+
result = double
|
38
|
+
expect(optional.and_then { |value| Optional.new(result) }.value).to eq result
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raises an error if the block doesn’t return another Optional' do
|
42
|
+
expect { optional.and_then { double } }.to raise_error(TypeError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '.from_value' do
|
48
|
+
it 'wraps a value in an Optional' do
|
49
|
+
expect(Optional.from_value(value).value).to eq value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#within' do
|
54
|
+
context 'when the value is nil' do
|
55
|
+
before(:example) do
|
56
|
+
allow(value).to receive(:nil?).and_return(true)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'doesn’t call the block' do
|
60
|
+
expect { |block| optional.within(&block) }.not_to yield_control
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when the value isn’t nil' do
|
65
|
+
before(:example) do
|
66
|
+
allow(value).to receive(:nil?).and_return(false)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'calls the block with the value' do
|
70
|
+
expect { |block| optional.within(&block) }.to yield_with_args(value)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns the block’s result wrapped in an Optional' do
|
74
|
+
result = double
|
75
|
+
expect(optional.within { result }.value).to eq result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'handling unrecognised messages' do
|
81
|
+
let(:response) { double }
|
82
|
+
|
83
|
+
before(:example) do
|
84
|
+
allow(value).to receive(:challenge).and_return(response)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'forwards any unrecognised message to the value' do
|
88
|
+
expect(value).to receive(:challenge)
|
89
|
+
optional.challenge
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns the message’s result wrapped in an Optional' do
|
93
|
+
expect(optional.challenge.value).to eq response
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: monads
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Stuart
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.0.0
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.0
|
33
|
+
description:
|
34
|
+
email: tom@codon.com
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- ".gitignore"
|
40
|
+
- ".rspec"
|
41
|
+
- Gemfile
|
42
|
+
- LICENSE.txt
|
43
|
+
- lib/monads.rb
|
44
|
+
- lib/monads/eventually.rb
|
45
|
+
- lib/monads/many.rb
|
46
|
+
- lib/monads/monad.rb
|
47
|
+
- lib/monads/optional.rb
|
48
|
+
- lib/monads/version.rb
|
49
|
+
- monads.gemspec
|
50
|
+
- spec/monads/eventually_spec.rb
|
51
|
+
- spec/monads/many_spec.rb
|
52
|
+
- spec/monads/optional_spec.rb
|
53
|
+
- spec/spec_helper.rb
|
54
|
+
homepage: https://github.com/tomstuart/monads
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 2.2.2
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: Simple Ruby implementations of some common monads.
|
78
|
+
test_files:
|
79
|
+
- spec/monads/eventually_spec.rb
|
80
|
+
- spec/monads/many_spec.rb
|
81
|
+
- spec/monads/optional_spec.rb
|
82
|
+
- spec/spec_helper.rb
|