functional-ruby 0.7.7 → 1.0.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.
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