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,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
@@ -1,3 +1,5 @@
1
1
  module Functional
2
- VERSION = '0.7.7'
2
+
3
+ # The current gem version.
4
+ VERSION = '1.0.0'
3
5
  end
@@ -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