kleisli 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -7
- data/README.md +35 -9
- data/lib/kleisli/{blankslate.rb → blank.rb} +14 -12
- data/lib/kleisli/composition.rb +19 -10
- data/lib/kleisli/either.rb +10 -3
- data/lib/kleisli/maybe.rb +3 -1
- data/lib/kleisli/version.rb +1 -1
- data/test/kleisli/composition_test.rb +78 -0
- data/test/kleisli/either_test.rb +4 -0
- data/test/kleisli/maybe_test.rb +6 -6
- data/test/kleisli/try_test.rb +0 -5
- metadata +51 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
5
|
-
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: faefce7df113e758631337927c7a3ec32f0305ff
|
4
|
+
data.tar.gz: 6e583da161293e7c603163f24dfd6af293c5445b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 24c366c7151184fb84fc42938f4afd1473e75575ff82c7e0ee5255b44bcef9f1ec028c10bd0ac19d84635b6b9ef53c7b6332f8e5e048df2ed9d73833482ca12c
|
7
|
+
data.tar.gz: b8d1ad6965cfe41ddf33e72af50ac7d4cfbc62b07aa23873625af8981c78a35f5a271aeea14929a76a1894fb26860faba1867af02f5d1c340368815cdf2d28bc
|
data/README.md
CHANGED
@@ -47,11 +47,9 @@ f.call "hello"
|
|
47
47
|
Functions and methods are interchangeable:
|
48
48
|
|
49
49
|
```ruby
|
50
|
-
|
51
|
-
s.reverse
|
52
|
-
end
|
50
|
+
foo = lambda { |s| s.reverse }
|
53
51
|
|
54
|
-
f = F . capitalize . foo
|
52
|
+
f = F . capitalize . fn(&foo)
|
55
53
|
f.call "hello"
|
56
54
|
# => "Olleh"
|
57
55
|
```
|
@@ -60,11 +58,24 @@ All functions and methods are partially applicable:
|
|
60
58
|
|
61
59
|
```ruby
|
62
60
|
|
61
|
+
# Partially applied method:
|
63
62
|
f = F . split(":") . strip
|
64
|
-
|
63
|
+
f.call " localhost:9092 "
|
64
|
+
# => ["localhost", "9092"]
|
65
|
+
|
66
|
+
# Partially applied lambda:
|
67
|
+
my_split = lambda { |str, *args| str.split(*args) }
|
68
|
+
f = F . fn(":", &split)
|
69
|
+
f.call " localhost:9092 "
|
65
70
|
# => ["localhost", "9092"]
|
66
71
|
```
|
67
72
|
|
73
|
+
Finally, for convenience, `F` is the identity function:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
F.call(1) # => 1
|
77
|
+
```
|
78
|
+
|
68
79
|
## Maybe monad
|
69
80
|
|
70
81
|
The Maybe monad is useful to express a pipeline of computations that might
|
@@ -88,14 +99,12 @@ maybe_user = Maybe(user) >-> user {
|
|
88
99
|
x = Some(10)
|
89
100
|
y = None()
|
90
101
|
|
91
|
-
# Now using fancy point-free style:
|
92
|
-
Maybe(user) >> F . Maybe . address >> F . Maybe . street
|
93
102
|
```
|
94
103
|
|
95
|
-
As
|
104
|
+
As usual (with Maybe and Either), using point-free style is much cleaner:
|
96
105
|
|
97
106
|
```ruby
|
98
|
-
Maybe(user) >> F . Maybe . address >> F . Maybe . street
|
107
|
+
Maybe(user) >> F . fn(&Maybe) . address >> F . fn(&Maybe) . street
|
99
108
|
```
|
100
109
|
|
101
110
|
### `fmap`
|
@@ -206,6 +215,11 @@ result.value # => "value was less or equal than 1"
|
|
206
215
|
# If it failed in the second block
|
207
216
|
result # => Left("value was not even")
|
208
217
|
result.value # => "value was not even"
|
218
|
+
|
219
|
+
# Point-free style bind!
|
220
|
+
result = Right(3) >> F . fn(&Right) . *(2)
|
221
|
+
result # => Right(6)
|
222
|
+
result.value # => 6
|
209
223
|
```
|
210
224
|
|
211
225
|
### `fmap`
|
@@ -225,6 +239,18 @@ result # => Right(20)
|
|
225
239
|
result # => Left("wrong")
|
226
240
|
```
|
227
241
|
|
242
|
+
### `or`
|
243
|
+
|
244
|
+
`or` does pretty much what would you expect:
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
require 'kleisli'
|
248
|
+
|
249
|
+
Right(10).or(Right(999)) # => Right(10)
|
250
|
+
Left("error").or(Left("new error")) # => Left("new error")
|
251
|
+
Left("error").or { |err| Left("new #{err}") } # => Left("new error")
|
252
|
+
```
|
253
|
+
|
228
254
|
### `to_maybe`
|
229
255
|
|
230
256
|
Sometimes it's useful to turn an `Either` into a `Maybe`. You can use
|
@@ -1,4 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# Note: BlankSlate was renamed to Blank to avoid collisions in this project.
|
3
|
+
#
|
2
4
|
#--
|
3
5
|
# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
|
4
6
|
# All rights reserved.
|
@@ -9,15 +11,15 @@
|
|
9
11
|
#++
|
10
12
|
|
11
13
|
######################################################################
|
12
|
-
#
|
14
|
+
# Blank provides an abstract base class with no predefined
|
13
15
|
# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
|
14
|
-
#
|
16
|
+
# Blank is useful as a base class when writing classes that
|
15
17
|
# depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
|
16
18
|
#
|
17
|
-
class
|
19
|
+
class Blank
|
18
20
|
class << self
|
19
21
|
|
20
|
-
# Hide the method named +name+ in the
|
22
|
+
# Hide the method named +name+ in the Blank class. Don't
|
21
23
|
# hide +instance_eval+ or any method beginning with "__".
|
22
24
|
def hide(name)
|
23
25
|
methods = instance_methods.map(&:to_sym)
|
@@ -48,21 +50,21 @@ end
|
|
48
50
|
|
49
51
|
######################################################################
|
50
52
|
# Since Ruby is very dynamic, methods added to the ancestors of
|
51
|
-
#
|
52
|
-
# list of available
|
53
|
+
# Blank <em>after Blank is defined</em> will show up in the
|
54
|
+
# list of available Blank methods. We handle this by defining a
|
53
55
|
# hook in the Object and Kernel classes that will hide any method
|
54
|
-
# defined after
|
56
|
+
# defined after Blank has been loaded.
|
55
57
|
#
|
56
58
|
module Kernel
|
57
59
|
class << self
|
58
60
|
alias_method :blank_slate_method_added, :method_added
|
59
61
|
|
60
62
|
# Detect method additions to Kernel and remove them in the
|
61
|
-
#
|
63
|
+
# Blank class.
|
62
64
|
def method_added(name)
|
63
65
|
result = blank_slate_method_added(name)
|
64
66
|
return result if self != Kernel
|
65
|
-
|
67
|
+
Blank.hide(name)
|
66
68
|
result
|
67
69
|
end
|
68
70
|
end
|
@@ -76,11 +78,11 @@ class Object
|
|
76
78
|
alias_method :blank_slate_method_added, :method_added
|
77
79
|
|
78
80
|
# Detect method additions to Object and remove them in the
|
79
|
-
#
|
81
|
+
# Blank class.
|
80
82
|
def method_added(name)
|
81
83
|
result = blank_slate_method_added(name)
|
82
84
|
return result if self != Object
|
83
|
-
|
85
|
+
Blank.hide(name)
|
84
86
|
result
|
85
87
|
end
|
86
88
|
|
@@ -103,7 +105,7 @@ class Module
|
|
103
105
|
result = blankslate_original_append_features(mod)
|
104
106
|
return result if mod != Object
|
105
107
|
instance_methods.each do |name|
|
106
|
-
|
108
|
+
Blank.hide(name)
|
107
109
|
end
|
108
110
|
result
|
109
111
|
end
|
data/lib/kleisli/composition.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'blank'
|
2
|
+
|
1
3
|
class Proc
|
2
4
|
def self.comp(f, g)
|
3
5
|
lambda { |*args| f[g[*args]] }
|
@@ -9,26 +11,33 @@ class Proc
|
|
9
11
|
end
|
10
12
|
|
11
13
|
module Kleisli
|
12
|
-
class ComposedFn <
|
14
|
+
class ComposedFn < Blank
|
13
15
|
include ::Kernel
|
14
16
|
|
15
17
|
def initialize(fns=[])
|
16
18
|
@fns = fns
|
17
19
|
end
|
18
20
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
def fn(*args, &block)
|
22
|
+
f = -> arguments, receiver {
|
23
|
+
block.call(receiver, *arguments)
|
24
|
+
}.curry[args]
|
25
|
+
ComposedFn.new(@fns + [f])
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(meth, *args, &block)
|
29
|
+
f = -> arguments, receiver {
|
30
|
+
receiver.send(meth, *arguments, &block)
|
26
31
|
}.curry[args]
|
27
|
-
ComposedFn.new(@fns + [
|
32
|
+
ComposedFn.new(@fns + [f])
|
28
33
|
end
|
29
34
|
|
30
35
|
def call(*args)
|
31
|
-
@fns.
|
36
|
+
if @fns.any?
|
37
|
+
@fns.reduce(:*).call(*args)
|
38
|
+
else
|
39
|
+
args.first
|
40
|
+
end
|
32
41
|
end
|
33
42
|
|
34
43
|
def to_ary
|
data/lib/kleisli/either.rb
CHANGED
@@ -28,7 +28,7 @@ module Kleisli
|
|
28
28
|
Maybe::Some.new(@right)
|
29
29
|
end
|
30
30
|
|
31
|
-
def or(other)
|
31
|
+
def or(other, &other_blk)
|
32
32
|
self
|
33
33
|
end
|
34
34
|
end
|
@@ -52,13 +52,20 @@ module Kleisli
|
|
52
52
|
Maybe::None.new
|
53
53
|
end
|
54
54
|
|
55
|
-
def or(other)
|
56
|
-
|
55
|
+
def or(other, &other_blk)
|
56
|
+
if other_blk
|
57
|
+
other_blk.call(@left)
|
58
|
+
else
|
59
|
+
other
|
60
|
+
end
|
57
61
|
end
|
58
62
|
end
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
66
|
+
Right = Kleisli::Either::Right.method(:new)
|
67
|
+
Left = Kleisli::Either::Left.method(:new)
|
68
|
+
|
62
69
|
def Right(v)
|
63
70
|
Kleisli::Either::Right.new(v)
|
64
71
|
end
|
data/lib/kleisli/maybe.rb
CHANGED
data/lib/kleisli/version.rb
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CompositionTest < MiniTest::Unit::TestCase
|
4
|
+
def test_one_method
|
5
|
+
f = F . first
|
6
|
+
result = f.call([1])
|
7
|
+
assert Fixnum === result, "#{result} is not a number"
|
8
|
+
assert_equal 1, result
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_two_methods
|
12
|
+
f = F . first . last
|
13
|
+
result = f.call([1, [2,3]])
|
14
|
+
assert Fixnum === result, "#{result} is not a number"
|
15
|
+
assert_equal 2, result
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_one_function
|
19
|
+
my_first = lambda { |x| x.first }
|
20
|
+
|
21
|
+
f = F . fn(&my_first)
|
22
|
+
result = f.call([1])
|
23
|
+
assert Fixnum === result, "#{result} is not a number"
|
24
|
+
assert_equal 1, result
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_two_functions
|
28
|
+
my_first = lambda { |x| x.first }
|
29
|
+
my_last = lambda { |x| x.last }
|
30
|
+
|
31
|
+
f = F . fn(&my_first) . fn(&my_last)
|
32
|
+
result = f.call([1, [2,3]])
|
33
|
+
assert Fixnum === result, "#{result} is not a number"
|
34
|
+
assert_equal 2, result
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_one_function_one_block
|
38
|
+
my_last = lambda { |x| x.last }
|
39
|
+
|
40
|
+
f = F . fn { |x| x.first } . fn(&my_last)
|
41
|
+
result = f.call([1, [2,3]])
|
42
|
+
assert Fixnum === result, "#{result} is not a number"
|
43
|
+
assert_equal 2, result
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_one_function_one_method
|
47
|
+
my_last = lambda { |x| x.last }
|
48
|
+
|
49
|
+
f = F . first . fn(&my_last)
|
50
|
+
result = f.call([1, [2,3]])
|
51
|
+
assert Fixnum === result, "#{result} is not a number"
|
52
|
+
assert_equal 2, result
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_undefined_method
|
56
|
+
f = F . foo
|
57
|
+
assert_raises(NoMethodError) { f.call(1) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_identity
|
61
|
+
assert_equal 1, F.call(1)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_partially_applied_method
|
65
|
+
f = F . split(":")
|
66
|
+
result = f.call("localhost:9092")
|
67
|
+
assert Array === result, "#{result} is not an array"
|
68
|
+
assert_equal ["localhost", "9092"], result
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_partially_applied_fn
|
72
|
+
split = lambda { |x, *args| x.split(*args) }
|
73
|
+
f = F . fn(":", &split)
|
74
|
+
result = f.call("localhost:9092")
|
75
|
+
assert Array === result, "#{result} is not an array"
|
76
|
+
assert_equal ["localhost", "9092"], result
|
77
|
+
end
|
78
|
+
end
|
data/test/kleisli/either_test.rb
CHANGED
data/test/kleisli/maybe_test.rb
CHANGED
@@ -9,13 +9,13 @@ class MaybeTest < MiniTest::Unit::TestCase
|
|
9
9
|
assert_equal nil, None().value
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def test_bind_none
|
13
|
+
assert_equal None(), None() >> F . fn(&Maybe) . *(2)
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
def test_bind_some
|
17
|
+
assert_equal Some(6), Some(3) >> F . fn(&Maybe) . *(2)
|
18
|
+
end
|
19
19
|
|
20
20
|
def test_fmap_none
|
21
21
|
assert_equal None(), None().fmap { |x| x * 2 }
|
data/test/kleisli/try_test.rb
CHANGED
metadata
CHANGED
@@ -1,57 +1,61 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: kleisli
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
|
-
authors:
|
6
|
+
authors:
|
7
7
|
- Josep M. Bach
|
8
8
|
- Ryan Levick
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2014-11-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
16
15
|
name: bundler
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
version: "1.6"
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.6'
|
23
21
|
type: :development
|
24
|
-
version_requirements: *id001
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: rake
|
27
22
|
prerelease: false
|
28
|
-
|
29
|
-
requirements:
|
30
|
-
- - ~>
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version:
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.6'
|
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'
|
33
35
|
type: :development
|
34
|
-
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
35
42
|
description: Usable, idiomatic common monads in Ruby
|
36
|
-
email:
|
43
|
+
email:
|
37
44
|
- josep.m.bach@gmail.com
|
38
45
|
- ryan.levick@gmail.com
|
39
46
|
executables: []
|
40
|
-
|
41
47
|
extensions: []
|
42
|
-
|
43
48
|
extra_rdoc_files: []
|
44
|
-
|
45
|
-
|
46
|
-
- .
|
47
|
-
- .travis.yml
|
49
|
+
files:
|
50
|
+
- ".gitignore"
|
51
|
+
- ".travis.yml"
|
48
52
|
- Gemfile
|
49
53
|
- LICENSE.txt
|
50
54
|
- README.md
|
51
55
|
- Rakefile
|
52
56
|
- kleisli.gemspec
|
53
57
|
- lib/kleisli.rb
|
54
|
-
- lib/kleisli/
|
58
|
+
- lib/kleisli/blank.rb
|
55
59
|
- lib/kleisli/composition.rb
|
56
60
|
- lib/kleisli/either.rb
|
57
61
|
- lib/kleisli/functor.rb
|
@@ -62,6 +66,7 @@ files:
|
|
62
66
|
- lib/kleisli/try.rb
|
63
67
|
- lib/kleisli/version.rb
|
64
68
|
- lib/kleisli/writer.rb
|
69
|
+
- test/kleisli/composition_test.rb
|
65
70
|
- test/kleisli/either_test.rb
|
66
71
|
- test/kleisli/future_test.rb
|
67
72
|
- test/kleisli/maybe_test.rb
|
@@ -70,32 +75,31 @@ files:
|
|
70
75
|
- test/kleisli/writer_test.rb
|
71
76
|
- test/test_helper.rb
|
72
77
|
homepage: https://github.com/txus/kleisli
|
73
|
-
licenses:
|
78
|
+
licenses:
|
74
79
|
- MIT
|
75
80
|
metadata: {}
|
76
|
-
|
77
81
|
post_install_message:
|
78
82
|
rdoc_options: []
|
79
|
-
|
80
|
-
require_paths:
|
83
|
+
require_paths:
|
81
84
|
- lib
|
82
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
-
requirements:
|
84
|
-
-
|
85
|
-
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
91
95
|
requirements: []
|
92
|
-
|
93
96
|
rubyforge_project:
|
94
|
-
rubygems_version: 2.
|
97
|
+
rubygems_version: 2.2.2
|
95
98
|
signing_key:
|
96
99
|
specification_version: 4
|
97
100
|
summary: Usable, idiomatic common monads in Ruby
|
98
|
-
test_files:
|
101
|
+
test_files:
|
102
|
+
- test/kleisli/composition_test.rb
|
99
103
|
- test/kleisli/either_test.rb
|
100
104
|
- test/kleisli/future_test.rb
|
101
105
|
- test/kleisli/maybe_test.rb
|