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.
- 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
|