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.
- checksums.yaml +4 -4
- data/README.md +92 -152
- data/doc/memo.txt +192 -0
- data/doc/pattern_matching.txt +485 -0
- data/doc/protocol.txt +221 -0
- data/doc/record.txt +144 -0
- data/doc/thread_safety.txt +8 -0
- data/lib/functional.rb +48 -18
- data/lib/functional/abstract_struct.rb +161 -0
- data/lib/functional/delay.rb +117 -0
- data/lib/functional/either.rb +222 -0
- data/lib/functional/memo.rb +93 -0
- data/lib/functional/method_signature.rb +72 -0
- data/lib/functional/option.rb +209 -0
- data/lib/functional/pattern_matching.rb +117 -100
- data/lib/functional/protocol.rb +157 -0
- data/lib/functional/protocol_info.rb +193 -0
- data/lib/functional/record.rb +155 -0
- data/lib/functional/type_check.rb +112 -0
- data/lib/functional/union.rb +152 -0
- data/lib/functional/version.rb +3 -1
- data/spec/functional/abstract_struct_shared.rb +154 -0
- data/spec/functional/complex_pattern_matching_spec.rb +205 -0
- data/spec/functional/configuration_spec.rb +17 -0
- data/spec/functional/delay_spec.rb +147 -0
- data/spec/functional/either_spec.rb +237 -0
- data/spec/functional/memo_spec.rb +207 -0
- data/spec/functional/option_spec.rb +292 -0
- data/spec/functional/pattern_matching_spec.rb +279 -276
- data/spec/functional/protocol_info_spec.rb +444 -0
- data/spec/functional/protocol_spec.rb +274 -0
- data/spec/functional/record_spec.rb +175 -0
- data/spec/functional/type_check_spec.rb +103 -0
- data/spec/functional/union_spec.rb +110 -0
- data/spec/spec_helper.rb +6 -4
- metadata +55 -45
- data/lib/functional/behavior.rb +0 -138
- data/lib/functional/behaviour.rb +0 -2
- data/lib/functional/catalog.rb +0 -487
- data/lib/functional/collection.rb +0 -403
- data/lib/functional/inflect.rb +0 -127
- data/lib/functional/platform.rb +0 -120
- data/lib/functional/search.rb +0 -132
- data/lib/functional/sort.rb +0 -41
- data/lib/functional/utilities.rb +0 -189
- data/md/behavior.md +0 -188
- data/md/catalog.md +0 -32
- data/md/collection.md +0 -32
- data/md/inflect.md +0 -32
- data/md/pattern_matching.md +0 -512
- data/md/platform.md +0 -32
- data/md/search.md +0 -32
- data/md/sort.md +0 -32
- data/md/utilities.md +0 -55
- data/spec/functional/behavior_spec.rb +0 -528
- data/spec/functional/catalog_spec.rb +0 -1206
- data/spec/functional/collection_spec.rb +0 -752
- data/spec/functional/inflect_spec.rb +0 -85
- data/spec/functional/integration_spec.rb +0 -205
- data/spec/functional/platform_spec.rb +0 -501
- data/spec/functional/search_spec.rb +0 -187
- data/spec/functional/sort_spec.rb +0 -61
- data/spec/functional/utilities_spec.rb +0 -277
@@ -0,0 +1,112 @@
|
|
1
|
+
module Functional
|
2
|
+
|
3
|
+
# Supplies type-checking helpers whenever included.
|
4
|
+
#
|
5
|
+
# @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor/TypeCheck.html TypeCheck in Concurrent Ruby
|
6
|
+
module TypeCheck
|
7
|
+
|
8
|
+
# Performs an `is_a?` check of the given value object against the
|
9
|
+
# given list of modules and/or classes.
|
10
|
+
#
|
11
|
+
# @param [Object] value the object to interrogate
|
12
|
+
# @param [Module] types zero or more modules and/or classes to check
|
13
|
+
# the value against
|
14
|
+
# @return [Boolean] true on success
|
15
|
+
def Type?(value, *types)
|
16
|
+
types.any? { |t| value.is_a? t }
|
17
|
+
end
|
18
|
+
module_function :Type?
|
19
|
+
|
20
|
+
# Performs an `is_a?` check of the given value object against the
|
21
|
+
# given list of modules and/or classes. Raises an exception on failure.
|
22
|
+
#
|
23
|
+
# @param [Object] value the object to interrogate
|
24
|
+
# @param [Module] types zero or more modules and/or classes to check
|
25
|
+
# the value against
|
26
|
+
# @return [Object] the value object
|
27
|
+
#
|
28
|
+
# @raise [Functional::TypeError] when the check fails
|
29
|
+
def Type!(value, *types)
|
30
|
+
Type?(value, *types) or
|
31
|
+
TypeCheck.error(value, 'is not', types)
|
32
|
+
value
|
33
|
+
end
|
34
|
+
module_function :Type!
|
35
|
+
|
36
|
+
# Is the given value object is an instance of or descendant of
|
37
|
+
# one of the classes/modules in the given list?
|
38
|
+
#
|
39
|
+
# Performs the check using the `===` operator.
|
40
|
+
#
|
41
|
+
# @param [Object] value the object to interrogate
|
42
|
+
# @param [Module] types zero or more modules and/or classes to check
|
43
|
+
# the value against
|
44
|
+
# @return [Boolean] true on success
|
45
|
+
def Match?(value, *types)
|
46
|
+
types.any? { |t| t === value }
|
47
|
+
end
|
48
|
+
module_function :Match?
|
49
|
+
|
50
|
+
# Is the given value object is an instance of or descendant of
|
51
|
+
# one of the classes/modules in the given list? Raises an exception
|
52
|
+
# on failure.
|
53
|
+
#
|
54
|
+
# Performs the check using the `===` operator.
|
55
|
+
#
|
56
|
+
# @param [Object] value the object to interrogate
|
57
|
+
# @param [Module] types zero or more modules and/or classes to check
|
58
|
+
# the value against
|
59
|
+
# @return [Object] the value object
|
60
|
+
#
|
61
|
+
# @raise [Functional::TypeError] when the check fails
|
62
|
+
def Match!(value, *types)
|
63
|
+
Match?(value, *types) or
|
64
|
+
TypeCheck.error(value, 'is not matching', types)
|
65
|
+
value
|
66
|
+
end
|
67
|
+
module_function :Match!
|
68
|
+
|
69
|
+
# Is the given class a subclass or exact match of one or more
|
70
|
+
# of the modules and/or classes in the given list?
|
71
|
+
#
|
72
|
+
# @param [Class] value the class to interrogate
|
73
|
+
# @param [Class] types zero or more classes to check the value against
|
74
|
+
# the value against
|
75
|
+
# @return [Boolean] true on success
|
76
|
+
def Child?(value, *types)
|
77
|
+
Type?(value, Class) &&
|
78
|
+
types.any? { |t| value <= t }
|
79
|
+
end
|
80
|
+
module_function :Child?
|
81
|
+
|
82
|
+
# Is the given class a subclass or exact match of one or more
|
83
|
+
# of the modules and/or classes in the given list?
|
84
|
+
#
|
85
|
+
# @param [Class] value the class to interrogate
|
86
|
+
# @param [Class] types zero or more classes to check the value against
|
87
|
+
# @return [Class] the value class
|
88
|
+
#
|
89
|
+
# @raise [Functional::TypeError] when the check fails
|
90
|
+
def Child!(value, *types)
|
91
|
+
Child?(value, *types) or
|
92
|
+
TypeCheck.error(value, 'is not child', types)
|
93
|
+
value
|
94
|
+
end
|
95
|
+
module_function :Child!
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# Create a {Functional::TypeError} object from the given data.
|
100
|
+
#
|
101
|
+
# @param [Object] value the class/method that was being interrogated
|
102
|
+
# @param [String] message the message fragment to inject into the error
|
103
|
+
# @param [Object] types list of modules and/or classes that were being
|
104
|
+
# checked against the value object
|
105
|
+
#
|
106
|
+
# @raise [Functional::TypeError] the formatted exception object
|
107
|
+
def self.error(value, message, types)
|
108
|
+
raise TypeError,
|
109
|
+
"Value (#{value.class}) '#{value}' #{message} any of: #{types.join('; ')}."
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require_relative 'abstract_struct'
|
2
|
+
|
3
|
+
module Functional
|
4
|
+
|
5
|
+
# An immutable data structure with multiple fields, only one of which
|
6
|
+
# can be set at any given time. A `Union` is a convenient way to bundle a
|
7
|
+
# number of field attributes together, using accessor methods, without having
|
8
|
+
# to write an explicit class.
|
9
|
+
#
|
10
|
+
# The `Union` module generates new `AbstractStruct` subclasses that hold a set of
|
11
|
+
# fields with one and only one value associated with a single field. For each
|
12
|
+
# field a reader method is created along with a predicate and a factory. The
|
13
|
+
# predicate method indicates whether or not the give field is set. The reader
|
14
|
+
# method returns the value of that field or `nil` when not set. The factory
|
15
|
+
# creates a new union with the appropriate field set with the given value.
|
16
|
+
#
|
17
|
+
# A `Union` is very similar to a Ruby `Struct` and shares many of its behaviors
|
18
|
+
# and attributes. Where a `Struct` can have zero or more values, each of which is
|
19
|
+
# assiciated with a field, a `Union` can have one and only one value. Unlike a
|
20
|
+
# Ruby `Struct`, a `Union` is immutable: its value is set at construction and
|
21
|
+
# it can never be changed. Divergence between the two classes derive from these
|
22
|
+
# two core differences.
|
23
|
+
#
|
24
|
+
# @example Creating a New Class
|
25
|
+
#
|
26
|
+
# LeftRightCenter = Functional::Union.new(:left, :right, :center) #=> LeftRightCenter
|
27
|
+
# LeftRightCenter.ancestors #=> [LeftRightCenter, Functional::AbstractStruct... ]
|
28
|
+
# LeftRightCenter.fields #=> [:left, :right, :center]
|
29
|
+
#
|
30
|
+
# prize = LeftRightCenter.right('One million dollars!') #=> #<union LeftRightCenter... >
|
31
|
+
# prize.fields #=> [:left, :right, :center]
|
32
|
+
# prize.values #=> [nil, "One million dollars!", nil]
|
33
|
+
#
|
34
|
+
# prize.left? #=> false
|
35
|
+
# prize.right? #=> true
|
36
|
+
# prize.center? #=> false
|
37
|
+
#
|
38
|
+
# prize.left #=> nil
|
39
|
+
# prize.right #=> "One million dollars!"
|
40
|
+
# prize.center #=> nil
|
41
|
+
#
|
42
|
+
# @example Registering a New Class with Union
|
43
|
+
#
|
44
|
+
# Functional::Union.new('Suit', :clubs, :diamonds, :hearts, :spades)
|
45
|
+
# #=> Functional::Union::Suit
|
46
|
+
#
|
47
|
+
# Functional::Union::Suit.hearts('Queen')
|
48
|
+
# #=> #<union Functional::Union::Suit :clubs=>nil, :diamonds=>nil, :hearts=>"Queen", :spades=>nil>
|
49
|
+
#
|
50
|
+
# @see Functional::AbstractStruct
|
51
|
+
# @see Functional::Union
|
52
|
+
# @see http://www.ruby-doc.org/core-2.1.2/Struct.html Ruby `Struct` class
|
53
|
+
# @see http://en.wikipedia.org/wiki/Union_type "Union type" on Wikipedia
|
54
|
+
#
|
55
|
+
# @!macro thread_safe_immutable_object
|
56
|
+
module Union
|
57
|
+
extend self
|
58
|
+
|
59
|
+
# Create a new union class with the given fields.
|
60
|
+
#
|
61
|
+
# @return [Functional::AbstractStruct] the new union subclass
|
62
|
+
# @raise [ArgumentError] no fields specified
|
63
|
+
def new(*fields)
|
64
|
+
raise ArgumentError.new('no fields provided') if fields.empty?
|
65
|
+
build(fields)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Use the given `AbstractStruct` class and build the methods necessary
|
71
|
+
# to support the given data fields.
|
72
|
+
#
|
73
|
+
# @param [Array] fields the list of symbolic names for all data fields
|
74
|
+
# @return [Functional::AbstractStruct] the union class
|
75
|
+
def build(fields)
|
76
|
+
union, fields = AbstractStruct.define_class(self, :union, fields)
|
77
|
+
union.private_class_method(:new)
|
78
|
+
define_properties(union)
|
79
|
+
define_initializer(union)
|
80
|
+
fields.each do |field|
|
81
|
+
define_reader(union, field)
|
82
|
+
define_predicate(union, field)
|
83
|
+
define_factory(union, field)
|
84
|
+
end
|
85
|
+
union
|
86
|
+
end
|
87
|
+
|
88
|
+
# Define the `field` and `value` attribute readers on the given union class.
|
89
|
+
#
|
90
|
+
# @param [Functional::AbstractStruct] union the new union class
|
91
|
+
# @return [Functional::AbstractStruct] the union class
|
92
|
+
def define_properties(union)
|
93
|
+
union.send(:attr_reader, :field)
|
94
|
+
union.send(:attr_reader, :value)
|
95
|
+
union
|
96
|
+
end
|
97
|
+
|
98
|
+
# Define a predicate method on the given union class for the given data field.
|
99
|
+
#
|
100
|
+
# @param [Functional::AbstractStruct] union the new union class
|
101
|
+
# @param [Symbol] field symbolic name of the current data field
|
102
|
+
# @return [Functional::AbstractStruct] the union class
|
103
|
+
def define_predicate(union, field)
|
104
|
+
union.send(:define_method, "#{field}?".to_sym) do
|
105
|
+
@field == field
|
106
|
+
end
|
107
|
+
union
|
108
|
+
end
|
109
|
+
|
110
|
+
# Define a reader method on the given union class for the given data field.
|
111
|
+
#
|
112
|
+
# @param [Functional::AbstractStruct] union the new union class
|
113
|
+
# @param [Symbol] field symbolic name of the current data field
|
114
|
+
# @return [Functional::AbstractStruct] the union class
|
115
|
+
def define_reader(union, field)
|
116
|
+
union.send(:define_method, field) do
|
117
|
+
send("#{field}?".to_sym) ? @value : nil
|
118
|
+
end
|
119
|
+
union
|
120
|
+
end
|
121
|
+
|
122
|
+
# Define an initializer method on the given union class.
|
123
|
+
#
|
124
|
+
# @param [Functional::AbstractStruct] union the new union class
|
125
|
+
# @return [Functional::AbstractStruct] the union class
|
126
|
+
def define_initializer(union)
|
127
|
+
union.send(:define_method, :initialize) do |field, value|
|
128
|
+
@field = field
|
129
|
+
@value = value
|
130
|
+
data = fields.reduce({}) do |memo, field|
|
131
|
+
memo[field] = ( field == @field ? @value : nil )
|
132
|
+
memo
|
133
|
+
end
|
134
|
+
set_data_hash(data)
|
135
|
+
set_values_array(data.values)
|
136
|
+
end
|
137
|
+
union
|
138
|
+
end
|
139
|
+
|
140
|
+
# Define a factory method on the given union class for the given data field.
|
141
|
+
#
|
142
|
+
# @param [Functional::AbstractStruct] union the new union class
|
143
|
+
# @param [Symbol] field symbolic name of the current data field
|
144
|
+
# @return [Functional::AbstractStruct] the union class
|
145
|
+
def define_factory(union, field)
|
146
|
+
union.class.send(:define_method, field) do |value|
|
147
|
+
new(field, value).freeze
|
148
|
+
end
|
149
|
+
union
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/functional/version.rb
CHANGED
@@ -0,0 +1,154 @@
|
|
1
|
+
shared_examples :abstract_struct do
|
2
|
+
|
3
|
+
specify { Functional::Protocol::Satisfy! struct_class, :Struct }
|
4
|
+
|
5
|
+
let(:other_struct) do
|
6
|
+
Class.new do
|
7
|
+
include Functional::AbstractStruct
|
8
|
+
self.fields = [:foo, :bar, :baz].freeze
|
9
|
+
self.datatype = :other_struct
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'field collection' do
|
14
|
+
|
15
|
+
it 'contains all possible fields' do
|
16
|
+
expected_fields.each do |field|
|
17
|
+
expect(struct_class.fields).to include(field)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'is frozen' do
|
22
|
+
expect(struct_class.fields).to be_frozen
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not overwrite fields for other structs' do
|
26
|
+
expect(struct_class.fields).to_not eq other_struct.fields
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'is the same when called on the class and on an object' do
|
30
|
+
expect(struct_class.fields).to eq struct_object.fields
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'readers' do
|
35
|
+
|
36
|
+
specify '#values returns all values in an array' do
|
37
|
+
expect(struct_object.values).to eq expected_values
|
38
|
+
end
|
39
|
+
|
40
|
+
specify '#values is frozen' do
|
41
|
+
expect(struct_object.values).to be_frozen
|
42
|
+
end
|
43
|
+
|
44
|
+
specify 'exist for each field' do
|
45
|
+
expected_fields.each do |field|
|
46
|
+
expect(struct_object).to respond_to(field)
|
47
|
+
expect(struct_object.method(field).arity).to eq 0
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
specify 'return the appropriate value all fields' do
|
52
|
+
expected_fields.each_with_index do |field, i|
|
53
|
+
expect(struct_object.send(field)).to eq expected_values[i]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'enumeration' do
|
59
|
+
|
60
|
+
specify '#each_pair with a block iterates over all fields and values' do
|
61
|
+
fields = []
|
62
|
+
values = []
|
63
|
+
|
64
|
+
struct_object.each_pair do |field, value|
|
65
|
+
fields << field
|
66
|
+
values << value
|
67
|
+
end
|
68
|
+
|
69
|
+
expect(fields).to eq struct_object.fields
|
70
|
+
expect(values).to eq struct_object.values
|
71
|
+
end
|
72
|
+
|
73
|
+
specify '#each_pair without a block returns an Enumerable' do
|
74
|
+
expect(struct_object.each_pair).to be_a Enumerable
|
75
|
+
end
|
76
|
+
|
77
|
+
specify '#each with a block iterates over all values' do
|
78
|
+
values = []
|
79
|
+
|
80
|
+
struct_object.each do |value|
|
81
|
+
values << value
|
82
|
+
end
|
83
|
+
|
84
|
+
expect(values).to eq struct_object.values
|
85
|
+
end
|
86
|
+
|
87
|
+
specify '#each without a block returns an Enumerable' do
|
88
|
+
expect(struct_object.each).to be_a Enumerable
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'reflection' do
|
93
|
+
|
94
|
+
specify 'always creates frozen objects' do
|
95
|
+
expect(struct_object).to be_frozen
|
96
|
+
end
|
97
|
+
|
98
|
+
specify 'asserts equality for two structs of the same class with equal values' do
|
99
|
+
other = struct_object.dup
|
100
|
+
|
101
|
+
expect(struct_object).to eq other
|
102
|
+
expect(struct_object).to eql other
|
103
|
+
end
|
104
|
+
|
105
|
+
specify 'rejects equality for two structs of different classes' do
|
106
|
+
other = Struct.new(*expected_fields).new(*expected_values)
|
107
|
+
|
108
|
+
expect(struct_object).to_not eq other
|
109
|
+
expect(struct_object).to_not eql other
|
110
|
+
end
|
111
|
+
|
112
|
+
specify 'rejects equality for two structs of the same class with different values' do
|
113
|
+
expect(struct_object).to_not eq other_object
|
114
|
+
expect(struct_object).to_not eql other_struct
|
115
|
+
end
|
116
|
+
|
117
|
+
specify '#to_h returns a Hash with all field/value pairs' do
|
118
|
+
hsh = struct_object.to_h
|
119
|
+
|
120
|
+
expect(hsh.keys).to eq struct_object.fields
|
121
|
+
expect(hsh.values).to eq struct_object.values
|
122
|
+
end
|
123
|
+
|
124
|
+
specify '#inspect result is enclosed in brackets' do
|
125
|
+
expect(struct_object.inspect).to match(/^#</)
|
126
|
+
expect(struct_object.inspect).to match(/>$/)
|
127
|
+
end
|
128
|
+
|
129
|
+
specify '#inspect result has lowercase class name as first element' do
|
130
|
+
struct = described_class.to_s.split('::').last.downcase
|
131
|
+
expect(struct_object.inspect).to match(/^#<#{struct} /)
|
132
|
+
end
|
133
|
+
|
134
|
+
specify '#inspect includes all field/value pairs' do
|
135
|
+
struct_object.fields.each_with_index do |field, i|
|
136
|
+
value_regex = "\"?#{struct_object.values[i]}\"?"
|
137
|
+
expect(struct_object.inspect).to match(/:#{field}=>#{value_regex}/)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
specify '#inspect is aliased as #to_s' do
|
142
|
+
expect(struct_object.inspect).to eq struct_object.to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
specify '#length returns the number of fields' do
|
146
|
+
expect(struct_object.length).to eq struct_class.fields.length
|
147
|
+
expect(struct_object.length).to eq expected_fields.length
|
148
|
+
end
|
149
|
+
|
150
|
+
specify 'aliases #length as #size' do
|
151
|
+
expect(struct_object.length).to eq struct_object.size
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
class Bar
|
5
|
+
def greet
|
6
|
+
return 'Hello, World!'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Foo < Bar
|
11
|
+
include Functional::PatternMatching
|
12
|
+
|
13
|
+
attr_accessor :name
|
14
|
+
|
15
|
+
defn(:initialize) { @name = 'baz' }
|
16
|
+
defn(:initialize, _) {|name| @name = name.to_s }
|
17
|
+
|
18
|
+
defn(:greet, _) do |name|
|
19
|
+
"Hello, #{name}!"
|
20
|
+
end
|
21
|
+
|
22
|
+
defn(:greet, :male, _) { |name|
|
23
|
+
"Hello, Mr. #{name}!"
|
24
|
+
}
|
25
|
+
defn(:greet, :female, _) { |name|
|
26
|
+
"Hello, Ms. #{name}!"
|
27
|
+
}
|
28
|
+
defn(:greet, nil, _) { |name|
|
29
|
+
"Goodbye, #{name}!"
|
30
|
+
}
|
31
|
+
defn(:greet, _, _) { |_, name|
|
32
|
+
"Hello, #{name}!"
|
33
|
+
}
|
34
|
+
|
35
|
+
defn(:hashable, _, {foo: :bar}, _) { |_, opts, _|
|
36
|
+
:foo_bar
|
37
|
+
}
|
38
|
+
defn(:hashable, _, {foo: _, bar: _}, _) { |_, f, b, _|
|
39
|
+
[f, b]
|
40
|
+
}
|
41
|
+
defn(:hashable, _, {foo: _}, _) { |_, f, _|
|
42
|
+
f
|
43
|
+
}
|
44
|
+
defn(:hashable, _, {}, _) {
|
45
|
+
:empty
|
46
|
+
}
|
47
|
+
defn(:hashable, _, _, _) { |_, _, _|
|
48
|
+
:unbound
|
49
|
+
}
|
50
|
+
|
51
|
+
defn(:options, _) { |opts|
|
52
|
+
opts
|
53
|
+
}
|
54
|
+
|
55
|
+
defn(:recurse) {
|
56
|
+
'w00t!'
|
57
|
+
}
|
58
|
+
defn(:recurse, :match) {
|
59
|
+
recurse()
|
60
|
+
}
|
61
|
+
defn(:recurse, :super) {
|
62
|
+
greet()
|
63
|
+
}
|
64
|
+
defn(:recurse, :instance) {
|
65
|
+
@name
|
66
|
+
}
|
67
|
+
defn(:recurse, _) { |arg|
|
68
|
+
arg
|
69
|
+
}
|
70
|
+
|
71
|
+
defn(:concat, Integer, Integer) { |first, second|
|
72
|
+
first + second
|
73
|
+
}
|
74
|
+
defn(:concat, Integer, String) { |first, second|
|
75
|
+
"#{first} #{second}"
|
76
|
+
}
|
77
|
+
defn(:concat, String, String) { |first, second|
|
78
|
+
first + second
|
79
|
+
}
|
80
|
+
defn(:concat, Integer, UNBOUND) { |first, second|
|
81
|
+
first + second.to_i
|
82
|
+
}
|
83
|
+
|
84
|
+
defn(:all, :one, ALL) { |args|
|
85
|
+
args
|
86
|
+
}
|
87
|
+
defn(:all, :one, Integer, ALL) { |int, args|
|
88
|
+
[int, args]
|
89
|
+
}
|
90
|
+
defn(:all, 1, _, ALL) { |var, args|
|
91
|
+
[var, args]
|
92
|
+
}
|
93
|
+
defn(:all, ALL) { | args|
|
94
|
+
args
|
95
|
+
}
|
96
|
+
|
97
|
+
defn(:old_enough, _){ true }.when{|x| x >= 16 }
|
98
|
+
defn(:old_enough, _){ false }
|
99
|
+
|
100
|
+
defn(:right_age, _) {
|
101
|
+
true
|
102
|
+
}.when{|x| x >= 16 && x <= 104 }
|
103
|
+
|
104
|
+
defn(:right_age, _) {
|
105
|
+
false
|
106
|
+
}
|
107
|
+
|
108
|
+
defn(:wrong_age, _) {
|
109
|
+
true
|
110
|
+
}.when{|x| x < 16 || x > 104 }
|
111
|
+
|
112
|
+
defn(:wrong_age, _) {
|
113
|
+
false
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
class Baz < Foo
|
118
|
+
def boom_boom_room
|
119
|
+
'zoom zoom zoom'
|
120
|
+
end
|
121
|
+
def who(first, last)
|
122
|
+
[first, last].join(' ')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Fizzbuzz < Baz
|
127
|
+
include Functional::PatternMatching
|
128
|
+
defn(:who, Integer) { |count|
|
129
|
+
(1..count).each.reduce(:+)
|
130
|
+
}
|
131
|
+
defn(:who) { 0 }
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'complex pattern matching' do
|
135
|
+
|
136
|
+
let(:name) { 'Pattern Matcher' }
|
137
|
+
subject { Foo.new(name) }
|
138
|
+
|
139
|
+
specify { expect(subject.greet).to eq 'Hello, World!' }
|
140
|
+
|
141
|
+
specify { expect(subject.greet('Jerry')).to eq 'Hello, Jerry!' }
|
142
|
+
|
143
|
+
specify { expect(subject.greet(:male, 'Jerry')).to eq 'Hello, Mr. Jerry!' }
|
144
|
+
specify { expect(subject.greet(:female, 'Jeri')).to eq 'Hello, Ms. Jeri!' }
|
145
|
+
specify { expect(subject.greet(:unknown, 'Jerry')).to eq 'Hello, Jerry!' }
|
146
|
+
specify { expect(subject.greet(nil, 'Jerry')).to eq 'Goodbye, Jerry!' }
|
147
|
+
specify {
|
148
|
+
expect { Foo.new.greet(1,2,3,4,5,6,7) }.to raise_error(NoMethodError)
|
149
|
+
}
|
150
|
+
|
151
|
+
specify { expect(subject.options(bar: :baz, one: 1, many: 2)).to eq({bar: :baz, one: 1, many: 2}) }
|
152
|
+
|
153
|
+
specify { expect(subject.hashable(:male, {foo: :bar}, :female)).to eq :foo_bar }
|
154
|
+
specify { expect(subject.hashable(:male, {foo: :baz}, :female)).to eq :baz }
|
155
|
+
specify { expect(subject.hashable(:male, {foo: 1, bar: 2}, :female)).to eq [1, 2] }
|
156
|
+
specify { expect(subject.hashable(:male, {foo: 1, baz: 2}, :female)).to eq 1 }
|
157
|
+
specify { expect(subject.hashable(:male, {bar: :baz}, :female)).to eq :unbound }
|
158
|
+
specify { expect(subject.hashable(:male, {}, :female)).to eq :empty }
|
159
|
+
|
160
|
+
specify { expect(subject.recurse).to eq 'w00t!' }
|
161
|
+
specify { expect(subject.recurse(:match)).to eq 'w00t!' }
|
162
|
+
specify { expect(subject.recurse(:super)).to eq 'Hello, World!' }
|
163
|
+
specify { expect(subject.recurse(:instance)).to eq name }
|
164
|
+
specify { expect(subject.recurse(:foo)).to eq :foo }
|
165
|
+
|
166
|
+
specify { expect(subject.concat(1, 1)).to eq 2 }
|
167
|
+
specify { expect(subject.concat(1, 'shoe')).to eq '1 shoe' }
|
168
|
+
specify { expect(subject.concat('shoe', 'fly')).to eq 'shoefly' }
|
169
|
+
specify { expect(subject.concat(1, 2.9)).to eq 3 }
|
170
|
+
|
171
|
+
specify { expect(subject.all(:one, 'a', 'bee', :see)).to eq(['a', 'bee', :see]) }
|
172
|
+
specify { expect(subject.all(:one, 1, 'bee', :see)).to eq([1, 'bee', :see]) }
|
173
|
+
specify { expect(subject.all(1, 'a', 'bee', :see)).to eq(['a', ['bee', :see]]) }
|
174
|
+
specify { expect(subject.all('a', 'bee', :see)).to eq(['a', 'bee', :see]) }
|
175
|
+
specify { expect { subject.all }.to raise_error(NoMethodError) }
|
176
|
+
|
177
|
+
specify { expect(subject.old_enough(20)).to be true }
|
178
|
+
specify { expect(subject.old_enough(10)).to be false }
|
179
|
+
|
180
|
+
specify { expect(subject.right_age(20)).to be true }
|
181
|
+
specify { expect(subject.right_age(10)).to be false }
|
182
|
+
specify { expect(subject.right_age(110)).to be false }
|
183
|
+
|
184
|
+
specify { expect(subject.wrong_age(20)).to be false }
|
185
|
+
specify { expect(subject.wrong_age(10)).to be true }
|
186
|
+
specify { expect(subject.wrong_age(110)).to be true }
|
187
|
+
|
188
|
+
context 'inheritance' do
|
189
|
+
|
190
|
+
specify { expect(Fizzbuzz.new.greet(:male, 'Jerry')).to eq 'Hello, Mr. Jerry!' }
|
191
|
+
specify { expect(Fizzbuzz.new.greet(:female, 'Jeri')).to eq 'Hello, Ms. Jeri!' }
|
192
|
+
specify { expect(Fizzbuzz.new.greet(:unknown, 'Jerry')).to eq 'Hello, Jerry!' }
|
193
|
+
specify { expect(Fizzbuzz.new.greet(nil, 'Jerry')).to eq 'Goodbye, Jerry!' }
|
194
|
+
|
195
|
+
specify { expect(Fizzbuzz.new.who(5)).to eq 15 }
|
196
|
+
specify { expect(Fizzbuzz.new.who()).to eq 0 }
|
197
|
+
specify {
|
198
|
+
expect {
|
199
|
+
Fizzbuzz.new.who('Jerry', 'secret middle name', "D'Antonio")
|
200
|
+
}.to raise_error(NoMethodError)
|
201
|
+
}
|
202
|
+
|
203
|
+
specify { expect(Fizzbuzz.new.boom_boom_room).to eq 'zoom zoom zoom' }
|
204
|
+
end
|
205
|
+
end
|