functional-ruby 0.7.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -152
  3. data/doc/memo.txt +192 -0
  4. data/doc/pattern_matching.txt +485 -0
  5. data/doc/protocol.txt +221 -0
  6. data/doc/record.txt +144 -0
  7. data/doc/thread_safety.txt +8 -0
  8. data/lib/functional.rb +48 -18
  9. data/lib/functional/abstract_struct.rb +161 -0
  10. data/lib/functional/delay.rb +117 -0
  11. data/lib/functional/either.rb +222 -0
  12. data/lib/functional/memo.rb +93 -0
  13. data/lib/functional/method_signature.rb +72 -0
  14. data/lib/functional/option.rb +209 -0
  15. data/lib/functional/pattern_matching.rb +117 -100
  16. data/lib/functional/protocol.rb +157 -0
  17. data/lib/functional/protocol_info.rb +193 -0
  18. data/lib/functional/record.rb +155 -0
  19. data/lib/functional/type_check.rb +112 -0
  20. data/lib/functional/union.rb +152 -0
  21. data/lib/functional/version.rb +3 -1
  22. data/spec/functional/abstract_struct_shared.rb +154 -0
  23. data/spec/functional/complex_pattern_matching_spec.rb +205 -0
  24. data/spec/functional/configuration_spec.rb +17 -0
  25. data/spec/functional/delay_spec.rb +147 -0
  26. data/spec/functional/either_spec.rb +237 -0
  27. data/spec/functional/memo_spec.rb +207 -0
  28. data/spec/functional/option_spec.rb +292 -0
  29. data/spec/functional/pattern_matching_spec.rb +279 -276
  30. data/spec/functional/protocol_info_spec.rb +444 -0
  31. data/spec/functional/protocol_spec.rb +274 -0
  32. data/spec/functional/record_spec.rb +175 -0
  33. data/spec/functional/type_check_spec.rb +103 -0
  34. data/spec/functional/union_spec.rb +110 -0
  35. data/spec/spec_helper.rb +6 -4
  36. metadata +55 -45
  37. data/lib/functional/behavior.rb +0 -138
  38. data/lib/functional/behaviour.rb +0 -2
  39. data/lib/functional/catalog.rb +0 -487
  40. data/lib/functional/collection.rb +0 -403
  41. data/lib/functional/inflect.rb +0 -127
  42. data/lib/functional/platform.rb +0 -120
  43. data/lib/functional/search.rb +0 -132
  44. data/lib/functional/sort.rb +0 -41
  45. data/lib/functional/utilities.rb +0 -189
  46. data/md/behavior.md +0 -188
  47. data/md/catalog.md +0 -32
  48. data/md/collection.md +0 -32
  49. data/md/inflect.md +0 -32
  50. data/md/pattern_matching.md +0 -512
  51. data/md/platform.md +0 -32
  52. data/md/search.md +0 -32
  53. data/md/sort.md +0 -32
  54. data/md/utilities.md +0 -55
  55. data/spec/functional/behavior_spec.rb +0 -528
  56. data/spec/functional/catalog_spec.rb +0 -1206
  57. data/spec/functional/collection_spec.rb +0 -752
  58. data/spec/functional/inflect_spec.rb +0 -85
  59. data/spec/functional/integration_spec.rb +0 -205
  60. data/spec/functional/platform_spec.rb +0 -501
  61. data/spec/functional/search_spec.rb +0 -187
  62. data/spec/functional/sort_spec.rb +0 -61
  63. data/spec/functional/utilities_spec.rb +0 -277
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29b9fc51a00acbd56545dc7f78fa1987fb254e47
4
- data.tar.gz: f34dbe382c08f6d38c4047ddbae22f4eadfd50ae
3
+ metadata.gz: fe9f8b8bb0900055e86688c7995156bb89b5c0af
4
+ data.tar.gz: 794627125c2647c8e813b873c69f9be62bf3f206
5
5
  SHA512:
6
- metadata.gz: b740d81ce57e2e0e6136081b8a40819197979d6e4bff1caa87670e0a1491f7681b9cfa201323cf8d9418bf63277fa43443722b80df474fedc0481e38e012a3a5
7
- data.tar.gz: f94b25d9b50e5f66131bbff66134da8e6a9af06a381a82c553d3139fc09aa15030465d0edd236b7ba68cd42be1acc6096764f3b7cd64617fcf528b1c2899048a
6
+ metadata.gz: b2e54bc22942b8e2be11ca19fe1c0bd393a1a868bff7f2717d5830efea7e048b53f574763352b778170fe6e46d76d7e42bd566200843595a97142a35572a7941
7
+ data.tar.gz: 21c78e069e28438cc55dcdd3f0a2dc0892a5f36236337cd42a28dc8597d04b33f9565a3a46b03ce8c405b3b7b80ade995e2cd4f24c01642335382eaaaf917fc6
data/README.md CHANGED
@@ -1,70 +1,29 @@
1
- # Functional Ruby [![Build Status](https://secure.travis-ci.org/jdantonio/functional-ruby.png)](https://travis-ci.org/jdantonio/functional-ruby?branch=master) [![Coverage Status](https://coveralls.io/repos/jdantonio/functional-ruby/badge.png)](https://coveralls.io/r/jdantonio/functional-ruby) [![Dependency Status](https://gemnasium.com/jdantonio/functional-ruby.png)](https://gemnasium.com/jdantonio/functional-ruby)
1
+ # Functional Ruby
2
+ [![Gem Version](https://badge.fury.io/rb/functional-ruby.png)](http://badge.fury.io/rb/functional-ruby) [![Build Status](https://secure.travis-ci.org/jdantonio/functional-ruby.png)](https://travis-ci.org/jdantonio/functional-ruby?branch=master) [![Coverage Status](https://coveralls.io/repos/jdantonio/functional-ruby/badge.png)](https://coveralls.io/r/jdantonio/functional-ruby) [![Code Climate](https://codeclimate.com/github/jdantonio/functional-ruby.png)](https://codeclimate.com/github/jdantonio/functional-ruby) [![Inline docs](http://inch-ci.org/github/jdantonio/functional-ruby.png)](http://inch-ci.org/github/jdantonio/functional-ruby) [![Dependency Status](https://gemnasium.com/jdantonio/functional-ruby.png)](https://gemnasium.com/jdantonio/functional-ruby)
2
3
 
3
- A gem for adding Erlang, Clojure, and Go inspired functional programming tools to Ruby.
4
+ **A gem for adding functional programming tools to Ruby. Inspired by [Erlang](http://www.erlang.org/),
5
+ [Clojure](http://clojure.org/), and [Functional Java](http://functionaljava.org/).**
4
6
 
5
- *NOTE: As of version 0.7.0 the concurrency tools from this gem have been moved to the
6
- [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby) gem.*
7
-
8
- The project is hosted on the following sites:
9
-
10
- * [RubyGems project page](https://rubygems.org/gems/functional-ruby)
11
- * [Source code on GitHub](https://github.com/jdantonio/functional-ruby)
12
- * [YARD documentation on RubyDoc.info](http://rubydoc.info/github/jdantonio/functional-ruby/frames)
13
- * [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/functional-ruby)
14
- * [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/functional-ruby)
15
- * [Follow me on Twitter](https://twitter.com/jerrydantonio)
7
+ *NOTE: Version 1.0 is a complete rewrite. Previous versions lacked a unified
8
+ focus. Version 1.0 is a cohesive set of utilities inspired by other languages
9
+ but designed to work together in ways idiomatic to Ruby.*
16
10
 
17
11
  ## Introduction
18
12
 
19
13
  Two things I love are [Ruby](http://www.ruby-lang.org/en/) and
20
14
  [functional](https://en.wikipedia.org/wiki/Functional_programming)
21
15
  [programming](http://c2.com/cgi/wiki?FunctionalProgramming).
22
- Sadly, the former is generally not associated with the latter. Unfortunately,
23
- too many people are blinded by their belief that Ruby is an object-oriented
24
- language. I reject this assertion. Ruby is certainly object-based, since
25
- everything is an object, but entire large-scale programs can be built without ever
26
- defining a single class. But Ruby's features that support functional programming
27
- don't stop there.
28
-
29
- Ask ten different programmers to define the term "functional programming" and
30
- you will likely get ten different answers. One characteristic that will certainly
31
- be on all their lists is support for
32
- [higher](http://en.wikipedia.org/wiki/Higher-order_function)
33
- [order](http://learnyouahaskell.com/higher-order-functions)
34
- [functions](http://learnyousomeerlang.com/higher-order-functions). Put simply, a
35
- higher order function is any function that can take one or more functions as
36
- parameters and/or return a function as a result. Many functional languages,
37
- such as Erlang, Haskell, and Closure, support higher order functions. Higher order
38
- functions are a remarkable tool that can completely change the was software is
39
- designed. Most classicaly object-oriented languages do not support higher
40
- order functions. Unfortunately, Ruby does not directly support higher order
41
- functions. Thanksfully, Ruby *does* give us blocks, `proc`s, and `lambda`s.
42
- Though not strictly higher order functions, in most cases they are functionally
43
- equivalent.
44
-
45
16
  If you combine Ruby's ability to create functions sans-classes with the power
46
- of blocks/`proc`s/`lambda`s, Ruby code can follow just about every modern functional
47
- programming design paradigm. Hence, I consider Ruby to be a *multi-paradigm* language.
48
- Add to this Ruby's vast metaprogramming capabilities and Ruby is easily one of the
49
- most powerful languages in common use today.
50
-
51
- This gem is my small and humble attempt to help Ruby reach its full potential as
52
- a highly performant, functional programming language. Virtually every function in
53
- this library takes a block parameter. Some allow a block plus one or more `proc`
54
- arguments. Most operate *on* data structures rather than being buried *in* data
55
- structures. Finally, several of the tools in this library are Ruby implementations
56
- of some of my favorite features from other functional programming languages. Not
57
- every function is pure, but functions with side effects are easy to spot because
58
- they almost always have names that end in an exclamation point.
59
-
60
- My hope is that this gem will help Ruby programmers explore Ruby as a functional
61
- language and improve their code in ways our object oriented brethern never
62
- dreamed possible.
17
+ of blocks, `proc`, and `lambda`, Ruby code can follow just about every modern functional
18
+ programming design paradigm. Add to this Ruby's vast metaprogramming capabilities
19
+ and Ruby is easily one of the most powerful languages in common use today.
63
20
 
64
21
  ### Goals
65
22
 
66
- My goal is to implement various functional programming patterns in Ruby. Specifically:
23
+ Our goal is to implement various functional programming patterns in Ruby. Specifically:
67
24
 
25
+ * Be an 'unopinionated' toolbox that provides useful utilities without debating which is better or why
26
+ * Remain free of external gem dependencies
68
27
  * Stay true to the spirit of the languages providing inspiration
69
28
  * But implement in a way that makes sense for Ruby
70
29
  * Keep the semantics as idiomatic Ruby as possible
@@ -73,27 +32,26 @@ My goal is to implement various functional programming patterns in Ruby. Specifi
73
32
  * Keep everything small
74
33
  * Be as fast as reasonably possible
75
34
 
76
- ## Features (and Documentation)
77
-
78
- Several features from Erlang, Go, and Clojure have been implemented thus far:
79
-
80
- * Interface specifications with Erlang-style [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
81
- * A [Catalog](https://github.com/jdantonio/functional-ruby/blob/master/md/catalog.md) class for managing sets of key/value pairs in a manner similar to Erlang's [proplists](http://www.erlang.org/doc/man/proplists.html)
82
- * A toolkit of [collection](https://github.com/jdantonio/functional-ruby/blob/master/md/collection.md) utilities for operating on list-like data structures
83
- * A set of string [inflections](https://github.com/jdantonio/functional-ruby/blob/master/md/inflect.md) borrowed from [Active Support](http://guides.rubyonrails.org/active_support_core_extensions.html#inflections)
84
- * Function overloading with Erlang-style [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
85
- * Tools for introspecting the runtime [Platform](https://github.com/jdantonio/functional-ruby/blob/master/md/platform.md) for information about the operating system and Ruby version
86
- * [Search](https://github.com/jdantonio/functional-ruby/blob/master/md/search.md) and [sort](https://github.com/jdantonio/functional-ruby/blob/master/md/sort.md) algorithms like you remember from your algorithms class, but with a functional twist
87
- * Several useful functional [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
35
+ ## Features
88
36
 
89
- ### Is it any good?
37
+ Complete API documentation can be found at [Rubydoc.info](http://rubydoc.info/github/jdantonio/functional-ruby/master/frames).
90
38
 
91
- [Yes](http://news.ycombinator.com/item?id=3067434)
39
+ * Protocol specifications inspired by Clojure [protocol](http://clojure.org/protocols),
40
+ Erlang [behavior](http://www.erlang.org/doc/design_principles/des_princ.html#id60128),
41
+ and Objective-C [protocol](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html)
42
+ * Function overloading with Erlang-style [function](http://erlang.org/doc/reference_manual/functions.html)
43
+ [pattern matching](http://erlang.org/doc/reference_manual/patterns.html)
44
+ * Simple, immutable data structures, such as *record* and *union*, inspired by
45
+ [Clojure](http://clojure.org/datatypes), [Erlang](http://www.erlang.org/doc/reference_manual/records.html),
46
+ and [others](http://en.wikipedia.org/wiki/Union_type)
47
+ * `Either` and `Option` classes based on [Functional Java](http://functionaljava.org/) and [Haskell](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Either.html)
48
+ * [Memoization](http://en.wikipedia.org/wiki/Memoization) of class methods based on Clojure [memoize](http://clojuredocs.org/clojure_core/clojure.core/memoize)
49
+ * Lazy execution with a `Delay` class based on Clojure [delay](http://clojuredocs.org/clojure_core/clojure.core/delay)
92
50
 
93
- ### Supported Ruby versions
51
+ ### Supported Ruby Versions
94
52
 
95
- MRI 1.9.2, 1.9.3, 2.0, 2.1, and JRuby (1.9 mode). This library is pure Ruby and has no gem dependencies.
96
- It should be fully compatible with any Ruby interpreter that is 1.9.x compliant.
53
+ MRI 2.0 and higher, JRuby (1.9 mode), and Rubinius 2.x. This library is pure Ruby and has no gem dependencies.
54
+ It should be fully compatible with any interpreter that is compliant with Ruby 2.0 or newer.
97
55
 
98
56
  ### Install
99
57
 
@@ -115,110 +73,92 @@ Once you've installed the gem you must `require` it in your project:
115
73
  require 'functional'
116
74
  ```
117
75
 
118
- ### Examples
76
+ ## Examples
119
77
 
120
- For complete examples, see the specific documentation linked above. Below are a
121
- few examples to whet your appetite.
78
+ Specifying a protocol:
122
79
 
123
- #### Pattern Matching (Erlang)
80
+ ```ruby
81
+ Functional::SpecifyProtocol(:Name) do
82
+ attr_accessor :first
83
+ attr_accessor :middle
84
+ attr_accessor :last
85
+ attr_accessor :suffix
86
+ end
87
+ ```
124
88
 
125
- Documentation: [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
89
+ Pattern matching using protocols, type checking, and other options:
126
90
 
127
91
  ```ruby
128
- require 'functional/pattern_matching'
129
-
130
92
  class Foo
131
- include PatternMatching
93
+ include Functional::PatternMatching
94
+ include Functional::Protocol
95
+ include Functional::TypeChecking
132
96
 
133
- defn(:greet, :male) {
134
- puts "Hello, sir!"
97
+ def greet
98
+ return 'Hello, World!'
99
+ end
100
+
101
+ defn(:greet, _) do |name|
102
+ "Hello, #{name}!"
103
+ end
104
+
105
+ defn(:greet, _) { |name|
106
+ "Pleased to meet you, #{name.fulle_name}!"
107
+ }.when {|name| Type?(CustomerModel, ClientModel) }
108
+
109
+ defn(:greet, _) { |name|
110
+ "Hello, #{name.first} #{name.last}!"
111
+ }.when {|name| Satisfy?(:Name) }
112
+
113
+ defn(:greet, :male, _) { |name|
114
+ "Hello, Mr. #{name}!"
135
115
  }
136
116
 
137
- defn(:greet, :female) {
138
- puts "Hello, ma'am!"
117
+ defn(:greet, :female, _) { |name|
118
+ "Hello, Ms. #{name}!"
139
119
  }
140
- end
141
120
 
142
- foo = Foo.new
143
- foo.greet(:male) #=> "Hello, sir!"
144
- foo.greet(:female) #=> "Hello, ma'am!"
145
- ```
121
+ defn(:greet, nil, _) { |name|
122
+ "Goodbye, #{name}!"
123
+ }
146
124
 
147
- #### Behavior (Erlang)
125
+ defn(:greet, _, _) { |_, name|
126
+ "Hello, #{name}!"
127
+ }
128
+ end
129
+ ```
148
130
 
149
- Documentation: [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
131
+ Memoization:
150
132
 
151
133
  ```ruby
152
- require 'functional/behavior'
153
-
154
- behaviour_info(:gen_foo, foo: 0, self_bar: 1)
134
+ class Factors
135
+ include Functional::Memo
155
136
 
156
- class Foo
157
- behavior(:gen_foo)
158
-
159
- def foo
160
- return 'foo/0'
137
+ def self.sum_of(number)
138
+ of(number).reduce(:+)
161
139
  end
162
140
 
163
- def self.bar(one, &block)
164
- return 'bar/1'
141
+ def self.of(number)
142
+ (1..number).select {|i| factor?(number, i)}
165
143
  end
166
- end
167
144
 
168
- foo = Foo.new
145
+ def self.factor?(number, potential)
146
+ number % potential == 0
147
+ end
169
148
 
170
- Foo.behaves_as? :gen_foo #=> true
171
- foo.behaves_as?(:gen_foo) #=> true
172
- foo.behaves_as?(:bogus) #=> false
173
- 'foo'.behaves_as? :gen_foo #=> false
149
+ memoize(:sum_of)
150
+ memoize(:of)
151
+ end
174
152
  ```
175
153
 
176
- #### Utilities
177
-
178
- Documentation: [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
179
-
180
- ```ruby
181
- Infinity #=> Infinity
182
- NaN #=> NaN
183
-
184
- repl? #=> true when called under irb, pry, bundle console, or rails console
185
-
186
- safe(1, 2){|a, b| a + b} #=> 3
187
- safe{ eval 'puts "Hello World!"' } #=> SecurityError: Insecure operation
154
+ ## Contributing
188
155
 
189
- pp_s [1,2,3,4] #=> "[1, 2, 3, 4]\n" props to Rha7
156
+ 1. Fork it
157
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
158
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
159
+ 4. Push to the branch (`git push origin my-new-feature`)
160
+ 5. Create new Pull Request
190
161
 
191
- delta(-1, 1) #=> 2
192
- delta({count: -1}, {count: 1}){|item| item[:count]} #=> 2
193
-
194
- # And many more!
195
- ```
162
+ ## License and Copyright
196
163
 
197
- ## Copyright
198
-
199
- *Functional Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
200
- It is free software and may be redistributed under the terms specified in the LICENSE file.
201
-
202
- ## License
203
-
204
- Released under the MIT license.
205
-
206
- http://www.opensource.org/licenses/mit-license.php
207
-
208
- > Permission is hereby granted, free of charge, to any person obtaining a copy
209
- > of this software and associated documentation files (the "Software"), to deal
210
- > in the Software without restriction, including without limitation the rights
211
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
212
- > copies of the Software, and to permit persons to whom the Software is
213
- > furnished to do so, subject to the following conditions:
214
- >
215
- > The above copyright notice and this permission notice shall be included in
216
- > all copies or substantial portions of the Software.
217
- >
218
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
219
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
220
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
221
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
222
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
223
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
224
- > THE SOFTWARE.
164
+ *Functional Ruby* is free software released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,192 @@
1
+ # @!macro [new] memoize
2
+ #
3
+ # ## Rationale
4
+ #
5
+ # Many computational operations take a significant amount of time and/or use
6
+ # an inordinate amount of resources. If subsequent calls to that function with
7
+ # the same parameters are guaranteed to return the same result, caching the
8
+ # result can lead to significant performance improvements. The process of
9
+ # caching such calls is called
10
+ # [memoization](http://en.wikipedia.org/wiki/Memoization).
11
+ #
12
+ # ## Declaration
13
+ #
14
+ # Using memoization requires two simple steps: including the
15
+ # `Functional::Memo` module within a class or module and calling the `memoize`
16
+ # function to enable memoization on one or more methods.
17
+ #
18
+ # ```ruby
19
+ # Module EvenNumbers
20
+ # include Functional::Memoize
21
+ #
22
+ # self.first(n)
23
+ # (2..n).select{|i| i % 2 == 0 }
24
+ # end
25
+ #
26
+ # memoize :first
27
+ # end
28
+ # ```
29
+ #
30
+ # When a function is memoized an internal cache is created that maps arguments
31
+ # to return values. When the function is called the arguments are checked
32
+ # against the cache. If the args are found the method is not called and the
33
+ # cached result is returned instead.
34
+ #
35
+ # ## Ramifications
36
+ #
37
+ # Memoizing long-running methods can lead to significant performance
38
+ # advantages. But there is a trade-off. Memoization may greatly increase the
39
+ # memory footprint of the application. The memo cache itself takes memory. The
40
+ # more arg/result pairs stored in the cache, the more memory is consumed.
41
+ #
42
+ # ### Cache Size Options
43
+ #
44
+ # To help control the size of the cache, a limit can be placed on the number
45
+ # of items retained in the cache. The `:at_most` option, when given, indicates
46
+ # the maximum size of the cache. Once the maximum cache size is reached, calls
47
+ # to to the method with uncached args will still result in the method being
48
+ # called, but the results will not be cached.
49
+ #
50
+ # ```ruby
51
+ # Module EvenNumbers
52
+ # include Functional::Memoize
53
+ #
54
+ # self.first(n)
55
+ # (2..n).select{|i| i % 2 == 0 }
56
+ # end
57
+ #
58
+ # memoize :first, at_most: 1000
59
+ # end
60
+ # ```
61
+ #
62
+ # There is no way to predict in advance what the proper cache size is, or if
63
+ # it should be restricted at all. Only performance testing under realistic
64
+ # conditions or profiling of a running system can provide guidance.
65
+ #
66
+ # ## Restrictions
67
+ #
68
+ # Not all methods are good candidates for memoization.Only methods that are
69
+ # [idempotent](http://en.wikipedia.org/wiki/Idempotence), [referentially
70
+ # transparent](http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)),
71
+ # and free of [side effects](http://en.wikipedia.org/wiki/Side_effect_(computer_science))
72
+ # can be effectively memoized. If a method creates side effects, such as
73
+ # writing to a log, only the first call to the method will create those side
74
+ # effects. Subsequent calls will return the cached value without calling the
75
+ # method.
76
+ #
77
+ # Similarly, methods which change internal state will only update the state on
78
+ # the initial call. Later calls will not result in state changes, they will
79
+ # only return the original result. Subsequently, instance methods cannot be
80
+ # memoized. Objects are, by definition, stateful. Method calls exist for the
81
+ # purpose of changing or using the internal state of the object. Such methods
82
+ # cannot be effectively memoized; it would require the internal state of the
83
+ # object to be cached and checked as well.
84
+ #
85
+ # Block parameters pose a similar problem. Block parameters are inherently
86
+ # stateful (they are closures which capture the enclosing context). And there
87
+ # is no way to check the state of the block along with the args to determine
88
+ # if the cached value should be used. Subsequently, and method call which
89
+ # includes a block will result in the cache being completely skipped. The base
90
+ # method will be called and the result will not be cached. This behavior will
91
+ # occur even when the given method was not programmed to accept a block
92
+ # parameter. Ruby will capture any block passed to any method and make it
93
+ # available to the method even when not documented as a formal parameter or
94
+ # used in the method. This has the interesting side effect of allowing the
95
+ # memo cache to be skipped on any method call, simply be passing a block
96
+ # parameter.
97
+ #
98
+ # ```ruby
99
+ # EvenNumbers.first(100) # causes the result to be cached
100
+ # EvenNumbers.first(100) # retrieves the previous result from the cache
101
+ # EvenNumbers.first(100){ nil } # skips the memo cache and calls the method again
102
+ # ```
103
+ #
104
+ # ### Complete Example
105
+ #
106
+ # The following example is borrowed from the book [Functional Thinking](http://shop.oreilly.com/product/0636920029687.do)
107
+ # by Neal Ford. In his book he shows an example of memoization in Groovy by
108
+ # summing factors of a given number. This is a great example because it
109
+ # exhibits all the criteria that make a method a good memoization candidate:
110
+ #
111
+ # * Idempotence
112
+ # * Referential transparency
113
+ # * Stateless
114
+ # * Free of side effect
115
+ # * Computationally expensive (for large numbers)
116
+ #
117
+ # The following code implements Ford's algorithms in Ruby, then memoizes two
118
+ # key methods. The Ruby code:
119
+ #
120
+ # ```ruby
121
+ # require 'functional'
122
+ #
123
+ # class Factors
124
+ # include Functional::Memo
125
+ #
126
+ # def self.sum_of(number)
127
+ # of(number).reduce(:+)
128
+ # end
129
+ #
130
+ # def self.of(number)
131
+ # (1..number).select {|i| factor?(number, i)}
132
+ # end
133
+ #
134
+ # def self.factor?(number, potential)
135
+ # number % potential == 0
136
+ # end
137
+ #
138
+ # def self.perfect?(number)
139
+ # sum_of(number) == 2 * number
140
+ # end
141
+ #
142
+ # def self.abundant?(number)
143
+ # sum_of(number) > 2 * number
144
+ # end
145
+ #
146
+ # def self.deficient?(number)
147
+ # sum_of(number) < 2 * number
148
+ # end
149
+ #
150
+ # memoize(:sum_of)
151
+ # memoize(:of)
152
+ # end
153
+ # ```
154
+ #
155
+ # This code was tested in IRB using MRI 2.1.2 on a MacBook Pro. The `sum_of`
156
+ # method was called three times against the number 10,000,000 and the
157
+ # benchmark results of each run were captured. The test code:
158
+ #
159
+ # ```ruby
160
+ # require 'benchmark'
161
+ #
162
+ # 3.times do
163
+ # stats = Benchmark.measure do
164
+ # Factors.sum_of(10_000_000)
165
+ # end
166
+ # puts stats
167
+ # end
168
+ # ```
169
+ #
170
+ # The results of the benchmarking are very revealing. The first run took over
171
+ # a second to calculate the results. The two subsequent runs, which retrieved
172
+ # the previous result from the memo cache, were nearly instantaneous:
173
+ #
174
+ # ```
175
+ # 1.080000 0.000000 1.080000 ( 1.077524)
176
+ # 0.000000 0.000000 0.000000 ( 0.000033)
177
+ # 0.000000 0.000000 0.000000 ( 0.000008)
178
+ # ```
179
+ #
180
+ # The same code run on the same computer using JRuby 1.7.12 exhibited similar
181
+ # results:
182
+ #
183
+ # ```
184
+ # 1.800000 0.030000 1.830000 ( 1.494000)
185
+ # 0.000000 0.000000 0.000000 ( 0.000000)
186
+ # 0.000000 0.000000 0.000000 ( 0.000000)
187
+ # ```
188
+ #
189
+ # ## Inspiration
190
+ #
191
+ # * [Memoization](http://en.wikipedia.org/wiki/Memoization) at Wikipedia
192
+ # * Clojure [memoize](http://clojuredocs.org/clojure_core/clojure.core/memoize) function