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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +14 -12
  4. data/doc/memo.md +192 -0
  5. data/doc/pattern_matching.md +481 -0
  6. data/doc/protocol.md +219 -0
  7. data/doc/record.md +247 -0
  8. data/lib/functional/abstract_struct.rb +8 -8
  9. data/lib/functional/delay.rb +31 -38
  10. data/lib/functional/either.rb +48 -45
  11. data/lib/functional/final_struct.rb +23 -34
  12. data/lib/functional/final_var.rb +20 -21
  13. data/lib/functional/memo.rb +33 -24
  14. data/lib/functional/method_signature.rb +1 -2
  15. data/lib/functional/option.rb +7 -7
  16. data/lib/functional/pattern_matching.rb +12 -10
  17. data/lib/functional/protocol.rb +2 -4
  18. data/lib/functional/protocol_info.rb +5 -3
  19. data/lib/functional/record.rb +82 -16
  20. data/lib/functional/synchronization.rb +88 -0
  21. data/lib/functional/tuple.rb +14 -4
  22. data/lib/functional/type_check.rb +0 -2
  23. data/lib/functional/union.rb +5 -4
  24. data/lib/functional/value_struct.rb +5 -3
  25. data/lib/functional/version.rb +1 -1
  26. data/spec/functional/complex_pattern_matching_spec.rb +1 -2
  27. data/spec/functional/configuration_spec.rb +0 -2
  28. data/spec/functional/delay_spec.rb +0 -2
  29. data/spec/functional/either_spec.rb +0 -1
  30. data/spec/functional/final_struct_spec.rb +0 -1
  31. data/spec/functional/final_var_spec.rb +0 -2
  32. data/spec/functional/memo_spec.rb +7 -10
  33. data/spec/functional/option_spec.rb +0 -1
  34. data/spec/functional/pattern_matching_spec.rb +0 -1
  35. data/spec/functional/protocol_info_spec.rb +0 -2
  36. data/spec/functional/protocol_spec.rb +1 -3
  37. data/spec/functional/record_spec.rb +170 -87
  38. data/spec/functional/tuple_spec.rb +0 -1
  39. data/spec/functional/type_check_spec.rb +0 -2
  40. data/spec/functional/union_spec.rb +0 -1
  41. data/spec/functional/value_struct_spec.rb +0 -1
  42. metadata +14 -29
  43. data/doc/memo.txt +0 -192
  44. data/doc/pattern_matching.txt +0 -485
  45. data/doc/protocol.txt +0 -221
  46. data/doc/record.txt +0 -207
  47. data/doc/thread_safety.txt +0 -17
@@ -0,0 +1,219 @@
1
+ ### Rationale
2
+
3
+ Traditional object orientation implements polymorphism inheritance. The *Is-A*
4
+ relationship indicates that one object "is a" instance of another object.
5
+ Implicit in this relationship, however, is the concept of [type](http://en.wikipedia.org/wiki/Data_type).
6
+ Every Ruby object has a *type*, and that type is the name of its `Class` or
7
+ `Module`. The Ruby runtime provides a number of reflective methods that allow
8
+ objects to be interrogated for type information. The principal of thses is the
9
+ `is_a?` (alias `kind_of`) method defined in class `Object`.
10
+
11
+ Unlike many traditional object oriented languages, Ruby is a [dynamically typed](http://en.wikipedia.org/wiki/Dynamic_typingDYNAMIC)
12
+ language. Types exist but the runtime is free to cast one type into another
13
+ at any time. Moreover, Ruby is a [duck typed](http://en.wikipedia.org/wiki/Duck_typing).
14
+ If an object "walks like a duck and quacks like a duck then it must be a duck."
15
+ When a method needs called on an object Ruby does not check the type of the object,
16
+ it simply checks to see if the requested function exists with the proper
17
+ [arity](http://en.wikipedia.org/wiki/Arity) and, if it does, dispatches the call.
18
+ The duck type analogue to `is_a?` is `respond_to?`. Thus an object can be interrogated
19
+ for its behavior rather than its type.
20
+
21
+ Although Ruby offers several methods for reflecting on the behavior of a module/class/object,
22
+ such as `method`, `instance_methods`, `const_defined?`, the aforementioned `respond_to?`,
23
+ and others, Ruby lacks a convenient way to group collections of methods in any way that
24
+ does not involve type. Both modules and classes provide mechanisms for combining
25
+ methods into cohesive abstractions, but they both imply type. This is anathema to Ruby's
26
+ dynamism and duck typing. What Ruby needs is a way to collect a group of method names
27
+ and signatures into a cohesive collection that embraces duck typing and dynamic dispatch.
28
+ This is what protocols do.
29
+
30
+ ### Specifying
31
+
32
+ A "protocol" is a loose collection of method, attribute, and constant names with optional
33
+ arity values. The protocol definition does very little on its own. The power of protocols
34
+ is that they provide a way for modules, classes, and objects to be interrogated with
35
+ respect to common behavior, not common type. At the core a protocol is nothing more
36
+ than a collection of `respond_to?` method calls that ask the question "Does this thing
37
+ *behave* like this other thing."
38
+
39
+ Protocols are specified with the `Functional::SpecifyProtocol` method. It takes one parameter,
40
+ the name of the protocol, and a block which contains the protocol specification. This registers
41
+ the protocol specification and makes it available for use later when interrogating ojects
42
+ for their behavior.
43
+
44
+ ##### Defining Attributes, Methods, and Constants
45
+
46
+ A single protocol specification can include definition for attributes, methods,
47
+ and constants. Methods and attributes can be defined as class/module methods or
48
+ as instance methods. Within the a protocol specification each item must include
49
+ the symbolic name of the item being defined.
50
+
51
+ ```ruby
52
+ Functional::SpecifyProtocol(:KitchenSink) do
53
+ instance_method :instance_method
54
+ class_method :class_method
55
+ attr_accessor :attr_accessor
56
+ attr_reader :attr_reader
57
+ attr_writer :attr_writer
58
+ class_attr_accessor :class_attr_accessor
59
+ class_attr_reader :class_attr_reader
60
+ class_attr_writer :class_attr_writer
61
+ constant :CONSTANT
62
+ end
63
+ ```
64
+
65
+ Definitions for accessors are expanded at specification into the apprporiate
66
+ method(s). Which means that this:
67
+
68
+ ```ruby
69
+ Functional::SpecifyProtocol(:Name) do
70
+ attr_accessor :first
71
+ attr_accessor :middle
72
+ attr_accessor :last
73
+ attr_accessor :suffix
74
+ end
75
+ ```
76
+
77
+ is the same as:
78
+
79
+ ```ruby
80
+ Functional::SpecifyProtocol(:Name) do
81
+ instance_method :first
82
+ instance_method :first=
83
+ instance_method :middle
84
+ instance_method :middle=
85
+ instance_method :last
86
+ instance_method :last=
87
+ instance_method :suffix
88
+ instance_method :suffix=
89
+ end
90
+ ```
91
+
92
+ Protocols only care about the methods themselves, not how they were declared.
93
+
94
+ ### Arity
95
+
96
+ In addition to defining *which* methods exist, the required method arity can
97
+ indicated. Arity is optional. When no arity is given any arity will be expected.
98
+ The arity rules follow those defined for the `arity` method of Ruby's
99
+ [Method class](http://www.ruby-doc.org/core-2.1.2/Method.htmlmethod-i-arity):
100
+
101
+ * Methods with a fixed number of arguments have a non-negative arity
102
+ * Methods with optional arguments have an arity `-n - 1`, where n is the number of required arguments
103
+ * Methods with a variable number of arguments have an arity of `-1`
104
+
105
+ ```ruby
106
+ Functional::SpecifyProtocol(:Foo) do
107
+ instance_method :any_args
108
+ instance_method :no_args, 0
109
+ instance_method :three_args, 3
110
+ instance_method :optional_args, -2
111
+ instance_method :variable_args, -1
112
+ end
113
+
114
+ class Bar
115
+
116
+ def any_args(a, b, c=1, d=2, *args)
117
+ end
118
+
119
+ def no_args
120
+ end
121
+
122
+ def three_args(a, b, c)
123
+ end
124
+
125
+ def optional_args(a, b=1, c=2)
126
+ end
127
+
128
+ def variable_args(*args)
129
+ end
130
+ end
131
+ ```
132
+
133
+ ### Reflection
134
+
135
+ Once a protocol has been defined, any class, method, or object may be interrogated
136
+ for adherence to one or more protocol specifications. The methods of the
137
+ `Functional::Protocol` classes provide this capability. The `Satisfy?` method
138
+ takes a module/class/object as the first parameter and one or more protocol names
139
+ as the second and subsequent parameters. It returns a boolean value indicating
140
+ whether the given object satisfies the protocol requirements:
141
+
142
+ ```ruby
143
+ Functional::SpecifyProtocol(:Queue) do
144
+ instance_method :push, 1
145
+ instance_method :pop, 0
146
+ instance_method :length, 0
147
+ end
148
+
149
+ Functional::SpecifyProtocol(:List) do
150
+ instance_method :[]=, 2
151
+ instance_method :[], 1
152
+ instance_method :each, 0
153
+ instance_method :length, 0
154
+ end
155
+
156
+ Functional::Protocol::Satisfy?(Queue, :Queue) => true
157
+ Functional::Protocol::Satisfy?(Queue, :List) => false
158
+
159
+ list = [1, 2, 3]
160
+ Functional::Protocol::Satisfy?(Array, :List, :Queue) => true
161
+ Functional::Protocol::Satisfy?(list, :List, :Queue) => true
162
+
163
+ Functional::Protocol::Satisfy?(Hash, :Queue) => false
164
+
165
+ Functional::Protocol::Satisfy?('foo bar baz', :List) => false
166
+ ```
167
+
168
+ The `Satisfy!` method performs the exact same check but instead raises an exception
169
+ when the protocol is not satisfied:
170
+
171
+ ```
172
+ 2.1.2 :021 > Functional::Protocol::Satisfy!(Queue, :List)
173
+ Functional::ProtocolError: Value (Class) 'Thread::Queue' does not behave as all of: :List.
174
+ from /Projects/functional-ruby/lib/functional/protocol.rb:67:in `error'
175
+ from /Projects/functional-ruby/lib/functional/protocol.rb:36:in `Satisfy!'
176
+ from (irb):21
177
+ ...
178
+ ```
179
+ The `Functional::Protocol` module can be included within other classes
180
+ to eliminate the namespace requirement when calling:
181
+
182
+ ```ruby
183
+ class MessageFormatter
184
+ include Functional::Protocol
185
+
186
+ def format(message)
187
+ if Satisfy?(message, :Internal)
188
+ format_internal_message(message)
189
+ elsif Satisfy?(message, :Error)
190
+ format_error_message(message)
191
+ else
192
+ format_generic_message(message)
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ def format_internal_message(message)
199
+ format the message...
200
+ end
201
+
202
+ def format_error_message(message)
203
+ format the message...
204
+ end
205
+
206
+ def format_generic_message(message)
207
+ format the message...
208
+ end
209
+ ```
210
+
211
+ ### Inspiration
212
+
213
+ Protocols and similar functionality exist in several other programming languages.
214
+ A few languages that provided inspiration for this inplementation are:
215
+
216
+ * Clojure [protocol](http://clojure.org/protocols)
217
+ * Erlang [behaviours](http://www.erlang.org/doc/design_principles/des_princ.htmlid60128)
218
+ * Objective-C [protocol](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html)
219
+ (and the corresponding Swift [protocol](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html))
@@ -0,0 +1,247 @@
1
+ ### Declaration
2
+
3
+ A `Record` class is declared in a manner identical to that used with Ruby's `Struct`.
4
+ The class method `new` is called with a list of one or more field names (symbols).
5
+ A new class will then be dynamically generated along with the necessary reader
6
+ attributes, one for each field. The newly created class will be anonymous and
7
+ will mixin `Functional::AbstractStruct`. The best practice is to assign the newly
8
+ created record class to a constant:
9
+
10
+ ```ruby
11
+ Customer = Functional::Record.new(:name, :address) => Customer
12
+ ```
13
+
14
+ Alternatively, the name of the record class, as a string, can be given as the
15
+ first parameter. In this case the new record class will be created as a constant
16
+ within the `Record` module:
17
+
18
+ ```ruby
19
+ Functional::Record.new("Customer", :name, :address) => Functional::Record::Customer
20
+ ```
21
+
22
+ ### Type Specification
23
+
24
+ Unlike a Ruby `Struct`, a `Record` may be declared with a type/protocol
25
+ specification. In this case, all data members are checked against the
26
+ specification whenever a new record is created. Declaring a `Record` with a
27
+ type specification is similar to declaring a normal `Record`, except that
28
+ the field list is given as a hash with field names as the keys and a class or
29
+ protocol as the values.
30
+
31
+ ```ruby
32
+ Functional::SpecifyProtocol(:Name) do
33
+ attr_reader :first
34
+ attr_reader :middle
35
+ attr_reader :last
36
+ end
37
+
38
+ TypedCustomer = Functional::Record.new(name: :Name, address: String) => TypedCustomer
39
+
40
+ Functional::Record.new("TypedCustomer", name: :Name, address: String) => Functional::Record::TypedCustomer
41
+ ```
42
+
43
+ ### Construction
44
+
45
+ Construction of a new object from a record is slightly different than for a Ruby `Struct`.
46
+ The constructor for a struct class may take zero or more field values and will use those
47
+ values to popuate the fields. The values passed to the constructor are assumed to be in
48
+ the same order as the fields were defined. This works for a struct because it is
49
+ mutable--the field values may be changed after instanciation. Therefore it is not
50
+ necessary to provide all values to a stuct at creation. This is not the case for a
51
+ record. A record is immutable. The values for all its fields must be set at instanciation
52
+ because they cannot be changed later. When creating a new record object the constructor
53
+ will accept a collection of field/value pairs in hash syntax and will create the new
54
+ record with the given values:
55
+
56
+ ```ruby
57
+ Customer.new(name: 'Dave', address: '123 Main')
58
+ => <record Customer :name=>"Dave", :address=>"123 Main">
59
+
60
+ Functional::Record::Customer.new(name: 'Dave', address: '123 Main')
61
+ => <record Functional::Record::Customer :name=>"Dave", :address=>"123 Main">
62
+ ```
63
+
64
+ When a record is defined with a type/protocol specification, the values of
65
+ all non-nil data members are checked against the specification. Any data
66
+ value that is not of the given type or does not satisfy the given protocol
67
+ will cause an exception to be raised:
68
+
69
+ ```ruby
70
+ class Name
71
+ attr_reader :first, :middle, :last
72
+ def initialize(first, middle, last)
73
+ @first = first
74
+ @middle = middle
75
+ @last = last
76
+ end
77
+ end
78
+
79
+ name = Name.new('Douglas', nil, 'Adams') => <Name:0x007fc8b951a278 ...
80
+ TypedCustomer.new(name: name, address: '123 Main') => <record TypedCustomer :name=><Name:0x007f914cce05b0 ...
81
+
82
+ TypedCustomer.new(name: 'Douglas Adams', address: '123 Main') => ArgumentError: 'name' must stasify the protocol :Name
83
+ TypedCustomer.new(name: name, address: 42) => ArgumentError: 'address' must be of type String
84
+ ```
85
+
86
+ ### Default Values
87
+
88
+ By default, all record fields are set to `nil` at instanciation unless explicity set
89
+ via the constructor. It is possible to specify default values other than `nil` for
90
+ zero or more of the fields when a new record class is created. The `new` method of
91
+ `Record` accepts a block which can be used to declare new default values:
92
+
93
+ ```ruby
94
+ Address = Functional::Record.new(:street_line_1, :street_line_2,
95
+ :city, :state, :postal_code, :country) do
96
+ default :state, 'Ohio'
97
+ default :country, 'USA'
98
+ end
99
+ => Address
100
+ ```
101
+
102
+ When a new object is created from a record class with explicit default values, those
103
+ values will be used for the appropriate fields when no other value is given at
104
+ construction:
105
+
106
+ ```ruby
107
+ Address.new(street_line_1: '2401 Ontario St',
108
+ city: 'Cleveland', postal_code: 44115)
109
+ => <record Address :street_line_1=>"2401 Ontario St", :street_line_2=>nil, :city=>"Cleveland", :state=>"Ohio", :postal_code=>44115, :country=>"USA">
110
+ ```
111
+
112
+ Of course, if a value for a field is given at construction that value will be used instead
113
+ of the custom default:
114
+
115
+ ```ruby
116
+ Address.new(street_line_1: '1060 W Addison St',
117
+ city: 'Chicago', state: 'Illinois', postal_code: 60613)
118
+ => <record Address :street_line_1=>"1060 W Addison St", :street_line_2=>nil, :city=>"Chicago", :state=>"Illinois", :postal_code=>60613, :country=>"USA">
119
+ ```
120
+
121
+ ### Mandatory Fields
122
+
123
+ By default, all record fields are optional. It is perfectly legal for a record
124
+ object to exist with all its fields set to `nil`. During declaration of a new record
125
+ class the block passed to `Record.new` can also be used to indicate which fields
126
+ are mandatory. When a new object is created from a record with mandatory fields
127
+ an exception will be thrown if any of those fields are nil:
128
+
129
+ ```ruby
130
+ Name = Functional::Record.new(:first, :middle, :last, :suffix) do
131
+ mandatory :first, :last
132
+ end
133
+ => Name
134
+
135
+ Name.new(first: 'Joe', last: 'Armstrong')
136
+ => <record Name :first=>"Joe", :middle=>nil, :last=>"Armstrong", :suffix=>nil>
137
+
138
+ Name.new(first: 'Matz') => ArgumentError: mandatory fields must not be nil
139
+ ```
140
+
141
+ Of course, declarations for default values and mandatory fields may be used
142
+ together:
143
+
144
+ ```ruby
145
+ Person = Functional::Record.new(:first_name, :middle_name, :last_name,
146
+ :street_line_1, :street_line_2,
147
+ :city, :state, :postal_code, :country) do
148
+ mandatory :first_name, :last_name
149
+ mandatory :country
150
+ default :state, 'Ohio'
151
+ default :country, 'USA'
152
+ end
153
+ => Person
154
+ ```
155
+
156
+ ### Default Value Memoization
157
+
158
+ Note that the block provided to `Record.new` is processed once and only once
159
+ when the new record class is declared. Thereafter the results are memoized
160
+ and copied (via `clone`, unless uncloneable) each time a new record object
161
+ is created. Default values should be simple types like `String`, `Fixnum`,
162
+ and `Boolean`. If complex operations need performed when setting default
163
+ values the a `Class` should be used instead of a `Record`.
164
+
165
+ ##### Why Declaration Differs from Ruby's Struct
166
+
167
+ Those familiar with Ruby's `Struct` class will notice one important
168
+ difference when declaring a `Record`: the block passes to `new` cannot be
169
+ used to define additional methods. When declaring a new class created from a
170
+ Ruby `Struct` the block can perform any additional class definition that
171
+ could be done had the class be defined normally. The excellent
172
+ [Values](https://github.com/tcrayford/Values) supports this same behavior.
173
+ `Record` does not allow additional class definitions during declaration for
174
+ one simple reason: doing so violates two very important tenets of functional
175
+ programming. Specifically, immutability and the separation of data from
176
+ operations.
177
+
178
+ `Record` exists for the purpose of creating immutable objects. If additional
179
+ instance methods were to be defined on a record class it would be possible
180
+ to violate immutability. Not only could additional, mutable state be added
181
+ to the class, but the existing immutable attributes could be overridden by
182
+ mutable methods. The security of providing an immutable object would be
183
+ completely shattered, thus defeating the original purpose of the record
184
+ class. Of course it would be possible to allow this feature and trust the
185
+ programmer to not violate the intended immutability of class, but opening
186
+ `Record` to the *possibility* of immutability violation is unnecessary and
187
+ unwise.
188
+
189
+ More important than the potential for immutability violations is the fact
190
+ the adding additional methods to a record violates the principal of
191
+ separating data from operations on that data. This is one of the core ideas
192
+ in functional programming. Data is defined in pure structures that contain
193
+ no behavior and operations on that data are provided by polymorphic
194
+ functions. This may seem counterintuitive to object oriented programmers,
195
+ but that is the nature of functional programming. Adding behavior to a
196
+ record, even when that behavior does not violate immutability, is still
197
+ anathema to functional programming, and it is why records in languages like
198
+ Erlang and Clojure do not have functions defined within them.
199
+
200
+ Should additional methods need defined on a `Record` class, the appropriate
201
+ practice is to declare the record class then declare another class which
202
+ extends the record. The record class remains pure data and the subclass
203
+ contains additional operations on that data.
204
+
205
+ ```ruby
206
+ NameRecord = Functional::Record.new(:first, :middle, :last, :suffix) do
207
+ mandatory :first, :last
208
+ end
209
+
210
+ class Name < NameRecord
211
+ def full_name
212
+ "{first} {last}"
213
+ end
214
+
215
+ def formal_name
216
+ name = [first, middle, last].select{|s| ! s.to_s.empty?}.join(' ')
217
+ suffix.to_s.empty? ? name : name + ", {suffix}"
218
+ end
219
+ end
220
+
221
+ jerry = Name.new(first: 'Jerry', last: "D'Antonio")
222
+ ted = Name.new(first: 'Ted', middle: 'Theodore', last: 'Logan', suffix: 'Esq.')
223
+
224
+ jerry.formal_name => "Jerry D'Antonio"
225
+ ted.formal_name => "Ted Theodore Logan, Esq."
226
+ ```
227
+
228
+ ### Inspiration
229
+
230
+ Neither struct nor records are new to computing. Both have been around for a very
231
+ long time. Mutable structs can be found in many languages including
232
+ [Ruby](http://www.ruby-doc.org/core-2.1.2/Struct.html),
233
+ [Go](http://golang.org/ref/specStruct_types),
234
+ [C](http://en.wikipedia.org/wiki/Struct_(C_programming_language)),
235
+ and [C](http://msdn.microsoft.com/en-us/library/ah19swz4.aspx),
236
+ just to name a few. Immutable records exist primarily in functional languages
237
+ like [Haskell](http://en.wikibooks.org/wiki/Haskell/More_on_datatypesNamed_Fields_.28Record_Syntax.29),
238
+ Clojure, and Erlang. The inspiration for declaring records with a type
239
+ specification is taken from [PureScript](http://www.purescript.org/), a
240
+ compile-to-JavaScript language inspired by Haskell.
241
+
242
+ * [Ruby Struct](http://www.ruby-doc.org/core-2.1.2/Struct.html)
243
+ * [Clojure Datatypes](http://clojure.org/datatypes)
244
+ * [Clojure *defrecord* macro](http://clojure.github.io/clojure/clojure.core-api.htmlclojure.core/defrecord)
245
+ * [Erlang Records (Reference)](http://www.erlang.org/doc/reference_manual/records.html)
246
+ * [Erlang Records (Examples)](http://www.erlang.org/doc/programming_examples/records.html)
247
+ * [PureScript Records](http://docs.purescript.org/en/latest/types.htmlrecords)