kleisli 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3542c8f4abc331bb9a9d47170b22392146489666
4
+ data.tar.gz: 4381c9631f3205faffc3a3764340d3ce2c623041
5
+ SHA512:
6
+ metadata.gz: 52389420ffa39afd4fcb73731e95301d9258374e26f85afd1e467330cc61764d5b7c9989d0ef7309948cbc2134237ee5a8970325028f2fea67281b56ac8eb584
7
+ data.tar.gz: bf55220e96e5649b079588c661d2e7ff430197c8964bfa75971d78b7937fd29a29ae3b6a6e98c1b235d82b5490bd9eb97e4ff0baa4f4f17dffd91e360be66364
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kleisli.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Josep M. Bach
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.
@@ -0,0 +1,275 @@
1
+ # Kleisli
2
+
3
+ An idiomatic, clean implementation of a few common useful monads in Ruby, written by [Ryan Levick][rylev] and me.
4
+
5
+ It aims to be idiomatic Ruby to use in real app, not a proof of concept.
6
+
7
+ In your Gemfile:
8
+
9
+ ```ruby
10
+ gem 'kleisli'
11
+ ```
12
+
13
+ We would like to thank Curry and Howard for their correspondence.
14
+
15
+ ## Notation
16
+
17
+ For all its monads, Kleisli implements `return` (we call it `lift` instead, as `return` is a reserved keyword in Ruby) with convenience global methods (see which for each monad below).
18
+
19
+ Kleisli uses a clever Ruby syntax trick to implement the `bind` operator, which looks like this: `>->`. We will probably burn in hell for this.
20
+
21
+ ## Maybe monad
22
+
23
+ The Maybe monad is useful to express a pipeline of computations that might
24
+ return nil at any point. `user.address.street` anyone?
25
+
26
+ ### `>->` (bind)
27
+
28
+ ```ruby
29
+ require "kleisli"
30
+
31
+ maybe_user = Maybe(user) >-> user {
32
+ Maybe(user.address) } >-> address {
33
+ Maybe(address.street) }
34
+
35
+ # If user exists
36
+ # => Some("Monad Street")
37
+ # If user is nil
38
+ # => None()
39
+
40
+ # You can also use Some and None as type constructors yourself.
41
+ x = Some(10)
42
+ y = None()
43
+ ```
44
+
45
+ ### `fmap`
46
+
47
+ ```ruby
48
+ require "kleisli"
49
+
50
+ # If we know that a user always has an address with a street
51
+ Maybe(user).fmap(&:address).fmap(&:street)
52
+
53
+ # If the user exists
54
+ # => Some("Monad Street")
55
+ # If the user is nil
56
+ # => None()
57
+ ```
58
+
59
+ ## Try
60
+
61
+ The Try monad is useful to express a pipeline of computations that might throw
62
+ an exception at any point.
63
+
64
+ ### `>->` (bind)
65
+
66
+ ```ruby
67
+ require "kleisli"
68
+
69
+ json_string = get_json_from_somewhere
70
+
71
+ result = Try { JSON.parse(json_string) } >-> json {
72
+ Try { json["dividend"].to_i / json["divisor"].to_i }
73
+ }
74
+
75
+ # If no exception was thrown:
76
+
77
+ result # => #<Try::Success @value=123>
78
+ result.value # => 123
79
+
80
+ # If there was a ZeroDivisionError exception for example:
81
+
82
+ result # => #<Try::Failure @exception=#<ZeroDivisionError ...>>
83
+ result.exception # => #<ZeroDivisionError ...>
84
+ ```
85
+
86
+ ### `fmap`
87
+
88
+ ```ruby
89
+ require "kleisli"
90
+
91
+ Try { JSON.parse(json_string) }.fmap(&:symbolize_keys).value
92
+
93
+ # If everything went well:
94
+ # => { :my => "json", :with => "symbolized keys" }
95
+ # If an exception was thrown:
96
+ # => nil
97
+ ```
98
+
99
+ ### `to_maybe`
100
+
101
+ Sometimes it's useful to interleave both `Try` and `Maybe`. To convert a `Try`
102
+ into a `Maybe` you can use `to_maybe`:
103
+
104
+ ```ruby
105
+ require "kleisli"
106
+
107
+ Try { JSON.parse(json_string) }.fmap(&:symbolize_keys).to_maybe
108
+
109
+ # If everything went well:
110
+ # => Some({ :my => "json", :with => "symbolized keys" })
111
+ # If an exception was thrown:
112
+ # => None()
113
+ ```
114
+
115
+ ## Either
116
+
117
+ The Either monad is useful to express a pipeline of computations that might return an error object with some information.
118
+
119
+ It has two type constructors: `Right` and `Left`. As a useful mnemonic, `Right` is for when everything went "right" and `Left` is used for errors.
120
+
121
+ Think of it as exceptions without messing with the call stack.
122
+
123
+ ### `>->` (bind)
124
+
125
+ ```ruby
126
+ require "kleisli"
127
+
128
+ result = Right(3) >-> value {
129
+ if value > 1
130
+ Right(value + 3)
131
+ else
132
+ Left("value was less or equal than 1")
133
+ end
134
+ } >-> value {
135
+ if value % 2 == 0
136
+ Right(value * 2)
137
+ else
138
+ Left("value was not even")
139
+ end
140
+ }
141
+
142
+ # If everything went well
143
+ result # => Right(12)
144
+ result.value # => 12
145
+
146
+ # If it failed in the first block
147
+ result # => Left("value was less or equal than 1")
148
+ result.value # => "value was less or equal than 1"
149
+
150
+ # If it failed in the second block
151
+ result # => Left("value was not even")
152
+ result.value # => "value was not even"
153
+ ```
154
+
155
+ ### `fmap`
156
+
157
+ ```ruby
158
+ require "kleisli"
159
+
160
+ result = if foo > bar
161
+ Right(10)
162
+ else
163
+ Left("wrong")
164
+ end.fmap { |x| x * 2 }
165
+
166
+ # If everything went well
167
+ result # => Right(20)
168
+ # If it didn't
169
+ result # => Left("wrong")
170
+ ```
171
+
172
+ ### `to_maybe`
173
+
174
+ Sometimes it's useful to turn an `Either` into a `Maybe`. You can use
175
+ `to_maybe` for that:
176
+
177
+ ```ruby
178
+ require "kleisli"
179
+
180
+ result = if foo > bar
181
+ Right(10)
182
+ else
183
+ Left("wrong")
184
+ end.to_maybe
185
+
186
+ # If everything went well:
187
+ result # => Some(10)
188
+ # If it didn't
189
+ result # => None()
190
+ ```
191
+
192
+ ## Writer
193
+
194
+ The Writer monad is arguably the least useful monad in Ruby (as side effects
195
+ are uncontrolled), but let's take a look at it anyway.
196
+
197
+ It is used to model computations that *append* to some kind of state (which
198
+ needs to be a Monoid, expressed in the `Kleisli::Monoid` mixin) at each step.
199
+
200
+ (We've already implemented the Monoid interface for `String`, `Array`, `Hash`,
201
+ `Fixnum` and `Float` for you.)
202
+
203
+ An example would be a pipeline of computations that append to a log, for
204
+ example a list of strings.
205
+
206
+ ### `>->` (bind)
207
+
208
+ ```ruby
209
+ require "kleisli"
210
+
211
+ writer = Writer([], 100) >-> value {
212
+ Writer(["added 100"], value + 100)
213
+ } >-> value {
214
+ Writer(["added 140 more"], value + 140)
215
+ } # => Writer(["added 100", "added 140 more"], 340)
216
+
217
+ log, value = writer.unwrap
218
+ log # => ["added 100", "added 140 more"]
219
+ value # => 340
220
+ ```
221
+
222
+ ### `fmap`
223
+
224
+ ```ruby
225
+ require "kleisli"
226
+
227
+ writer = Writer([], 100).fmap { |value|
228
+ value + 100
229
+ } # => Writer([], 200)
230
+ ```
231
+
232
+ ## Future
233
+
234
+ The Future monad models a pipeline of computations that will happen in the future, as soon as the value needed for each step is available. It is useful to model, for example, a sequential chain of HTTP calls.
235
+
236
+ ### `>->` (bind)
237
+
238
+ ```ruby
239
+ require "kleisli"
240
+
241
+ f = Future("myendpoint.com") >-> url {
242
+ Future { HTTP.get(url.call) }
243
+ } >-> response {
244
+ Future {
245
+ other_url = JSON.parse(response.call.body)[:other_url]
246
+ HTTP.get(other_url)
247
+ }
248
+ } >-> other_response {
249
+ Future { JSON.parse(other_response.call.body) }
250
+ }
251
+
252
+ # Do some other stuff...
253
+
254
+ f.await # => block until the whole pipeline is realized
255
+ # => { "my" => "response body" }
256
+ ```
257
+
258
+ ### `fmap`
259
+
260
+ ```ruby
261
+ require "kleisli"
262
+
263
+ Future { expensive_operation }.fmap { |x| x * 2 }.await
264
+ # => result of expensive_operation * 2
265
+ ```
266
+
267
+ ## Who's this
268
+
269
+ This was made by [Josep M. Bach (Txus)](http://blog.txus.io) and [Ryan
270
+ Levick][rylev] under the MIT license. We are [@txustice][twitter] and
271
+ [@itchyankles][itchyankles] on twitter (where you should probably follow us!).
272
+
273
+ [twitter]: https://twitter.com/txustice
274
+ [itchyankles]: https://twitter.com/itchyankles
275
+ [rylev]: https://github.com/rylev
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/**/*.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kleisli/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kleisli"
8
+ spec.version = Kleisli::VERSION
9
+ spec.authors = ["Josep M. Bach", "Ryan Levick"]
10
+ spec.email = ["josep.m.bach@gmail.com", "ryan.levick@gmail.com"]
11
+ spec.summary = %q{Usable, idiomatic common monads in Ruby}
12
+ spec.description = %q{Usable, idiomatic common monads in Ruby}
13
+ spec.homepage = "https://github.com/txus/kleisli"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
@@ -0,0 +1,9 @@
1
+ require "kleisli/version"
2
+ require "kleisli/maybe"
3
+ require "kleisli/try"
4
+ require "kleisli/writer"
5
+ require "kleisli/future"
6
+ require "kleisli/either"
7
+
8
+ module Kleisli
9
+ end
@@ -0,0 +1,60 @@
1
+ require 'kleisli/monad'
2
+ require 'kleisli/maybe'
3
+
4
+ module Kleisli
5
+ class Either < Monad
6
+ attr_reader :right, :left
7
+
8
+ def ==(other)
9
+ right == other.right && left == other.left
10
+ end
11
+
12
+ class Right < Either
13
+ alias value right
14
+
15
+ def initialize(right)
16
+ @right = right
17
+ end
18
+
19
+ def >(f)
20
+ f.call(@right)
21
+ end
22
+
23
+ def fmap(&f)
24
+ Right.new(f.call(@right))
25
+ end
26
+
27
+ def to_maybe
28
+ Maybe::Some.new(@right)
29
+ end
30
+ end
31
+
32
+ class Left < Either
33
+ alias value left
34
+
35
+ def initialize(left)
36
+ @left = left
37
+ end
38
+
39
+ def >(f)
40
+ self
41
+ end
42
+
43
+ def fmap(&f)
44
+ self
45
+ end
46
+
47
+ def to_maybe
48
+ Maybe::None.new
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def Right(v)
55
+ Kleisli::Either::Right.new(v)
56
+ end
57
+
58
+ def Left(v)
59
+ Kleisli::Either::Left.new(v)
60
+ end
@@ -0,0 +1,7 @@
1
+ module Kleisli
2
+ class Functor
3
+ def fmap(&f)
4
+ raise NotImplementedError, "this functor doesn't implement fmap"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ require 'kleisli/monad'
2
+
3
+ module Kleisli
4
+ class Future < Monad
5
+ def self.lift(v=nil, &block)
6
+ if block
7
+ new(Thread.new(&block))
8
+ else
9
+ new(Thread.new { v })
10
+ end
11
+ end
12
+
13
+ def initialize(t)
14
+ @t = t
15
+ end
16
+
17
+ def >(f)
18
+ f.call(-> { await })
19
+ end
20
+
21
+ def fmap(&f)
22
+ Future.lift(f.call(-> { await }))
23
+ end
24
+
25
+ def await
26
+ @t.join.value
27
+ end
28
+ end
29
+ end
30
+
31
+ def Future(v=nil, &block)
32
+ Kleisli::Future.lift(v, &block)
33
+ end
@@ -0,0 +1,55 @@
1
+ require 'kleisli/monad'
2
+
3
+ module Kleisli
4
+ class Maybe < Monad
5
+ attr_reader :value
6
+
7
+ def self.lift(value)
8
+ if value.nil?
9
+ None.new
10
+ else
11
+ Some.new(value)
12
+ end
13
+ end
14
+
15
+ def ==(other)
16
+ value == other.value
17
+ end
18
+
19
+ class None < Maybe
20
+ def fmap(&f)
21
+ self
22
+ end
23
+
24
+ def >(block)
25
+ self
26
+ end
27
+ end
28
+
29
+ class Some < Maybe
30
+ def initialize(value)
31
+ @value = value
32
+ end
33
+
34
+ def fmap(&f)
35
+ Maybe.lift(f.call(@value))
36
+ end
37
+
38
+ def >(block)
39
+ block.call(@value)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def Maybe(v)
46
+ Kleisli::Maybe.lift(v)
47
+ end
48
+
49
+ def None()
50
+ Maybe(nil)
51
+ end
52
+
53
+ def Some(v)
54
+ Maybe(v)
55
+ end
@@ -0,0 +1,9 @@
1
+ require "kleisli/functor"
2
+
3
+ module Kleisli
4
+ class Monad < Functor
5
+ def >(block)
6
+ raise NotImplementedError, "this monad doesn't implement >->"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,65 @@
1
+ module Kleisli
2
+ module Monoid
3
+ def fold(others)
4
+ others.reduce(self) { |acc, x| acc.mappend x }
5
+ end
6
+
7
+ def mempty
8
+ raise NotImplementedError, "this monoid doesn't implement mpemty"
9
+ end
10
+
11
+ def mappend(other)
12
+ raise NotImplementedError, "this monoid doesn't implement mappend"
13
+ end
14
+ end
15
+ end
16
+
17
+ String.class_eval do
18
+ include Kleisli::Monoid
19
+
20
+ def mempty
21
+ ""
22
+ end
23
+
24
+ alias mappend +
25
+ end
26
+
27
+ Array.class_eval do
28
+ include Kleisli::Monoid
29
+
30
+ def mempty
31
+ []
32
+ end
33
+
34
+ alias mappend +
35
+ end
36
+
37
+ Hash.class_eval do
38
+ include Kleisli::Monoid
39
+
40
+ def mempty
41
+ {}
42
+ end
43
+
44
+ alias mappend merge
45
+ end
46
+
47
+ Fixnum.class_eval do
48
+ include Kleisli::Monoid
49
+
50
+ def mempty
51
+ 0
52
+ end
53
+
54
+ alias mappend +
55
+ end
56
+
57
+ Float.class_eval do
58
+ include Kleisli::Monoid
59
+
60
+ def mempty
61
+ 0
62
+ end
63
+
64
+ alias mappend +
65
+ end
@@ -0,0 +1,56 @@
1
+ require 'kleisli/monad'
2
+ require 'kleisli/maybe'
3
+
4
+ module Kleisli
5
+ class Try < Monad
6
+ attr_reader :exception, :value
7
+
8
+ def self.lift(f)
9
+ Success.new(f.call)
10
+ rescue => e
11
+ Failure.new(e)
12
+ end
13
+
14
+ class Success < Try
15
+ def initialize(value)
16
+ @value = value
17
+ end
18
+
19
+ def >(f)
20
+ f.call(@value)
21
+ rescue => e
22
+ Failure.new(e)
23
+ end
24
+
25
+ def fmap(&f)
26
+ Try { f.call(@value) }
27
+ end
28
+
29
+ def to_maybe
30
+ Maybe::Some.new(@value)
31
+ end
32
+ end
33
+
34
+ class Failure < Try
35
+ def initialize(exception)
36
+ @exception = exception
37
+ end
38
+
39
+ def >(f)
40
+ self
41
+ end
42
+
43
+ def fmap(&f)
44
+ self
45
+ end
46
+
47
+ def to_maybe
48
+ Maybe::None.new
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def Try(&f)
55
+ Kleisli::Try.lift(f)
56
+ end
@@ -0,0 +1,3 @@
1
+ module Kleisli
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,36 @@
1
+ require 'kleisli/monad'
2
+ require 'kleisli/monoid'
3
+
4
+ module Kleisli
5
+ class Writer < Monad
6
+ def self.lift(log, value)
7
+ new(log, value)
8
+ end
9
+
10
+ def initialize(log, value)
11
+ @log, @value = log, value
12
+ end
13
+
14
+ def ==(other)
15
+ unwrap == other.unwrap
16
+ end
17
+
18
+ def >(f)
19
+ other_log, other_value = f.call(@value).unwrap
20
+ Writer.new(@log + other_log, other_value)
21
+ end
22
+
23
+ def fmap(&f)
24
+ new_value = f.call(@value)
25
+ Writer.new(@log, new_value)
26
+ end
27
+
28
+ def unwrap
29
+ [@log, @value]
30
+ end
31
+ end
32
+ end
33
+
34
+ def Writer(log, value)
35
+ Kleisli::Writer.lift(log, value)
36
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+
3
+ class EitherTest < MiniTest::Unit::TestCase
4
+ def test_lift_right
5
+ assert_equal 3, Right(3).value
6
+ end
7
+
8
+ def test_lift_left
9
+ assert_equal "error", Left("error").value
10
+ end
11
+
12
+ def test_bind_right
13
+ v = Right(1) >-> x {
14
+ if x == 1
15
+ Right(x + 90)
16
+ else
17
+ Left("FAIL")
18
+ end
19
+ }
20
+ assert_equal Right(91), v
21
+ end
22
+
23
+ def test_bind_left
24
+ v = Left("error") >-> x {
25
+ Right(x * 20)
26
+ }
27
+ assert_equal Left("error"), v
28
+ end
29
+
30
+ def test_fmap_right
31
+ assert_equal Right(2), Right(1).fmap { |x| x * 2 }
32
+ end
33
+
34
+ def test_fmap_left
35
+ assert_equal Left("error"), Left("error").fmap { |x| x * 2 }
36
+ end
37
+
38
+ def test_to_maybe_right
39
+ assert_equal Some(2), Right(1).fmap { |x| x * 2 }.to_maybe
40
+ end
41
+
42
+ def test_to_maybe_left
43
+ assert_equal None(), Left("error").fmap { |x| x * 2 }.to_maybe
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class FutureTest < MiniTest::Unit::TestCase
4
+ def test_immediate_value
5
+ assert_equal 30, Future(30).await
6
+ end
7
+
8
+ def test_simple_future_executes_in_parallel
9
+ str = ""
10
+ Future { sleep 0.1; str << "bar" }.tap {
11
+ str << "foo"
12
+ }.await
13
+ assert_equal "foobar", str
14
+ end
15
+
16
+ def test_bind
17
+ f = Future(30) >-> n {
18
+ Future { n.call * 2 }
19
+ } >-> n {
20
+ Future { n.call * 2 } >-> m {
21
+ Future(m.call + 2)
22
+ }
23
+ }
24
+ assert_equal 122, f.await
25
+ end
26
+
27
+ def test_fmap
28
+ f = Future(30).fmap { |x| x.call * 2 }
29
+ assert_equal 60, f.await
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ class MaybeTest < MiniTest::Unit::TestCase
4
+ def test_unwrapping_some
5
+ assert_equal 3, Some(3).value
6
+ end
7
+
8
+ def test_unwrapping_none
9
+ assert_equal nil, None().value
10
+ end
11
+
12
+ def test_bind_none
13
+ assert_equal None(), None() >-> x { Maybe(x * 2) }
14
+ end
15
+
16
+ def test_bind_some
17
+ assert_equal Some(6), Some(3) >-> x { Maybe(x * 2) }
18
+ end
19
+
20
+ def test_fmap_none
21
+ assert_equal None(), None().fmap { |x| x * 2 }
22
+ end
23
+
24
+ def test_fmap_some
25
+ assert_equal Some(6), Some(3).fmap { |x| x * 2 }
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class MonoidTest < MiniTest::Unit::TestCase
4
+ def test_string_fold
5
+ assert_equal "hellogoodbye", "hello".fold(%w(good bye))
6
+ end
7
+
8
+ def test_array_fold
9
+ assert_equal [1, 2, 3], [1].fold([[2], [3]])
10
+ end
11
+
12
+ def test_hash_fold
13
+ assert_equal({a: 1, b: 2, c: 3}, {a: 1}.fold([{b: 2}, {c: 3}]))
14
+ end
15
+
16
+ def test_fixnum_fold
17
+ assert_equal 6, 1.fold([2,3])
18
+ end
19
+
20
+ def test_float_fold
21
+ assert_equal 6.0, 1.0.fold([2.0,3.0])
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ class TryTest < MiniTest::Unit::TestCase
4
+ def test_success
5
+ assert_equal 2, Try { 10 / 5 }.value
6
+ end
7
+
8
+ def test_failure
9
+ assert_kind_of ZeroDivisionError, Try { 10 / 0 }.exception
10
+ end
11
+
12
+ def test_to_maybe_success
13
+ assert_equal Some(2), Try { 10 / 5 }.to_maybe
14
+ end
15
+
16
+ def test_to_maybe_failure
17
+ assert_equal None(), Try { 10 / 0 }.to_maybe
18
+ end
19
+
20
+ def test_fmap_success
21
+ assert_equal 4, Try { 10 / 5 }.fmap { |x| x * 2 }.value
22
+ end
23
+
24
+ def test_fmap_failure
25
+ assert_kind_of ZeroDivisionError, Try { 10 / 0 }.fmap { |x| x * 2 }.exception
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class WriterTest < MiniTest::Unit::TestCase
4
+ def test_unwrap
5
+ log, value = Writer("log", 100).unwrap
6
+ assert_equal "log", log
7
+ assert_equal 100, value
8
+ end
9
+
10
+ def test_bind
11
+ writer = Writer("foo", 100) >-> value { Writer("bar", value + 100) }
12
+ assert_equal Writer("foobar", 200), writer
13
+ end
14
+
15
+ def test_fmap
16
+ writer = Writer("foo", 100).fmap { |value| value + 100 }
17
+ assert_equal Writer("foo", 200), writer
18
+ end
19
+ end
@@ -0,0 +1,2 @@
1
+ require 'kleisli'
2
+ require 'test/unit'
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kleisli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Josep M. Bach
8
+ - Ryan Levick
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-10-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.7'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.7'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ description: Usable, idiomatic common monads in Ruby
43
+ email:
44
+ - josep.m.bach@gmail.com
45
+ - ryan.levick@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".gitignore"
51
+ - Gemfile
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - kleisli.gemspec
56
+ - lib/kleisli.rb
57
+ - lib/kleisli/either.rb
58
+ - lib/kleisli/functor.rb
59
+ - lib/kleisli/future.rb
60
+ - lib/kleisli/maybe.rb
61
+ - lib/kleisli/monad.rb
62
+ - lib/kleisli/monoid.rb
63
+ - lib/kleisli/try.rb
64
+ - lib/kleisli/version.rb
65
+ - lib/kleisli/writer.rb
66
+ - test/kleisli/either_test.rb
67
+ - test/kleisli/future_test.rb
68
+ - test/kleisli/maybe_test.rb
69
+ - test/kleisli/monoid_test.rb
70
+ - test/kleisli/try_test.rb
71
+ - test/kleisli/writer_test.rb
72
+ - test/test_helper.rb
73
+ homepage: https://github.com/txus/kleisli
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Usable, idiomatic common monads in Ruby
97
+ test_files:
98
+ - test/kleisli/either_test.rb
99
+ - test/kleisli/future_test.rb
100
+ - test/kleisli/maybe_test.rb
101
+ - test/kleisli/monoid_test.rb
102
+ - test/kleisli/try_test.rb
103
+ - test/kleisli/writer_test.rb
104
+ - test/test_helper.rb