functional-ruby 0.7.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -152
  3. data/doc/memo.txt +192 -0
  4. data/doc/pattern_matching.txt +485 -0
  5. data/doc/protocol.txt +221 -0
  6. data/doc/record.txt +144 -0
  7. data/doc/thread_safety.txt +8 -0
  8. data/lib/functional.rb +48 -18
  9. data/lib/functional/abstract_struct.rb +161 -0
  10. data/lib/functional/delay.rb +117 -0
  11. data/lib/functional/either.rb +222 -0
  12. data/lib/functional/memo.rb +93 -0
  13. data/lib/functional/method_signature.rb +72 -0
  14. data/lib/functional/option.rb +209 -0
  15. data/lib/functional/pattern_matching.rb +117 -100
  16. data/lib/functional/protocol.rb +157 -0
  17. data/lib/functional/protocol_info.rb +193 -0
  18. data/lib/functional/record.rb +155 -0
  19. data/lib/functional/type_check.rb +112 -0
  20. data/lib/functional/union.rb +152 -0
  21. data/lib/functional/version.rb +3 -1
  22. data/spec/functional/abstract_struct_shared.rb +154 -0
  23. data/spec/functional/complex_pattern_matching_spec.rb +205 -0
  24. data/spec/functional/configuration_spec.rb +17 -0
  25. data/spec/functional/delay_spec.rb +147 -0
  26. data/spec/functional/either_spec.rb +237 -0
  27. data/spec/functional/memo_spec.rb +207 -0
  28. data/spec/functional/option_spec.rb +292 -0
  29. data/spec/functional/pattern_matching_spec.rb +279 -276
  30. data/spec/functional/protocol_info_spec.rb +444 -0
  31. data/spec/functional/protocol_spec.rb +274 -0
  32. data/spec/functional/record_spec.rb +175 -0
  33. data/spec/functional/type_check_spec.rb +103 -0
  34. data/spec/functional/union_spec.rb +110 -0
  35. data/spec/spec_helper.rb +6 -4
  36. metadata +55 -45
  37. data/lib/functional/behavior.rb +0 -138
  38. data/lib/functional/behaviour.rb +0 -2
  39. data/lib/functional/catalog.rb +0 -487
  40. data/lib/functional/collection.rb +0 -403
  41. data/lib/functional/inflect.rb +0 -127
  42. data/lib/functional/platform.rb +0 -120
  43. data/lib/functional/search.rb +0 -132
  44. data/lib/functional/sort.rb +0 -41
  45. data/lib/functional/utilities.rb +0 -189
  46. data/md/behavior.md +0 -188
  47. data/md/catalog.md +0 -32
  48. data/md/collection.md +0 -32
  49. data/md/inflect.md +0 -32
  50. data/md/pattern_matching.md +0 -512
  51. data/md/platform.md +0 -32
  52. data/md/search.md +0 -32
  53. data/md/sort.md +0 -32
  54. data/md/utilities.md +0 -55
  55. data/spec/functional/behavior_spec.rb +0 -528
  56. data/spec/functional/catalog_spec.rb +0 -1206
  57. data/spec/functional/collection_spec.rb +0 -752
  58. data/spec/functional/inflect_spec.rb +0 -85
  59. data/spec/functional/integration_spec.rb +0 -205
  60. data/spec/functional/platform_spec.rb +0 -501
  61. data/spec/functional/search_spec.rb +0 -187
  62. data/spec/functional/sort_spec.rb +0 -61
  63. data/spec/functional/utilities_spec.rb +0 -277
@@ -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