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.
@@ -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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .DS_Store
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.4
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fp.gemspec
4
+ gemspec
@@ -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.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :spec
@@ -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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,5 @@
1
+ require "fp/version"
2
+ require "fp/fn"
3
+
4
+ module FP
5
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module FP
2
+ VERSION = "0.1.0"
3
+ end
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: []