kleisli 0.0.6 → 0.1.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 +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
|