functional-ruby 0.7.7 → 1.0.0

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