functional-ruby 1.1.0 → 1.2.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +14 -12
- data/doc/memo.md +192 -0
- data/doc/pattern_matching.md +481 -0
- data/doc/protocol.md +219 -0
- data/doc/record.md +247 -0
- data/lib/functional/abstract_struct.rb +8 -8
- data/lib/functional/delay.rb +31 -38
- data/lib/functional/either.rb +48 -45
- data/lib/functional/final_struct.rb +23 -34
- data/lib/functional/final_var.rb +20 -21
- data/lib/functional/memo.rb +33 -24
- data/lib/functional/method_signature.rb +1 -2
- data/lib/functional/option.rb +7 -7
- data/lib/functional/pattern_matching.rb +12 -10
- data/lib/functional/protocol.rb +2 -4
- data/lib/functional/protocol_info.rb +5 -3
- data/lib/functional/record.rb +82 -16
- data/lib/functional/synchronization.rb +88 -0
- data/lib/functional/tuple.rb +14 -4
- data/lib/functional/type_check.rb +0 -2
- data/lib/functional/union.rb +5 -4
- data/lib/functional/value_struct.rb +5 -3
- data/lib/functional/version.rb +1 -1
- data/spec/functional/complex_pattern_matching_spec.rb +1 -2
- data/spec/functional/configuration_spec.rb +0 -2
- data/spec/functional/delay_spec.rb +0 -2
- data/spec/functional/either_spec.rb +0 -1
- data/spec/functional/final_struct_spec.rb +0 -1
- data/spec/functional/final_var_spec.rb +0 -2
- data/spec/functional/memo_spec.rb +7 -10
- data/spec/functional/option_spec.rb +0 -1
- data/spec/functional/pattern_matching_spec.rb +0 -1
- data/spec/functional/protocol_info_spec.rb +0 -2
- data/spec/functional/protocol_spec.rb +1 -3
- data/spec/functional/record_spec.rb +170 -87
- data/spec/functional/tuple_spec.rb +0 -1
- data/spec/functional/type_check_spec.rb +0 -2
- data/spec/functional/union_spec.rb +0 -1
- data/spec/functional/value_struct_spec.rb +0 -1
- metadata +14 -29
- data/doc/memo.txt +0 -192
- data/doc/pattern_matching.txt +0 -485
- data/doc/protocol.txt +0 -221
- data/doc/record.txt +0 -207
- data/doc/thread_safety.txt +0 -17
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: functional-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jerry D'Antonio
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
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: '0'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
11
|
+
date: 2015-07-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
27
13
|
description: |2
|
28
14
|
A gem for adding functional programming tools to Ruby. Inspired by Erlang, Clojure, Haskell, and Functional Java.
|
29
15
|
email: jerry.dantonio@gmail.com
|
@@ -33,20 +19,18 @@ extra_rdoc_files:
|
|
33
19
|
- README.md
|
34
20
|
- LICENSE
|
35
21
|
- CHANGELOG.md
|
36
|
-
- doc/memo.
|
37
|
-
- doc/pattern_matching.
|
38
|
-
- doc/protocol.
|
39
|
-
- doc/record.
|
40
|
-
- doc/thread_safety.txt
|
22
|
+
- doc/memo.md
|
23
|
+
- doc/pattern_matching.md
|
24
|
+
- doc/protocol.md
|
25
|
+
- doc/record.md
|
41
26
|
files:
|
42
27
|
- CHANGELOG.md
|
43
28
|
- LICENSE
|
44
29
|
- README.md
|
45
|
-
- doc/memo.
|
46
|
-
- doc/pattern_matching.
|
47
|
-
- doc/protocol.
|
48
|
-
- doc/record.
|
49
|
-
- doc/thread_safety.txt
|
30
|
+
- doc/memo.md
|
31
|
+
- doc/pattern_matching.md
|
32
|
+
- doc/protocol.md
|
33
|
+
- doc/record.md
|
50
34
|
- lib/functional.rb
|
51
35
|
- lib/functional/abstract_struct.rb
|
52
36
|
- lib/functional/delay.rb
|
@@ -60,6 +44,7 @@ files:
|
|
60
44
|
- lib/functional/protocol.rb
|
61
45
|
- lib/functional/protocol_info.rb
|
62
46
|
- lib/functional/record.rb
|
47
|
+
- lib/functional/synchronization.rb
|
63
48
|
- lib/functional/tuple.rb
|
64
49
|
- lib/functional/type_check.rb
|
65
50
|
- lib/functional/union.rb
|
@@ -96,7 +81,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
81
|
requirements:
|
97
82
|
- - ">="
|
98
83
|
- !ruby/object:Gem::Version
|
99
|
-
version:
|
84
|
+
version: 2.0.0
|
100
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
86
|
requirements:
|
102
87
|
- - ">="
|
@@ -104,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
89
|
version: '0'
|
105
90
|
requirements: []
|
106
91
|
rubyforge_project:
|
107
|
-
rubygems_version: 2.
|
92
|
+
rubygems_version: 2.4.8
|
108
93
|
signing_key:
|
109
94
|
specification_version: 4
|
110
95
|
summary: Erlang, Clojure, Haskell, and Functional Java inspired functional programming
|
data/doc/memo.txt
DELETED
@@ -1,192 +0,0 @@
|
|
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
|
data/doc/pattern_matching.txt
DELETED
@@ -1,485 +0,0 @@
|
|
1
|
-
# @!macro [new] pattern_matching
|
2
|
-
#
|
3
|
-
# ## Features
|
4
|
-
#
|
5
|
-
# * Pattern matching for instance methods.
|
6
|
-
# * Pattern matching for object constructors.
|
7
|
-
# * Parameter count matching
|
8
|
-
# * Matching against primitive values
|
9
|
-
# * Matching by class/datatype
|
10
|
-
# * Matching against specific key/vaue pairs in hashes
|
11
|
-
# * Matching against the presence of keys within hashes
|
12
|
-
# * Implicit hash for last parameter
|
13
|
-
# * Variable-length parameter lists
|
14
|
-
# * Guard clauses
|
15
|
-
# * Recursive calls to other pattern matches
|
16
|
-
# * Recursive calls to superclass pattern matches
|
17
|
-
# * Recursive calls to superclass methods
|
18
|
-
# * Dispatching to superclass methods when no match is found
|
19
|
-
# * Reasonable error messages when no match is found
|
20
|
-
#
|
21
|
-
# ## Usage
|
22
|
-
#
|
23
|
-
# First, familiarize yourself with Erlang [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching).
|
24
|
-
# This gem may not make much sense if you don't understand how Erlang dispatches functions.
|
25
|
-
#
|
26
|
-
# In the Ruby class file where you want to use pattern matching, require the *functional-ruby* gem:
|
27
|
-
#
|
28
|
-
# ```ruby
|
29
|
-
# require 'functional'
|
30
|
-
# ```
|
31
|
-
#
|
32
|
-
# Then include `Functional::PatternMatching` in your class:
|
33
|
-
#
|
34
|
-
# ```ruby
|
35
|
-
# require 'functional'
|
36
|
-
#
|
37
|
-
# class Foo
|
38
|
-
# include Functional::PatternMatching
|
39
|
-
#
|
40
|
-
# ...
|
41
|
-
#
|
42
|
-
# end
|
43
|
-
# ```
|
44
|
-
#
|
45
|
-
# You can then define functions with `defn` instead of the normal *def* statement.
|
46
|
-
# The syntax for `defn` is:
|
47
|
-
#
|
48
|
-
# ```ruby
|
49
|
-
# defn(:symbol_name_of_function, zero, or, more, parameters) { |block, arguments|
|
50
|
-
# # code to execute
|
51
|
-
# }
|
52
|
-
# ```
|
53
|
-
# You can then call your new function just like any other:
|
54
|
-
#
|
55
|
-
# ```ruby
|
56
|
-
# require 'functional/pattern_matching'
|
57
|
-
#
|
58
|
-
# class Foo
|
59
|
-
# include Functional::PatternMatching
|
60
|
-
#
|
61
|
-
# defn(:hello) {
|
62
|
-
# puts "Hello, World!"
|
63
|
-
# }
|
64
|
-
# end
|
65
|
-
#
|
66
|
-
# foo = Foo.new
|
67
|
-
# foo.hello #=> "Hello, World!"
|
68
|
-
# ```
|
69
|
-
#
|
70
|
-
# Patterns to match against are included in the parameter list:
|
71
|
-
#
|
72
|
-
# ```ruby
|
73
|
-
# defn(:greet, :male) {
|
74
|
-
# puts "Hello, sir!"
|
75
|
-
# }
|
76
|
-
#
|
77
|
-
# defn(:greet, :female) {
|
78
|
-
# puts "Hello, ma'am!"
|
79
|
-
# }
|
80
|
-
#
|
81
|
-
# ...
|
82
|
-
#
|
83
|
-
# foo.greet(:male) #=> "Hello, sir!"
|
84
|
-
# foo.greet(:female) #=> "Hello, ma'am!"
|
85
|
-
# ```
|
86
|
-
#
|
87
|
-
# If a particular method call can not be matched a *NoMethodError* is thrown with
|
88
|
-
# a reasonably helpful error message:
|
89
|
-
#
|
90
|
-
# ```ruby
|
91
|
-
# foo.greet(:unknown) #=> NoMethodError: no method `greet` matching [:unknown] found for class Foo
|
92
|
-
# foo.greet #=> NoMethodError: no method `greet` matching [] found for class Foo
|
93
|
-
# ```
|
94
|
-
#
|
95
|
-
# Parameters that are expected to exist but that can take any value are considered
|
96
|
-
# *unbound* parameters. Unbound parameters are specified by the `_` underscore
|
97
|
-
# character or `UNBOUND`:
|
98
|
-
#
|
99
|
-
# ```ruby
|
100
|
-
# defn(:greet, _) do |name|
|
101
|
-
# "Hello, #{name}!"
|
102
|
-
# end
|
103
|
-
#
|
104
|
-
# defn(:greet, UNBOUND, UNBOUND) do |first, last|
|
105
|
-
# "Hello, #{first} #{last}!"
|
106
|
-
# end
|
107
|
-
#
|
108
|
-
# ...
|
109
|
-
#
|
110
|
-
# foo.greet('Jerry') #=> "Hello, Jerry!"
|
111
|
-
# ```
|
112
|
-
#
|
113
|
-
# All unbound parameters will be passed to the block in the order they are specified in the definition:
|
114
|
-
#
|
115
|
-
# ```ruby
|
116
|
-
# defn(:greet, _, _) do |first, last|
|
117
|
-
# "Hello, #{first} #{last}!"
|
118
|
-
# end
|
119
|
-
#
|
120
|
-
# ...
|
121
|
-
#
|
122
|
-
# foo.greet('Jerry', "D'Antonio") #=> "Hello, Jerry D'Antonio!"
|
123
|
-
# ```
|
124
|
-
#
|
125
|
-
# If for some reason you don't care about one or more unbound parameters within
|
126
|
-
# the block you can use the `_` underscore character in the block parameters list
|
127
|
-
# as well:
|
128
|
-
#
|
129
|
-
# ```ruby
|
130
|
-
# defn(:greet, _, _, _) do |first, _, last|
|
131
|
-
# "Hello, #{first} #{last}!"
|
132
|
-
# end
|
133
|
-
#
|
134
|
-
# ...
|
135
|
-
#
|
136
|
-
# foo.greet('Jerry', "I'm not going to tell you my middle name!", "D'Antonio") #=> "Hello, Jerry D'Antonio!"
|
137
|
-
# ```
|
138
|
-
#
|
139
|
-
# Hash parameters can match against specific keys and either bound or unbound parameters. This allows for
|
140
|
-
# function dispatch by hash parameters without having to dig through the hash:
|
141
|
-
#
|
142
|
-
# ```ruby
|
143
|
-
# defn(:hashable, {foo: :bar}) { |opts|
|
144
|
-
# :foo_bar
|
145
|
-
# }
|
146
|
-
# defn(:hashable, {foo: _}) { |f|
|
147
|
-
# f
|
148
|
-
# }
|
149
|
-
#
|
150
|
-
# ...
|
151
|
-
#
|
152
|
-
# foo.hashable({foo: :bar}) #=> :foo_bar
|
153
|
-
# foo.hashable({foo: :baz}) #=> :baz
|
154
|
-
# ```
|
155
|
-
#
|
156
|
-
# The Ruby idiom of the final parameter being a hash is also supported:
|
157
|
-
#
|
158
|
-
# ```ruby
|
159
|
-
# defn(:options, _) { |opts|
|
160
|
-
# opts
|
161
|
-
# }
|
162
|
-
#
|
163
|
-
# ...
|
164
|
-
#
|
165
|
-
# foo.options(bar: :baz, one: 1, many: 2)
|
166
|
-
# ```
|
167
|
-
#
|
168
|
-
# As is the Ruby idiom of variable-length argument lists. The constant `ALL` as the last parameter
|
169
|
-
# will match one or more arguments and pass them to the block as an array:
|
170
|
-
#
|
171
|
-
# ```ruby
|
172
|
-
# defn(:baz, Integer, ALL) { |int, args|
|
173
|
-
# [int, args]
|
174
|
-
# }
|
175
|
-
# defn(:baz, ALL) { |args|
|
176
|
-
# args
|
177
|
-
# }
|
178
|
-
# ```
|
179
|
-
#
|
180
|
-
# Superclass polymorphism is supported as well. If an object cannot match a method
|
181
|
-
# signature it will defer to the parent class:
|
182
|
-
#
|
183
|
-
# ```ruby
|
184
|
-
# class Bar
|
185
|
-
# def greet
|
186
|
-
# return 'Hello, World!'
|
187
|
-
# end
|
188
|
-
# end
|
189
|
-
#
|
190
|
-
# class Foo < Bar
|
191
|
-
# include Functional::PatternMatching
|
192
|
-
#
|
193
|
-
# defn(:greet, _) do |name|
|
194
|
-
# "Hello, #{name}!"
|
195
|
-
# end
|
196
|
-
# end
|
197
|
-
#
|
198
|
-
# ...
|
199
|
-
#
|
200
|
-
# foo.greet('Jerry') #=> "Hello, Jerry!"
|
201
|
-
# foo.greet #=> "Hello, World!"
|
202
|
-
# ```
|
203
|
-
#
|
204
|
-
# Guard clauses in Erlang are defined with `when` clauses between the parameter list and the function body.
|
205
|
-
# In Ruby, guard clauses are defined by chaining a call to `when` onto the the `defn` call and passing
|
206
|
-
# a block. If the guard clause evaluates to true then the function will match. If the guard evaluates
|
207
|
-
# to false the function will not match and pattern matching will continue:
|
208
|
-
#
|
209
|
-
# Erlang:
|
210
|
-
#
|
211
|
-
# ```erlang
|
212
|
-
# old_enough(X) when X >= 16 -> true;
|
213
|
-
# old_enough(_) -> false.
|
214
|
-
# ```
|
215
|
-
#
|
216
|
-
# Ruby:
|
217
|
-
#
|
218
|
-
# ```ruby
|
219
|
-
# defn(:old_enough, _){ true }.when{|x| x >= 16 }
|
220
|
-
# defn(:old_enough, _){ false }
|
221
|
-
# ```
|
222
|
-
#
|
223
|
-
# ### Order Matters
|
224
|
-
#
|
225
|
-
# As with Erlang, the order of pattern matches is significant. Patterns will be matched
|
226
|
-
# *in the order declared* and the first match will be used. If a particular function call
|
227
|
-
# can be matched by more than one pattern, the *first matched pattern* will be used. It
|
228
|
-
# is the programmer's responsibility to ensure patterns are declared in the correct order.
|
229
|
-
#
|
230
|
-
# ### Blocks and Procs and Lambdas, oh my!
|
231
|
-
#
|
232
|
-
# When using this gem it is critical to remember that `defn` takes a block and
|
233
|
-
# that blocks in Ruby have special rules. There are [plenty](https://www.google.com/search?q=ruby+block+proc+lambda)
|
234
|
-
# of good tutorials on the web explaining [blocks](http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/)
|
235
|
-
# and [Procs](https://coderwall.com/p/_-_mha) and [lambdas](http://railsguru.org/2010/03/learn-ruby-procs-blocks-lambda/)
|
236
|
-
# in Ruby. Please read them. Please don't submit a bug report if you use a
|
237
|
-
# `return` statement within your `defn` and your code blows up with a
|
238
|
-
# [LocalJumpError](http://ruby-doc.org/core-2.0/LocalJumpError.html).
|
239
|
-
#
|
240
|
-
# ### Examples
|
241
|
-
#
|
242
|
-
# For more examples see the integration tests in *spec/integration_spec.rb*.
|
243
|
-
#
|
244
|
-
# #### Simple Functions
|
245
|
-
#
|
246
|
-
# This example is based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
|
247
|
-
#
|
248
|
-
# Erlang:
|
249
|
-
#
|
250
|
-
# ```erlang
|
251
|
-
# greet(male, Name) ->
|
252
|
-
# io:format("Hello, Mr. ~s!", [Name]);
|
253
|
-
# greet(female, Name) ->
|
254
|
-
# io:format("Hello, Mrs. ~s!", [Name]);
|
255
|
-
# greet(_, Name) ->
|
256
|
-
# io:format("Hello, ~s!", [Name]).
|
257
|
-
# ```
|
258
|
-
#
|
259
|
-
# Ruby:
|
260
|
-
#
|
261
|
-
# ```ruby
|
262
|
-
# require 'functional/pattern_matching'
|
263
|
-
#
|
264
|
-
# class Foo
|
265
|
-
# include Functional::PatternMatching
|
266
|
-
#
|
267
|
-
# defn(:greet, _) do |name|
|
268
|
-
# "Hello, #{name}!"
|
269
|
-
# end
|
270
|
-
#
|
271
|
-
# defn(:greet, :male, _) { |name|
|
272
|
-
# "Hello, Mr. #{name}!"
|
273
|
-
# }
|
274
|
-
# defn(:greet, :female, _) { |name|
|
275
|
-
# "Hello, Ms. #{name}!"
|
276
|
-
# }
|
277
|
-
# defn(:greet, _, _) { |_, name|
|
278
|
-
# "Hello, #{name}!"
|
279
|
-
# }
|
280
|
-
# end
|
281
|
-
# ```
|
282
|
-
#
|
283
|
-
# #### Simple Functions with Overloading
|
284
|
-
#
|
285
|
-
# This example is based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
|
286
|
-
#
|
287
|
-
# Erlang:
|
288
|
-
#
|
289
|
-
# ```erlang
|
290
|
-
# greet(Name) ->
|
291
|
-
# io:format("Hello, ~s!", [Name]).
|
292
|
-
#
|
293
|
-
# greet(male, Name) ->
|
294
|
-
# io:format("Hello, Mr. ~s!", [Name]);
|
295
|
-
# greet(female, Name) ->
|
296
|
-
# io:format("Hello, Mrs. ~s!", [Name]);
|
297
|
-
# greet(_, Name) ->
|
298
|
-
# io:format("Hello, ~s!", [Name]).
|
299
|
-
# ```
|
300
|
-
#
|
301
|
-
# Ruby:
|
302
|
-
#
|
303
|
-
# ```ruby
|
304
|
-
# require 'functional/pattern_matching'
|
305
|
-
#
|
306
|
-
# class Foo
|
307
|
-
# include Functional::PatternMatching
|
308
|
-
#
|
309
|
-
# defn(:greet, _) do |name|
|
310
|
-
# "Hello, #{name}!"
|
311
|
-
# end
|
312
|
-
#
|
313
|
-
# defn(:greet, :male, _) { |name|
|
314
|
-
# "Hello, Mr. #{name}!"
|
315
|
-
# }
|
316
|
-
# defn(:greet, :female, _) { |name|
|
317
|
-
# "Hello, Ms. #{name}!"
|
318
|
-
# }
|
319
|
-
# defn(:greet, nil, _) { |name|
|
320
|
-
# "Goodbye, #{name}!"
|
321
|
-
# }
|
322
|
-
# defn(:greet, _, _) { |_, name|
|
323
|
-
# "Hello, #{name}!"
|
324
|
-
# }
|
325
|
-
# end
|
326
|
-
# ```
|
327
|
-
#
|
328
|
-
# #### Constructor Overloading
|
329
|
-
#
|
330
|
-
# ```ruby
|
331
|
-
# require 'functional/pattern_matching'
|
332
|
-
#
|
333
|
-
# class Foo
|
334
|
-
# include Functional::PatternMatching
|
335
|
-
#
|
336
|
-
# defn(:initialize) { @name = 'baz' }
|
337
|
-
# defn(:initialize, _) {|name| @name = name.to_s }
|
338
|
-
# end
|
339
|
-
# ```
|
340
|
-
#
|
341
|
-
# #### Matching by Class/Datatype
|
342
|
-
#
|
343
|
-
# ```ruby
|
344
|
-
# require 'functional/pattern_matching'
|
345
|
-
#
|
346
|
-
# class Foo
|
347
|
-
# include Functional::PatternMatching
|
348
|
-
#
|
349
|
-
# defn(:concat, Integer, Integer) { |first, second|
|
350
|
-
# first + second
|
351
|
-
# }
|
352
|
-
# defn(:concat, Integer, String) { |first, second|
|
353
|
-
# "#{first} #{second}"
|
354
|
-
# }
|
355
|
-
# defn(:concat, String, String) { |first, second|
|
356
|
-
# first + second
|
357
|
-
# }
|
358
|
-
# defn(:concat, Integer, _) { |first, second|
|
359
|
-
# first + second.to_i
|
360
|
-
# }
|
361
|
-
# end
|
362
|
-
# ```
|
363
|
-
#
|
364
|
-
# #### Matching a Hash Parameter
|
365
|
-
#
|
366
|
-
# ```ruby
|
367
|
-
# require 'functional/pattern_matching'
|
368
|
-
#
|
369
|
-
# class Foo
|
370
|
-
# include Functional::PatternMatching
|
371
|
-
#
|
372
|
-
# defn(:hashable, {foo: :bar}) { |opts|
|
373
|
-
# # matches any hash with key :foo and value :bar
|
374
|
-
# :foo_bar
|
375
|
-
# }
|
376
|
-
# defn(:hashable, {foo: _, bar: _}) { |f, b|
|
377
|
-
# # matches any hash with keys :foo and :bar
|
378
|
-
# # passes the values associated with those keys to the block
|
379
|
-
# [f, b]
|
380
|
-
# }
|
381
|
-
# defn(:hashable, {foo: _}) { |f|
|
382
|
-
# # matches any hash with key :foo
|
383
|
-
# # passes the value associated with that key to the block
|
384
|
-
# # must appear AFTER the prior match or it will override that one
|
385
|
-
# f
|
386
|
-
# }
|
387
|
-
# defn(:hashable, {}) { ||
|
388
|
-
# # matches an empty hash
|
389
|
-
# :empty
|
390
|
-
# }
|
391
|
-
# defn(:hashable, _) { |opts|
|
392
|
-
# # matches any hash (or any other value)
|
393
|
-
# opts
|
394
|
-
# }
|
395
|
-
# end
|
396
|
-
#
|
397
|
-
# ...
|
398
|
-
#
|
399
|
-
# foo.hashable({foo: :bar}) #=> :foo_bar
|
400
|
-
# foo.hashable({foo: :baz}) #=> :baz
|
401
|
-
# foo.hashable({foo: 1, bar: 2}) #=> [1, 2]
|
402
|
-
# foo.hashable({foo: 1, baz: 2}) #=> 1
|
403
|
-
# foo.hashable({bar: :baz}) #=> {bar: :baz}
|
404
|
-
# foo.hashable({}) #=> :empty
|
405
|
-
# ```
|
406
|
-
#
|
407
|
-
# #### Variable Length Argument Lists with ALL
|
408
|
-
#
|
409
|
-
# ```ruby
|
410
|
-
# defn(:all, :one, ALL) { |args|
|
411
|
-
# args
|
412
|
-
# }
|
413
|
-
# defn(:all, :one, Integer, ALL) { |int, args|
|
414
|
-
# [int, args]
|
415
|
-
# }
|
416
|
-
# defn(:all, 1, _, ALL) { |var, args|
|
417
|
-
# [var, args]
|
418
|
-
# }
|
419
|
-
# defn(:all, ALL) { | args|
|
420
|
-
# args
|
421
|
-
# }
|
422
|
-
#
|
423
|
-
# ...
|
424
|
-
#
|
425
|
-
# foo.all(:one, 'a', 'bee', :see) #=> ['a', 'bee', :see]
|
426
|
-
# foo.all(:one, 1, 'bee', :see) #=> [1, 'bee', :see]
|
427
|
-
# foo.all(1, 'a', 'bee', :see) #=> ['a', ['bee', :see]]
|
428
|
-
# foo.all('a', 'bee', :see) #=> ['a', 'bee', :see]
|
429
|
-
# foo.all() #=> NoMethodError: no method `all` matching [] found for class Foo
|
430
|
-
# ```
|
431
|
-
#
|
432
|
-
# #### Guard Clauses
|
433
|
-
#
|
434
|
-
# These examples are based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions)
|
435
|
-
# in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
|
436
|
-
#
|
437
|
-
# Erlang:
|
438
|
-
#
|
439
|
-
# ```erlang
|
440
|
-
# old_enough(X) when X >= 16 -> true;
|
441
|
-
# old_enough(_) -> false.
|
442
|
-
#
|
443
|
-
# right_age(X) when X >= 16, X =< 104 ->
|
444
|
-
# true;
|
445
|
-
# right_age(_) ->
|
446
|
-
# false.
|
447
|
-
#
|
448
|
-
# wrong_age(X) when X < 16; X > 104 ->
|
449
|
-
# true;
|
450
|
-
# wrong_age(_) ->
|
451
|
-
# false.
|
452
|
-
# ```
|
453
|
-
#
|
454
|
-
# ```ruby
|
455
|
-
# defn(:old_enough, _){ true }.when{|x| x >= 16 }
|
456
|
-
# defn(:old_enough, _){ false }
|
457
|
-
#
|
458
|
-
# defn(:right_age, _) {
|
459
|
-
# true
|
460
|
-
# }.when{|x| x >= 16 && x <= 104 }
|
461
|
-
#
|
462
|
-
# defn(:right_age, _) {
|
463
|
-
# false
|
464
|
-
# }
|
465
|
-
#
|
466
|
-
# defn(:wrong_age, _) {
|
467
|
-
# false
|
468
|
-
# }.when{|x| x < 16 || x > 104 }
|
469
|
-
#
|
470
|
-
# defn(:wrong_age, _) {
|
471
|
-
# true
|
472
|
-
# }
|
473
|
-
# ```
|
474
|
-
#
|
475
|
-
# ## Inspiration
|
476
|
-
#
|
477
|
-
# Pattern matching has its roots in logic programming languages such as
|
478
|
-
# [Prolog](http://en.wikipedia.org/wiki/Prolog). Pattern matching is a core
|
479
|
-
# feature of the [Erlang](http://www.erlang.org/) programming language. A few
|
480
|
-
# helpful resources are:
|
481
|
-
#
|
482
|
-
# * Erlang [modules](http://erlang.org/doc/reference_manual/modules.html)
|
483
|
-
# * Erlang [pattern matching](http://erlang.org/doc/reference_manual/patterns.html)
|
484
|
-
|
485
|
-
|