functional-ruby 0.7.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -152
  3. data/doc/memo.txt +192 -0
  4. data/doc/pattern_matching.txt +485 -0
  5. data/doc/protocol.txt +221 -0
  6. data/doc/record.txt +144 -0
  7. data/doc/thread_safety.txt +8 -0
  8. data/lib/functional.rb +48 -18
  9. data/lib/functional/abstract_struct.rb +161 -0
  10. data/lib/functional/delay.rb +117 -0
  11. data/lib/functional/either.rb +222 -0
  12. data/lib/functional/memo.rb +93 -0
  13. data/lib/functional/method_signature.rb +72 -0
  14. data/lib/functional/option.rb +209 -0
  15. data/lib/functional/pattern_matching.rb +117 -100
  16. data/lib/functional/protocol.rb +157 -0
  17. data/lib/functional/protocol_info.rb +193 -0
  18. data/lib/functional/record.rb +155 -0
  19. data/lib/functional/type_check.rb +112 -0
  20. data/lib/functional/union.rb +152 -0
  21. data/lib/functional/version.rb +3 -1
  22. data/spec/functional/abstract_struct_shared.rb +154 -0
  23. data/spec/functional/complex_pattern_matching_spec.rb +205 -0
  24. data/spec/functional/configuration_spec.rb +17 -0
  25. data/spec/functional/delay_spec.rb +147 -0
  26. data/spec/functional/either_spec.rb +237 -0
  27. data/spec/functional/memo_spec.rb +207 -0
  28. data/spec/functional/option_spec.rb +292 -0
  29. data/spec/functional/pattern_matching_spec.rb +279 -276
  30. data/spec/functional/protocol_info_spec.rb +444 -0
  31. data/spec/functional/protocol_spec.rb +274 -0
  32. data/spec/functional/record_spec.rb +175 -0
  33. data/spec/functional/type_check_spec.rb +103 -0
  34. data/spec/functional/union_spec.rb +110 -0
  35. data/spec/spec_helper.rb +6 -4
  36. metadata +55 -45
  37. data/lib/functional/behavior.rb +0 -138
  38. data/lib/functional/behaviour.rb +0 -2
  39. data/lib/functional/catalog.rb +0 -487
  40. data/lib/functional/collection.rb +0 -403
  41. data/lib/functional/inflect.rb +0 -127
  42. data/lib/functional/platform.rb +0 -120
  43. data/lib/functional/search.rb +0 -132
  44. data/lib/functional/sort.rb +0 -41
  45. data/lib/functional/utilities.rb +0 -189
  46. data/md/behavior.md +0 -188
  47. data/md/catalog.md +0 -32
  48. data/md/collection.md +0 -32
  49. data/md/inflect.md +0 -32
  50. data/md/pattern_matching.md +0 -512
  51. data/md/platform.md +0 -32
  52. data/md/search.md +0 -32
  53. data/md/sort.md +0 -32
  54. data/md/utilities.md +0 -55
  55. data/spec/functional/behavior_spec.rb +0 -528
  56. data/spec/functional/catalog_spec.rb +0 -1206
  57. data/spec/functional/collection_spec.rb +0 -752
  58. data/spec/functional/inflect_spec.rb +0 -85
  59. data/spec/functional/integration_spec.rb +0 -205
  60. data/spec/functional/platform_spec.rb +0 -501
  61. data/spec/functional/search_spec.rb +0 -187
  62. data/spec/functional/sort_spec.rb +0 -61
  63. data/spec/functional/utilities_spec.rb +0 -277
@@ -0,0 +1,221 @@
1
+ # @!macro [new] protocol
2
+ #
3
+ # ## Rationale
4
+ #
5
+ # Traditional object orientation implements polymorphism inheritance. The *Is-A*
6
+ # relationship indicates that one object "is a" instance of another object.
7
+ # Implicit in this relationship, however, is the concept of [type](http://en.wikipedia.org/wiki/Data_type).
8
+ # Every Ruby object has a *type*, and that type is the name of its `Class` or
9
+ # `Module`. The Ruby runtime provides a number of reflective methods that allow
10
+ # objects to be interrogated for type information. The principal of thses is the
11
+ # `is_a?` (alias `kind_of`) method defined in class `Object`.
12
+ #
13
+ # Unlike many traditional object oriented languages, Ruby is a [dynamically typed](http://en.wikipedia.org/wiki/Dynamic_typing#DYNAMIC)
14
+ # language. Types exist but the runtime is free to cast one type into another
15
+ # at any time. Moreover, Ruby is a [duck typed](http://en.wikipedia.org/wiki/Duck_typing).
16
+ # If an object "walks like a duck and quacks like a duck then it must be a duck."
17
+ # When a method needs called on an object Ruby does not check the type of the object,
18
+ # it simply checks to see if the requested function exists with the proper
19
+ # [arity](http://en.wikipedia.org/wiki/Arity) and, if it does, dispatches the call.
20
+ # The duck type analogue to `is_a?` is `respond_to?`. Thus an object can be interrogated
21
+ # for its behavior rather than its type.
22
+ #
23
+ # Although Ruby offers several methods for reflecting on the behavior of a module/class/object,
24
+ # such as `method`, `instance_methods`, `const_defined?`, the aforementioned `respond_to?`,
25
+ # and others, Ruby lacks a convenient way to group collections of methods in any way that
26
+ # does not involve type. Both modules and classes provide mechanisms for combining
27
+ # methods into cohesive abstractions, but they both imply type. This is anathema to Ruby's
28
+ # dynamism and duck typing. What Ruby needs is a way to collect a group of method names
29
+ # and signatures into a cohesive collection that embraces duck typing and dynamic dispatch.
30
+ # This is what protocols do.
31
+ #
32
+ # ## Specifying
33
+ #
34
+ # A "protocol" is a loose collection of method, attribute, and constant names with optional
35
+ # arity values. The protocol definition does very little on its own. The power of protocols
36
+ # is that they provide a way for modules, classes, and objects to be interrogated with
37
+ # respect to common behavior, not common type. At the core a protocol is nothing more
38
+ # than a collection of `respond_to?` method calls that ask the question "Does this thing
39
+ # *behave* like this other thing."
40
+ #
41
+ # Protocols are specified with the `Functional::SpecifyProtocol` method. It takes one parameter,
42
+ # the name of the protocol, and a block which contains the protocol specification. This registers
43
+ # the protocol specification and makes it available for use later when interrogating ojects
44
+ # for their behavior.
45
+ #
46
+ # ### Defining Attributes, Methods, and Constants
47
+ #
48
+ # A single protocol specification can include definition for attributes, methods,
49
+ # and constants. Methods and attributes can be defined as class/module methods or
50
+ # as instance methods. Within the a protocol specification each item must include
51
+ # the symbolic name of the item being defined.
52
+ #
53
+ # ```ruby
54
+ # Functional::SpecifyProtocol(:KitchenSink) do
55
+ # instance_method :instance_method
56
+ # class_method :class_method
57
+ # attr_accessor :attr_accessor
58
+ # attr_reader :attr_reader
59
+ # attr_writer :attr_writer
60
+ # class_attr_accessor :class_attr_accessor
61
+ # class_attr_reader :class_attr_reader
62
+ # class_attr_writer :class_attr_writer
63
+ # constant :CONSTANT
64
+ # end
65
+ # ```
66
+ #
67
+ # Definitions for accessors are expanded at specification into the apprporiate
68
+ # method(s). Which means that this:
69
+ #
70
+ # ```ruby
71
+ # Functional::SpecifyProtocol(:Name) do
72
+ # attr_accessor :first
73
+ # attr_accessor :middle
74
+ # attr_accessor :last
75
+ # attr_accessor :suffix
76
+ # end
77
+ # ```
78
+ #
79
+ # is the same as:
80
+ #
81
+ # ```ruby
82
+ # Functional::SpecifyProtocol(:Name) do
83
+ # instance_method :first
84
+ # instance_method :first=
85
+ # instance_method :middle
86
+ # instance_method :middle=
87
+ # instance_method :last
88
+ # instance_method :last=
89
+ # instance_method :suffix
90
+ # instance_method :suffix=
91
+ # end
92
+ # ```
93
+ #
94
+ # Protocols only care about the methods themselves, not how they were declared.
95
+ #
96
+ # ### Arity
97
+ #
98
+ # In addition to defining *which* methods exist, the required method arity can
99
+ # indicated. Arity is optional. When no arity is given any arity will be expected.
100
+ # The arity rules follow those defined for the `#arity` method of Ruby's
101
+ # [Method class](http://www.ruby-doc.org/core-2.1.2/Method.html#method-i-arity):
102
+ #
103
+ # * Methods with a fixed number of arguments have a non-negative arity
104
+ # * Methods with optional arguments have an arity `-n - 1`, where n is the number of required arguments
105
+ # * Methods with a variable number of arguments have an arity of `-1`
106
+ #
107
+ # ```ruby
108
+ # Functional::SpecifyProtocol(:Foo) do
109
+ # instance_method :any_args
110
+ # instance_method :no_args, 0
111
+ # instance_method :three_args, 3
112
+ # instance_method :optional_args, -2
113
+ # instance_method :variable_args, -1
114
+ # end
115
+ #
116
+ # class Bar
117
+ #
118
+ # def any_args(a, b, c=1, d=2, *args)
119
+ # end
120
+ #
121
+ # def no_args
122
+ # end
123
+ #
124
+ # def three_args(a, b, c)
125
+ # end
126
+ #
127
+ # def optional_args(a, b=1, c=2)
128
+ # end
129
+ #
130
+ # def variable_args(*args)
131
+ # end
132
+ # end
133
+ # ```
134
+ #
135
+ # ## Reflection
136
+ #
137
+ # Once a protocol has been defined, any class, method, or object may be interrogated
138
+ # for adherence to one or more protocol specifications. The methods of the
139
+ # `Functional::Protocol` classes provide this capability. The `Satisfy?` method
140
+ # takes a module/class/object as the first parameter and one or more protocol names
141
+ # as the second and subsequent parameters. It returns a boolean value indicating
142
+ # whether the given object satisfies the protocol requirements:
143
+ #
144
+ # ```ruby
145
+ # Functional::SpecifyProtocol(:Queue) do
146
+ # instance_method :push, 1
147
+ # instance_method :pop, 0
148
+ # instance_method :length, 0
149
+ # end
150
+ #
151
+ # Functional::SpecifyProtocol(:List) do
152
+ # instance_method :[]=, 2
153
+ # instance_method :[], 1
154
+ # instance_method :each, 0
155
+ # instance_method :length, 0
156
+ # end
157
+ #
158
+ # Functional::Protocol::Satisfy?(Queue, :Queue) #=> true
159
+ # Functional::Protocol::Satisfy?(Queue, :List) #=> false
160
+ #
161
+ # list = [1, 2, 3]
162
+ # Functional::Protocol::Satisfy?(Array, :List, :Queue) #=> true
163
+ # Functional::Protocol::Satisfy?(list, :List, :Queue) #=> true
164
+ #
165
+ # Functional::Protocol::Satisfy?(Hash, :Queue) #=> false
166
+ #
167
+ # Functional::Protocol::Satisfy?('foo bar baz', :List) #=> false
168
+ # ```
169
+ #
170
+ # The `Satisfy!` method performs the exact same check but instead raises an exception
171
+ # when the protocol is not satisfied:
172
+ #
173
+ # ```
174
+ # 2.1.2 :021 > Functional::Protocol::Satisfy!(Queue, :List)
175
+ # Functional::ProtocolError: Value (Class) 'Thread::Queue' does not behave as all of: :List.
176
+ # from /Projects/functional-ruby/lib/functional/protocol.rb:67:in `error'
177
+ # from /Projects/functional-ruby/lib/functional/protocol.rb:36:in `Satisfy!'
178
+ # from (irb):21
179
+ # ...
180
+ # ```
181
+ # The `Functional::Protocol` module can be included within other classes
182
+ # to eliminate the namespace requirement when calling:
183
+ #
184
+ # ```ruby
185
+ # class MessageFormatter
186
+ # include Functional::Protocol
187
+ #
188
+ # def format(message)
189
+ # if Satisfy?(message, :Internal)
190
+ # format_internal_message(message)
191
+ # elsif Satisfy?(message, :Error)
192
+ # format_error_message(message)
193
+ # else
194
+ # format_generic_message(message)
195
+ # end
196
+ # end
197
+ #
198
+ # private
199
+ #
200
+ # def format_internal_message(message)
201
+ # # format the message...
202
+ # end
203
+ #
204
+ # def format_error_message(message)
205
+ # # format the message...
206
+ # end
207
+ #
208
+ # def format_generic_message(message)
209
+ # # format the message...
210
+ # end
211
+ # ```
212
+ #
213
+ # ## Inspiration
214
+ #
215
+ # Protocols and similar functionality exist in several other programming languages.
216
+ # A few languages that provided inspiration for this inplementation are:
217
+ #
218
+ # * Clojure [protocol](http://clojure.org/protocols)
219
+ # * Erlang [behaviours](http://www.erlang.org/doc/design_principles/des_princ.html#id60128)
220
+ # * Objective-C [protocol](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html)
221
+ # (and the corresponding Swift [protocol](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html))
@@ -0,0 +1,144 @@
1
+ # @!macro [new] record
2
+ #
3
+ # ## Declaration
4
+ #
5
+ # A `Record` class is declared in a manner identical to that used with Ruby's `Struct`.
6
+ # The class method `new` is called with a list of one or more field names (symbols).
7
+ # A new class will then be dynamically generated along with the necessary reader
8
+ # attributes, one for each field. The newly created class will be anonymous and
9
+ # will mixin `Functional::AbstractStruct`. The best practice is to assign the newly
10
+ # created record class to a constant:
11
+ #
12
+ # ```ruby
13
+ # Customer = Functional::Record.new(:name, :address) #=> Customer
14
+ # ```
15
+ #
16
+ # Alternatively, the name of the record class, as a string, can be given as the
17
+ # first parameter. In this case the new record class will be created as a constant
18
+ # within the `Record` module:
19
+ #
20
+ # ```ruby
21
+ # Functional::Record.new("Customer", :name, :address) #=> Functional::Record::Customer
22
+ # ```
23
+ #
24
+ # **NOTE:** The `new` method of `Record` does not accept a block the way Ruby's `Struct`
25
+ # does. The block passed to the `new` method of `Record` is used to set mandatory fields
26
+ # and default values (see below). It is *not* used for additional class declarations.
27
+ #
28
+ # ### Construction
29
+ #
30
+ # Construction of a new object from a record is slightly different than for a Ruby `Struct`.
31
+ # The constructor for a struct class may take zero or more field values and will use those
32
+ # values to popuate the fields. The values passed to the constructor are assumed to be in
33
+ # the same order as the fields were defined. This works for a struct because it is
34
+ # mutable--the field values may be changed after instanciation. Therefore it is not
35
+ # necessary to provide all values to a stuct at creation. This is not the case for a
36
+ # record. A record is immutable. The values for all its fields must be set at instanciation
37
+ # because they cannot be changed later. When creating a new record object the constructor
38
+ # will accept a collection of field/value pairs in hash syntax and will create the new
39
+ # record with the given values:
40
+ #
41
+ # ```ruby
42
+ # Customer.new(name: 'Dave', address: '123 Main')
43
+ # #=> #<record Customer :name=>"Dave", :address=>"123 Main">
44
+ #
45
+ # Functional::Record::Customer.new(name: 'Dave', address: '123 Main')
46
+ # #=> #<record Functional::Record::Customer :name=>"Dave", :address=>"123 Main">
47
+ # ```
48
+ #
49
+ # ### Default Values
50
+ #
51
+ # By default, all record fields are set to `nil` at instanciation unless explicity set
52
+ # via the constructor. It is possible to specify default values other than `nil` for
53
+ # zero or more of the fields when a new record class is created. The `new` method of
54
+ # `Record` accepts a block which can be used to declare new default values:
55
+ #
56
+ # ```ruby
57
+ # Address = Functional::Record.new(:street_line_1, :street_line_2,
58
+ # :city, :state, :postal_code, :country) do
59
+ # default :state, 'Ohio'
60
+ # default :country, 'USA'
61
+ # end
62
+ # #=> Address
63
+ # ```
64
+ #
65
+ # When a new object is created from a record class with explicit default values, those
66
+ # values will be used for the appropriate fields when no other value is given at
67
+ # construction:
68
+ #
69
+ # ```ruby
70
+ # Address.new(street_line_1: '2401 Ontario St',
71
+ # city: 'Cleveland', postal_code: 44115)
72
+ # #=> #<record Address :street_line_1=>"2401 Ontario St", :street_line_2=>nil, :city=>"Cleveland", :state=>"Ohio", :postal_code=>44115, :country=>"USA">
73
+ # ```
74
+ #
75
+ # Of course, if a value for a field is given at construction that value will be used instead
76
+ # of the custom default:
77
+ #
78
+ # ```ruby
79
+ # Address.new(street_line_1: '1060 W Addison St',
80
+ # city: 'Chicago', state: 'Illinois', postal_code: 60613)
81
+ # #=> #<record Address :street_line_1=>"1060 W Addison St", :street_line_2=>nil, :city=>"Chicago", :state=>"Illinois", :postal_code=>60613, :country=>"USA">
82
+ # ```
83
+ #
84
+ # ### Mandatory Fields
85
+ #
86
+ # By default, all record fields are optional. It is perfectly legal for a record
87
+ # object to exist with all its fields set to `nil`. During declaration of a new record
88
+ # class the block passed to `Record.new` can also be used to indicate which fields
89
+ # are mandatory. When a new object is created from a record with mandatory fields
90
+ # an exception will be thrown if any of those fields are nil:
91
+ #
92
+ # ```ruby
93
+ # Name = Functional::Record.new(:first, :middle, :last, :suffix) do
94
+ # mandatory :first, :last
95
+ # end
96
+ # #=> Name
97
+ #
98
+ # Name.new(first: 'Joe', last: 'Armstrong')
99
+ # #=> #<record Name :first=>"Joe", :middle=>nil, :last=>"Armstrong", :suffix=>nil>
100
+ #
101
+ # Name.new(first: 'Matz') #=> ArgumentError: mandatory fields must not be nil
102
+ # ```
103
+ #
104
+ # Of course, declarations for default values and mandatory fields may be used
105
+ # together:
106
+ #
107
+ # ```ruby
108
+ # Person = Functional::Record.new(:first_name, :middle_name, :last_name,
109
+ # :street_line_1, :street_line_2,
110
+ # :city, :state, :postal_code, :country) do
111
+ # mandatory :first_name, :last_name
112
+ # mandatory :country
113
+ # default :state, 'Ohio'
114
+ # default :country, 'USA'
115
+ # end
116
+ # #=> Person
117
+ # ```
118
+ #
119
+ # ### Default Value Memoization
120
+ #
121
+ # Note that the block provided to `Record.new` is processed once and only once
122
+ # when the new record class is declared. Thereafter the results are memoized
123
+ # and copied (via `clone`, unless uncloneable) each time a new record object
124
+ # is created. Default values should be simple types like `String`, `Fixnum`,
125
+ # and `Boolean`. If complex operations need performed when setting default
126
+ # values the a `Class` should be used instead of a `Record`.
127
+ #
128
+ # ## Inspiration
129
+ #
130
+ # Neither struct nor records are new to computing. Both have been around for a very
131
+ # long time. Mutable structs can be found in many languages including
132
+ # [Ruby](http://www.ruby-doc.org/core-2.1.2/Struct.html),
133
+ # [Go](http://golang.org/ref/spec#Struct_types),
134
+ # [C](http://en.wikipedia.org/wiki/Struct_(C_programming_language)),
135
+ # and [C#](http://msdn.microsoft.com/en-us/library/ah19swz4.aspx),
136
+ # just to name a few. Immutable records exist primarily in functional languages
137
+ # like [Haskell](http://en.wikibooks.org/wiki/Haskell/More_on_datatypes#Named_Fields_.28Record_Syntax.29),
138
+ # Clojure, and Erlang. The latter two are the main influences for this implementation.
139
+ #
140
+ # * [Ruby Struct](http://www.ruby-doc.org/core-2.1.2/Struct.html)
141
+ # * [Clojure Datatypes](http://clojure.org/datatypes)
142
+ # * [Clojure *defrecord* macro](http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/defrecord)
143
+ # * [Erlang Records (Reference)](http://www.erlang.org/doc/reference_manual/records.html)
144
+ # * [Erlang Records (Examples)](http://www.erlang.org/doc/programming_examples/records.html)
@@ -0,0 +1,8 @@
1
+ # @!macro [new] thread_safe_immutable_object
2
+ #
3
+ # @note This is an immutable, read-only, frozen, thread safe object that can
4
+ # be used in concurrent systems. Thread safety guarantees *cannot* be made
5
+ # about objects contained *within* this object, however. Ruby variables are
6
+ # mutable references to mutable objects. This cannot be changed. The best
7
+ # practice it to only encapsulate immutable, frozen, or thread safe objects.
8
+ # Ultimately, thread safety is the responsibility of the programmer.
@@ -1,26 +1,56 @@
1
- require 'functional/behavior'
2
- require 'functional/behaviour'
3
- require 'functional/catalog'
4
- require 'functional/collection'
5
- require 'functional/inflect'
1
+ require 'functional/delay'
2
+ require 'functional/either'
3
+ require 'functional/memo'
4
+ require 'functional/option'
6
5
  require 'functional/pattern_matching'
7
- require 'functional/platform'
8
- require 'functional/search'
9
- require 'functional/sort'
10
- require 'functional/utilities'
6
+ require 'functional/protocol'
7
+ require 'functional/protocol_info'
8
+ require 'functional/record'
9
+ require 'functional/type_check'
10
+ require 'functional/union'
11
11
  require 'functional/version'
12
12
 
13
- Infinity = 1/0.0 unless defined?(Infinity)
14
- NaN = 0/0.0 unless defined?(NaN)
15
-
16
- $ENABLE_BEHAVIOR_CHECK_ON_CONSTRUCTION ||= true
13
+ Functional::SpecifyProtocol(:Disposition) do
14
+ instance_method :value, 0
15
+ instance_method :value?, 0
16
+ instance_method :reason, 0
17
+ instance_method :reason?, 0
18
+ instance_method :fulfilled?, 0
19
+ instance_method :rejected?, 0
20
+ end
17
21
 
22
+ # Erlang, Clojure, and Go inspired functional programming tools to Ruby.
18
23
  module Functional
19
24
 
20
- class << self
21
- include Collection
22
- include Inflect
23
- include Search
24
- include Sort
25
+ # Infinity
26
+ Infinity = 1/0.0
27
+
28
+ # Not a number
29
+ NaN = 0/0.0
30
+
31
+ # A gem-level configuration class.
32
+ # @!visibility private
33
+ class Configuration
34
+ end
35
+
36
+ # create the default configuration on load
37
+ # @!visibility private
38
+ @configuration = Configuration.new
39
+
40
+ # The current gem configutation.
41
+ #
42
+ # @return [Functional::Configuration]
43
+ #
44
+ # @!visibility private
45
+ def self.configuration
46
+ @configuration
47
+ end
48
+
49
+ # Perform gem-level configuration.
50
+ #
51
+ # @yield the configuration commands
52
+ # @yieldparam [Functional::Configuration] the current configuration object
53
+ def self.configure
54
+ yield(configuration)
25
55
  end
26
56
  end