fp 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 -0
- data/.gitignore +10 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +186 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/fp.gemspec +25 -0
- data/fp.rb +59 -0
- data/lib/fp.rb +5 -0
- data/lib/fp/fn.rb +59 -0
- data/lib/fp/version.rb +3 -0
- metadata +112 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: baccf0eb49c70cddf93cee6e0812f39ab9fee72c
|
|
4
|
+
data.tar.gz: 724ce47500b4a7933c1d268b38d7bc85b113907e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0cb3f3bc3129649ba39a05e740a887e44716a4263946980a7d2c792ef35015631081e32f95d06bc0c363edaaee774df2b3fe21ca28fb3bccb170fdf441e74ad4
|
|
7
|
+
data.tar.gz: 7700f5ef033f71d398822bf022766b42d5d5c0b7a9ba948c18241f9e71f32a7f7396d7b3d00d354b13b7b7fed111ceeb5d3d9d8c36063d4917ff64e3e98f0d6d
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# FP.rb
|
|
2
|
+
|
|
3
|
+
This Gem provides functionality that makes it easier to use functional patterns in Ruby.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'fp'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install fp
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### The FP::Fn Base Class
|
|
24
|
+
|
|
25
|
+
The FP::Fn class simulates closure-like behavior with classes and familiar class syntax. It eliminates
|
|
26
|
+
the boilerplate that is otherwise necessary when using classes in a functional style.
|
|
27
|
+
|
|
28
|
+
This class provides a way to emulate familiar patterns from functional languages. In most functional
|
|
29
|
+
languages, it is common to define functions within the body of other functions to help
|
|
30
|
+
with computation.
|
|
31
|
+
These inner functions have access to
|
|
32
|
+
the arguments that outer function are called with, as these are part of the environment in which they are defined.
|
|
33
|
+
|
|
34
|
+
Lets look at an example:
|
|
35
|
+
Imagine that for some reason you had to implement exponentiation yourself, and only had addition to work with.
|
|
36
|
+
Since multiplication can be define as a series of additions, and exponentiation is defined as a series of
|
|
37
|
+
multiplications, you can use addition and recursive function calls to solve the problem.
|
|
38
|
+
|
|
39
|
+
In Ruby, the closest example would look something like this:
|
|
40
|
+
|
|
41
|
+
````ruby
|
|
42
|
+
def slow_exponentiate(x, y)
|
|
43
|
+
multiply_x = ->(num_left) {
|
|
44
|
+
if num_left > 0
|
|
45
|
+
x + multiply_x.(num_left - 1)
|
|
46
|
+
else
|
|
47
|
+
0
|
|
48
|
+
end
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exponentiate_x = ->(num_left) {
|
|
52
|
+
if num_left > 0
|
|
53
|
+
multiply_x.(exponentiate_x.(num_left - 1))
|
|
54
|
+
else
|
|
55
|
+
1
|
|
56
|
+
end
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exponentiate_x.(y)
|
|
60
|
+
end
|
|
61
|
+
````
|
|
62
|
+
In Haskell, this solution would look something like this:
|
|
63
|
+
|
|
64
|
+
````haskell
|
|
65
|
+
slowExponentiate x y =
|
|
66
|
+
let exponentiate_x 0 = 1
|
|
67
|
+
exponentiate_x num_left = multiply_x(exponentiate_x(num_left - 1))
|
|
68
|
+
multiply_x 0 = 0
|
|
69
|
+
multiply_x num_left = x + multiply_x(num_left - 1)
|
|
70
|
+
in exponentiate_x y
|
|
71
|
+
````
|
|
72
|
+
|
|
73
|
+
Here is similar code in JavaScript:
|
|
74
|
+
|
|
75
|
+
````javascript
|
|
76
|
+
function slowExponentiate(x, y) {
|
|
77
|
+
var multiplyX = function(num_left) {
|
|
78
|
+
if(num_left > 0) {
|
|
79
|
+
return x + multiplyX(num_left - 1);
|
|
80
|
+
} else {
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
var exponentiateX = function(num_left){
|
|
86
|
+
if(num_left > 0) {
|
|
87
|
+
multiplyX(exponentiateX(num_left - 1);
|
|
88
|
+
} else {
|
|
89
|
+
return 0
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return exponentiate(y);
|
|
94
|
+
}
|
|
95
|
+
````
|
|
96
|
+
|
|
97
|
+
The thing is, many would consider this code to be ugly.
|
|
98
|
+
The FP::Fn gives you the best of both worlds: the familiar syntax of
|
|
99
|
+
Ruby with this powerful functional idiom.
|
|
100
|
+
|
|
101
|
+
````ruby
|
|
102
|
+
class SlowExponentiate < FP::Fn
|
|
103
|
+
arguments :x, :y, by: :position
|
|
104
|
+
|
|
105
|
+
def call(x, y)
|
|
106
|
+
exponentiate_x(y)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
def exponentiate_x(num_left)
|
|
111
|
+
if num_left > 0
|
|
112
|
+
multiply_x(exponentiate_x(num_left - 1))
|
|
113
|
+
else
|
|
114
|
+
1
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def multiply_x(num_left)
|
|
119
|
+
if num_left > 0
|
|
120
|
+
x + multiply_x(num_left-1)
|
|
121
|
+
else
|
|
122
|
+
0
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
SlowExponentiate.(2, 0) # => 1
|
|
128
|
+
SlowExponentiate.(2, 1) # => 2
|
|
129
|
+
SlowExponentiate.(2, 8) # => 256
|
|
130
|
+
````
|
|
131
|
+
|
|
132
|
+
What *exactly* does it do? Well, it:
|
|
133
|
+
|
|
134
|
+
- Creates class methods that correspond to public instance methods that
|
|
135
|
+
creates a `new` instance and calls the method on that instance, returning the result.
|
|
136
|
+
|
|
137
|
+
- Provides an `arguments` class method to specify the arguments that will become part of this "closure".
|
|
138
|
+
|
|
139
|
+
Doing the same thing with classes normally would look something like this:
|
|
140
|
+
|
|
141
|
+
````ruby
|
|
142
|
+
class SlowExponentiate
|
|
143
|
+
attr_reader :x, :y
|
|
144
|
+
|
|
145
|
+
def initialize(x, y)
|
|
146
|
+
@x = x
|
|
147
|
+
@y = y
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def call
|
|
151
|
+
exponentiate_x(y)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
def exponentiate_x(num_left)
|
|
156
|
+
if num_left > 0
|
|
157
|
+
multiply_x(exponentiate_x(num_left - 1))
|
|
158
|
+
else
|
|
159
|
+
1
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def multiply_x(num_left)
|
|
164
|
+
if num_left > 0
|
|
165
|
+
x + multiply_x(num_left-1)
|
|
166
|
+
else
|
|
167
|
+
0
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
SlowExponentiate.new(2, 8).call # => 256
|
|
173
|
+
````
|
|
174
|
+
|
|
175
|
+
Over time, the initialization overhead adds up, slowly making it harder to add more functionality to your code. Additionally, there are many different ways
|
|
176
|
+
to write above code in pure Ruby, each with different tradeoffs.
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
181
|
+
|
|
182
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/joelmccracken/fp.rb.
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "fp"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/fp.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'fp/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "fp"
|
|
8
|
+
spec.version = FP::VERSION
|
|
9
|
+
spec.authors = ["Joel McCracken"]
|
|
10
|
+
spec.email = ["mccracken.joel@gmail.com"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{A little library for functional programming}
|
|
13
|
+
spec.homepage = "https://github.com/joelmccracken/fp.rb"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
+
spec.bindir = "exe"
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
24
|
+
spec.add_development_dependency "pry", "~> 0.10.3"
|
|
25
|
+
end
|
data/fp.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module FP
|
|
2
|
+
class Fn
|
|
3
|
+
def self.method_added(method_name, &block)
|
|
4
|
+
is_attr_reader = (@arguments || []).include?(method_name)
|
|
5
|
+
is_public_method = public_instance_methods.include?(method_name)
|
|
6
|
+
|
|
7
|
+
if !is_attr_reader && is_public_method
|
|
8
|
+
eval <<-EOS
|
|
9
|
+
def self.#{method_name}(*args, &block)
|
|
10
|
+
new(*args, &block).#{method_name}
|
|
11
|
+
end
|
|
12
|
+
EOS
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.arguments(*args)
|
|
17
|
+
maybe_spec = args.last
|
|
18
|
+
|
|
19
|
+
if maybe_spec.is_a?(Hash)
|
|
20
|
+
@arguments = args[0..-2]
|
|
21
|
+
@spec = args.last
|
|
22
|
+
else
|
|
23
|
+
@arguments = args
|
|
24
|
+
@spec = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
by_name = @spec && @spec[:by] == :name
|
|
28
|
+
by_position = @spec && [:pos, :position].include?(@spec[:by])
|
|
29
|
+
no_spec = !@spec
|
|
30
|
+
|
|
31
|
+
attr_readers = @arguments.map { |arg| ":#{arg}"}.join ", "
|
|
32
|
+
|
|
33
|
+
eval "attr_reader #{attr_readers}"
|
|
34
|
+
|
|
35
|
+
attr_assignments = @arguments.map { |arg|
|
|
36
|
+
"@#{arg} = #{arg}"
|
|
37
|
+
}.join("\n")
|
|
38
|
+
|
|
39
|
+
if by_position || no_spec
|
|
40
|
+
arguments_string = @arguments.join(", ")
|
|
41
|
+
|
|
42
|
+
eval <<-EOS
|
|
43
|
+
def initialize(#{arguments_string})
|
|
44
|
+
#{attr_assignments}
|
|
45
|
+
end
|
|
46
|
+
EOS
|
|
47
|
+
elsif by_name
|
|
48
|
+
arguments_string = @arguments.map{ |arg| "#{arg}:"}.join(", ")
|
|
49
|
+
eval <<-EOS
|
|
50
|
+
def initialize(#{arguments_string})
|
|
51
|
+
#{attr_assignments}
|
|
52
|
+
end
|
|
53
|
+
EOS
|
|
54
|
+
else
|
|
55
|
+
raise "Unknown value (#{@spec && @spec[:by]}) for arguments spec :by parameter; valid options are :name and :position"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/fp.rb
ADDED
data/lib/fp/fn.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module FP
|
|
2
|
+
class Fn
|
|
3
|
+
def self.method_added(method_name, &block)
|
|
4
|
+
is_attr_reader = (@arguments || []).include?(method_name)
|
|
5
|
+
is_public_method = public_instance_methods.include?(method_name)
|
|
6
|
+
|
|
7
|
+
if !is_attr_reader && is_public_method
|
|
8
|
+
eval <<-EOS
|
|
9
|
+
def self.#{method_name}(*args, &block)
|
|
10
|
+
new(*args, &block).#{method_name}
|
|
11
|
+
end
|
|
12
|
+
EOS
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.arguments(*args)
|
|
17
|
+
maybe_spec = args.last
|
|
18
|
+
|
|
19
|
+
if maybe_spec.is_a?(Hash)
|
|
20
|
+
@arguments = args[0..-2]
|
|
21
|
+
@spec = args.last
|
|
22
|
+
else
|
|
23
|
+
@arguments = args
|
|
24
|
+
@spec = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
by_name = @spec && @spec[:by] == :name
|
|
28
|
+
by_position = @spec && [:pos, :position].include?(@spec[:by])
|
|
29
|
+
no_spec = !@spec
|
|
30
|
+
|
|
31
|
+
attr_readers = @arguments.map { |arg| ":#{arg}"}.join ", "
|
|
32
|
+
|
|
33
|
+
eval "attr_reader #{attr_readers}"
|
|
34
|
+
|
|
35
|
+
attr_assignments = @arguments.map { |arg|
|
|
36
|
+
"@#{arg} = #{arg}"
|
|
37
|
+
}.join("\n")
|
|
38
|
+
|
|
39
|
+
if by_position || no_spec
|
|
40
|
+
arguments_string = @arguments.join(", ")
|
|
41
|
+
|
|
42
|
+
eval <<-EOS
|
|
43
|
+
def initialize(#{arguments_string})
|
|
44
|
+
#{attr_assignments}
|
|
45
|
+
end
|
|
46
|
+
EOS
|
|
47
|
+
elsif by_name
|
|
48
|
+
arguments_string = @arguments.map{ |arg| "#{arg}:"}.join(", ")
|
|
49
|
+
eval <<-EOS
|
|
50
|
+
def initialize(#{arguments_string})
|
|
51
|
+
#{attr_assignments}
|
|
52
|
+
end
|
|
53
|
+
EOS
|
|
54
|
+
else
|
|
55
|
+
raise "Unknown value (#{@spec && @spec[:by]}) for arguments spec :by parameter; valid options are :name and :position"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/fp/version.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: fp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Joel McCracken
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-04-06 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.11'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.11'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '5.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '5.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: pry
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 0.10.3
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 0.10.3
|
|
69
|
+
description:
|
|
70
|
+
email:
|
|
71
|
+
- mccracken.joel@gmail.com
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".gitignore"
|
|
77
|
+
- ".travis.yml"
|
|
78
|
+
- Gemfile
|
|
79
|
+
- README.md
|
|
80
|
+
- Rakefile
|
|
81
|
+
- bin/console
|
|
82
|
+
- bin/setup
|
|
83
|
+
- fp.gemspec
|
|
84
|
+
- fp.rb
|
|
85
|
+
- lib/fp.rb
|
|
86
|
+
- lib/fp/fn.rb
|
|
87
|
+
- lib/fp/version.rb
|
|
88
|
+
homepage: https://github.com/joelmccracken/fp.rb
|
|
89
|
+
licenses:
|
|
90
|
+
- MIT
|
|
91
|
+
metadata: {}
|
|
92
|
+
post_install_message:
|
|
93
|
+
rdoc_options: []
|
|
94
|
+
require_paths:
|
|
95
|
+
- lib
|
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0'
|
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '0'
|
|
106
|
+
requirements: []
|
|
107
|
+
rubyforge_project:
|
|
108
|
+
rubygems_version: 2.4.8
|
|
109
|
+
signing_key:
|
|
110
|
+
specification_version: 4
|
|
111
|
+
summary: A little library for functional programming
|
|
112
|
+
test_files: []
|