kleisli 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.
@@ -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