functional-ruby 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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)