funkify 0.0.2 → 0.0.3
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 +4 -4
- data/README.md +26 -9
- data/Rakefile +28 -0
- data/lib/funkify.rb +45 -15
- data/lib/funkify/version.rb +1 -1
- data/spec/funkify_spec.rb +39 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1509b7e4e421ff184a3ddebf8bae52b54063883
|
4
|
+
data.tar.gz: 30e1768e67a4a65de187b0035050c795fa572e61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0ba7e0c32a65562b351b001264506ed55c13980382c25169dd76839cc23feb8138df7715aaf0cc9b8e000fe3cb56277bbc6684a3f2674f9e0263bf121594d58
|
7
|
+
data.tar.gz: e22735ce17f3f5e11ef60952c176d9d07ce1cb71f99370043035c0314e9998af8ba6b31ebf0e7f55760651713cba23dac33986947addedd82e1c05e77cd518c1
|
data/README.md
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
_Haskell-style partial application and composition for Ruby methods_
|
4
4
|
|
5
|
-
|
6
5
|
"In computer science, partial application refers to the process of
|
7
6
|
fixing a number of arguments to a function, producing another function of smaller arity."
|
8
7
|
--[Wikipedia](http://en.wikipedia.org/wiki/Partial_application)
|
9
8
|
|
10
|
-
[
|
11
|
-
[
|
9
|
+
[Curring in Haskell](http://www.haskell.org/haskellwiki/Currying)<br>
|
10
|
+
[Partial application in Haskell](http://www.haskell.org/haskellwiki/Partial_application)<br>
|
11
|
+
[Function composition in Haskell](http://www.haskell.org/haskellwiki/Function_composition)
|
12
12
|
|
13
13
|
|
14
14
|
## Usage
|
@@ -38,6 +38,8 @@ class MyFunkyClass
|
|
38
38
|
end
|
39
39
|
```
|
40
40
|
|
41
|
+
### Partial application and currying
|
42
|
+
|
41
43
|
When a method supports autocurrying it can still be invoked normally (if all parameters are provided) however if less than the required number are given a `Proc` is returned
|
42
44
|
with the given parameters partially applied:
|
43
45
|
|
@@ -49,18 +51,29 @@ add_1 = funky.add(1) #=> The `1` is partially applied and a `Proc` is returned
|
|
49
51
|
add_1.(2) #=> We invoke that `Proc` with the remaining argument and the final result (`3`) is returned.
|
50
52
|
```
|
51
53
|
|
52
|
-
|
54
|
+
### Function composition
|
55
|
+
|
56
|
+
We compose methods using the `*` and `|` operators.
|
53
57
|
|
58
|
+
`*` composes right to left, this is the standard way to compose functions found in languages like Haskell:
|
54
59
|
```ruby
|
55
|
-
|
56
|
-
add_1_and_multiply_by_5.(10) #=> 55
|
60
|
+
(mult(5) * add(1)).(10) #=> 55
|
57
61
|
|
58
|
-
# We can
|
62
|
+
# We can further compose the above with another method:
|
63
|
+
(negate * mult(5) * add(1)).(10) #=> -55
|
64
|
+
```
|
59
65
|
|
60
|
-
|
66
|
+
`|` composes left to right, like a shell pipeline:
|
67
|
+
```ruby
|
68
|
+
(mult(5) | add(1) | negate).(3) #=> -16
|
61
69
|
```
|
62
70
|
|
63
|
-
|
71
|
+
As a cute bonus, we can inject values from the left into a pipeline with the `pass` method ([see more](http://showterm.io/47f46234281cf2c25f44a#fast)):
|
72
|
+
```ruby
|
73
|
+
pass(3) | (mult(5) | add(1) | negate) #=> -16
|
74
|
+
```
|
75
|
+
|
76
|
+
#### Other examples:
|
64
77
|
|
65
78
|
Add 10 to every item in an Enumerable:
|
66
79
|
|
@@ -87,6 +100,10 @@ And then execute:
|
|
87
100
|
Or install it yourself as:
|
88
101
|
|
89
102
|
$ gem install funkify
|
103
|
+
|
104
|
+
## Dedication
|
105
|
+
|
106
|
+
This library was inspired in part by stimulating conversations with [epitron](https://github.com/epitron) on Freenode.
|
90
107
|
|
91
108
|
## Contributing
|
92
109
|
|
data/Rakefile
CHANGED
@@ -8,6 +8,34 @@ def run_specs paths
|
|
8
8
|
exec "bacon -Ispec -rubygems #{quiet} #{paths.join ' '}"
|
9
9
|
end
|
10
10
|
|
11
|
+
desc "run tests"
|
11
12
|
task :test do
|
12
13
|
run_specs Dir['spec/**/*_spec.rb'].shuffle!
|
13
14
|
end
|
15
|
+
|
16
|
+
desc "run pry with the development version of funkify loaded"
|
17
|
+
task :pry do
|
18
|
+
sh "pry -I./lib -r funkify"
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "remove all gems"
|
22
|
+
task :rm_gems do
|
23
|
+
sh "rm *.gem" rescue nil
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "build the gem"
|
27
|
+
task :gem => :rm_gems do
|
28
|
+
sh "gem build funkify.gemspec"
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "display the current version"
|
32
|
+
task :version do
|
33
|
+
puts Funkify::VERSION
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "build and push latest gems"
|
37
|
+
task :pushgem => :gem do
|
38
|
+
Dir["*.gem"].each do |gemfile|
|
39
|
+
sh "gem push funkify-#{Funkify::VERSION}.gem"
|
40
|
+
end
|
41
|
+
end
|
data/lib/funkify.rb
CHANGED
@@ -9,6 +9,14 @@ module Funkify
|
|
9
9
|
def *(other)
|
10
10
|
Funkify.compose(self, other)
|
11
11
|
end
|
12
|
+
|
13
|
+
def |(other)
|
14
|
+
if arity.zero?
|
15
|
+
other.(*self.())
|
16
|
+
else
|
17
|
+
Funkify.compose(other, self)
|
18
|
+
end
|
19
|
+
end
|
12
20
|
end
|
13
21
|
|
14
22
|
module_function
|
@@ -22,6 +30,11 @@ module Funkify
|
|
22
30
|
end
|
23
31
|
end
|
24
32
|
|
33
|
+
def pass(*xs)
|
34
|
+
-> { xs }
|
35
|
+
end
|
36
|
+
public :pass
|
37
|
+
|
25
38
|
def compose(*args)
|
26
39
|
head, *tail = args
|
27
40
|
head = _procify(head)
|
@@ -41,22 +54,22 @@ module Funkify
|
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
return
|
55
|
-
end
|
57
|
+
def self.auto_curry_all_methods(receiver)
|
58
|
+
in_use = nil
|
59
|
+
receiver.define_singleton_method(:method_added) do |name|
|
60
|
+
return if in_use
|
61
|
+
in_use = true
|
62
|
+
receiver.auto_curry name
|
63
|
+
in_use = false
|
64
|
+
end
|
65
|
+
end
|
56
66
|
|
57
|
-
|
58
|
-
|
59
|
-
|
67
|
+
def self.auto_curry_some_methods(names, receiver)
|
68
|
+
names.each do |name|
|
69
|
+
m = receiver.instance_method(name)
|
70
|
+
curried_method = nil
|
71
|
+
|
72
|
+
receiver.class_eval do
|
60
73
|
define_method(name) do |*args|
|
61
74
|
curried_method ||= m.bind(self).to_proc.curry
|
62
75
|
curried_method[*args]
|
@@ -64,4 +77,21 @@ module Funkify
|
|
64
77
|
end
|
65
78
|
end
|
66
79
|
end
|
80
|
+
|
81
|
+
module ClassMethods
|
82
|
+
def auto_curry(*names)
|
83
|
+
if names.empty?
|
84
|
+
Funkify.auto_curry_all_methods(self)
|
85
|
+
else
|
86
|
+
Funkify.auto_curry_some_methods(names, self)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def point_free(&block)
|
91
|
+
-> (*args) do
|
92
|
+
b = instance_exec(&block).curry
|
93
|
+
args.empty? ? b : b[*args]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
67
97
|
end
|
data/lib/funkify/version.rb
CHANGED
data/spec/funkify_spec.rb
CHANGED
@@ -105,20 +105,50 @@ describe Funkify do
|
|
105
105
|
end.new
|
106
106
|
end
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
describe "normal composition" do
|
109
|
+
it 'returns a new Proc when composing methods' do
|
110
|
+
(@c.negate * @c.plus_1).is_a?(Proc).should == true
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'invokes composed methods in the correct order (right-to-left)' do
|
114
|
+
(@c.negate * @c.plus_1).(5).should == -6
|
115
|
+
end
|
111
116
|
|
112
|
-
|
113
|
-
|
117
|
+
it 'can compose partially applied methods' do
|
118
|
+
(@c.add(5) * @c.mult(2)).(5).should == 15
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'can compose multiple methods' do
|
122
|
+
(@c.negate * @c.add(5) * @c.mult(5)).(5).should == -30
|
123
|
+
end
|
114
124
|
end
|
115
125
|
|
116
|
-
|
117
|
-
|
126
|
+
describe "reverse composition" do
|
127
|
+
it 'returns a new Proc when composing methods' do
|
128
|
+
(@c.negate | @c.plus_1).is_a?(Proc).should == true
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'invokes reverse-composed methods in the correct order (left-to-right)' do
|
132
|
+
(@c.negate | @c.plus_1).(5).should == -4
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'can reverse-compose partially applied methods' do
|
136
|
+
(@c.add(5) | @c.mult(2)).(5).should == 20
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'can reverse-compose multiple methods' do
|
140
|
+
(@c.negate | @c.add(5) | @c.mult(5)).(5).should == 0
|
141
|
+
end
|
118
142
|
end
|
119
143
|
|
120
|
-
|
121
|
-
|
144
|
+
describe "pass method" do
|
145
|
+
it 'passes values into a reverse-composition stream' do
|
146
|
+
(@c.pass(5) | ( @c.add(5) | @c.mult(5))).should == 50
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'passes values into a normal-composition stream' do
|
150
|
+
(@c.pass(5) | ( @c.add(5) * @c.mult(5))).should == 30
|
151
|
+
end
|
122
152
|
end
|
123
153
|
end
|
124
154
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: funkify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mair
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-07-
|
11
|
+
date: 2013-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|