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 CHANGED
@@ -1,7 +1,7 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 54cce46f431f0930414953f5d860ab9805f8aa38
4
- data.tar.gz: 03b56b5e9209404852e50d3a1ff639173efd82ff
5
- SHA512:
6
- metadata.gz: ca6dabae528183053604cf6adccf3a621e96d25fc5171d507113eeb5aaf2c7349cc21bceefe4e053275f62093381b3b737927cf76234ab63de3bbb3b0e03385d
7
- data.tar.gz: 1f19da23d4ca766b1c8b16323863fb750b197c0e84602d0b4b34f6316bc7bb3fc171d8ae868e5bb023b978801890be7d95a5b2efc6cf399bf78dd28d5a072698
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
- def foo(s)
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
- puts f.call " localhost:9092 "
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 always, using point-free style is much cleaner:
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
- # BlankSlate provides an abstract base class with no predefined
14
+ # Blank provides an abstract base class with no predefined
13
15
  # methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
14
- # BlankSlate is useful as a base class when writing classes that
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 BlankSlate
19
+ class Blank
18
20
  class << self
19
21
 
20
- # Hide the method named +name+ in the BlankSlate class. Don't
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
- # BlankSlate <em>after BlankSlate is defined</em> will show up in the
52
- # list of available BlankSlate methods. We handle this by defining a
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 BlankSlate has been loaded.
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
- # BlankSlate class.
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
- BlankSlate.hide(name)
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
- # BlankSlate class.
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
- BlankSlate.hide(name)
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
- BlankSlate.hide(name)
108
+ Blank.hide(name)
107
109
  end
108
110
  result
109
111
  end
@@ -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 < BasicObject
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 method_missing(m, *args, &block)
20
- fn = -> a, x {
21
- if x.respond_to?(m)
22
- x.send(m, *a)
23
- else
24
- send(m, *[x, a])
25
- end
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 + [fn])
32
+ ComposedFn.new(@fns + [f])
28
33
  end
29
34
 
30
35
  def call(*args)
31
- @fns.reduce(:*).call(*args)
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
@@ -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
- other
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
@@ -50,8 +50,10 @@ module Kleisli
50
50
  end
51
51
  end
52
52
 
53
+ Maybe = Kleisli::Maybe.method(:lift)
54
+
53
55
  def Maybe(v)
54
- Kleisli::Maybe.lift(v)
56
+ Maybe.(v)
55
57
  end
56
58
 
57
59
  def None()
@@ -1,3 +1,3 @@
1
1
  module Kleisli
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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
@@ -42,4 +42,8 @@ class EitherTest < MiniTest::Unit::TestCase
42
42
  def test_to_maybe_left
43
43
  assert_equal None(), Left("error").fmap { |x| x * 2 }.to_maybe
44
44
  end
45
+
46
+ def test_pointfree
47
+ assert_equal Right(10), Right(5) >> F . fn(&Right) . *(2)
48
+ end
45
49
  end
@@ -9,13 +9,13 @@ class MaybeTest < MiniTest::Unit::TestCase
9
9
  assert_equal nil, None().value
10
10
  end
11
11
 
12
- # def test_bind_none
13
- # assert_equal None(), None() >> F . Maybe . *(2)
14
- # end
12
+ def test_bind_none
13
+ assert_equal None(), None() >> F . fn(&Maybe) . *(2)
14
+ end
15
15
 
16
- # def test_bind_some
17
- # assert_equal Some(6), Some(3) >> F . Maybe . *(2)
18
- # end
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 }
@@ -29,9 +29,4 @@ class TryTest < MiniTest::Unit::TestCase
29
29
  try = Try { 20 / 10 } >-> number { Try { 10 / number } }
30
30
  assert_equal 5, try.value
31
31
  end
32
-
33
- # def test_pointfree
34
- # try = Try { 20 / 10 } >> F . Try { 10 / 2 }
35
- # assert_equal 5, try.value
36
- # end
37
32
  end
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.6
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
- date: 2014-11-17 00:00:00 Z
14
- dependencies:
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
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
19
- requirements:
20
- - - ~>
21
- - !ruby/object:Gem::Version
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
- requirement: &id002 !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ~>
31
- - !ruby/object:Gem::Version
32
- version: "10.0"
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
- version_requirements: *id002
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
- files:
46
- - .gitignore
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/blankslate.rb
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
- - &id003
85
- - ">="
86
- - !ruby/object:Gem::Version
87
- version: "0"
88
- required_rubygems_version: !ruby/object:Gem::Requirement
89
- requirements:
90
- - *id003
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.4.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