functional-ruby 1.1.0 → 1.2.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +14 -12
  4. data/doc/memo.md +192 -0
  5. data/doc/pattern_matching.md +481 -0
  6. data/doc/protocol.md +219 -0
  7. data/doc/record.md +247 -0
  8. data/lib/functional/abstract_struct.rb +8 -8
  9. data/lib/functional/delay.rb +31 -38
  10. data/lib/functional/either.rb +48 -45
  11. data/lib/functional/final_struct.rb +23 -34
  12. data/lib/functional/final_var.rb +20 -21
  13. data/lib/functional/memo.rb +33 -24
  14. data/lib/functional/method_signature.rb +1 -2
  15. data/lib/functional/option.rb +7 -7
  16. data/lib/functional/pattern_matching.rb +12 -10
  17. data/lib/functional/protocol.rb +2 -4
  18. data/lib/functional/protocol_info.rb +5 -3
  19. data/lib/functional/record.rb +82 -16
  20. data/lib/functional/synchronization.rb +88 -0
  21. data/lib/functional/tuple.rb +14 -4
  22. data/lib/functional/type_check.rb +0 -2
  23. data/lib/functional/union.rb +5 -4
  24. data/lib/functional/value_struct.rb +5 -3
  25. data/lib/functional/version.rb +1 -1
  26. data/spec/functional/complex_pattern_matching_spec.rb +1 -2
  27. data/spec/functional/configuration_spec.rb +0 -2
  28. data/spec/functional/delay_spec.rb +0 -2
  29. data/spec/functional/either_spec.rb +0 -1
  30. data/spec/functional/final_struct_spec.rb +0 -1
  31. data/spec/functional/final_var_spec.rb +0 -2
  32. data/spec/functional/memo_spec.rb +7 -10
  33. data/spec/functional/option_spec.rb +0 -1
  34. data/spec/functional/pattern_matching_spec.rb +0 -1
  35. data/spec/functional/protocol_info_spec.rb +0 -2
  36. data/spec/functional/protocol_spec.rb +1 -3
  37. data/spec/functional/record_spec.rb +170 -87
  38. data/spec/functional/tuple_spec.rb +0 -1
  39. data/spec/functional/type_check_spec.rb +0 -2
  40. data/spec/functional/union_spec.rb +0 -1
  41. data/spec/functional/value_struct_spec.rb +0 -1
  42. metadata +14 -29
  43. data/doc/memo.txt +0 -192
  44. data/doc/pattern_matching.txt +0 -485
  45. data/doc/protocol.txt +0 -221
  46. data/doc/record.txt +0 -207
  47. data/doc/thread_safety.txt +0 -17
@@ -0,0 +1,88 @@
1
+ module Functional
2
+
3
+ # @!visibility private
4
+ #
5
+ # Based on work originally done by Petr Chalupa (@pitr-ch) in Concurrent Ruby.
6
+ # https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent/synchronization/object.rb
7
+ module Synchronization
8
+
9
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
10
+
11
+ require 'jruby'
12
+
13
+ # @!visibility private
14
+ class Object
15
+
16
+ # @!visibility private
17
+ def initialize(*args)
18
+ end
19
+
20
+ protected
21
+
22
+ # @!visibility private
23
+ def synchronize
24
+ JRuby.reference0(self).synchronized { yield }
25
+ end
26
+
27
+ # @!visibility private
28
+ def ensure_ivar_visibility!
29
+ # relying on undocumented behavior of JRuby, ivar access is volatile
30
+ end
31
+ end
32
+
33
+ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
34
+
35
+ # @!visibility private
36
+ class Object
37
+
38
+ # @!visibility private
39
+ def initialize(*args)
40
+ end
41
+
42
+ protected
43
+
44
+ # @!visibility private
45
+ def synchronize(&block)
46
+ Rubinius.synchronize(self, &block)
47
+ end
48
+
49
+ # @!visibility private
50
+ def ensure_ivar_visibility!
51
+ # Rubinius instance variables are not volatile so we need to insert barrier
52
+ Rubinius.memory_barrier
53
+ end
54
+ end
55
+
56
+ else
57
+
58
+ require 'thread'
59
+
60
+ # @!visibility private
61
+ class Object
62
+
63
+ # @!visibility private
64
+ def initialize(*args)
65
+ @__lock__ = ::Mutex.new
66
+ @__condition__ = ::ConditionVariable.new
67
+ end
68
+
69
+ protected
70
+
71
+ # @!visibility private
72
+ def synchronize
73
+ if @__lock__.owned?
74
+ yield
75
+ else
76
+ @__lock__.synchronize { yield }
77
+ end
78
+ end
79
+
80
+ # @!visibility private
81
+ def ensure_ivar_visibility!
82
+ # relying on undocumented behavior of CRuby, GVL acquire has lock which ensures visibility of ivars
83
+ # https://github.com/ruby/ruby/blob/ruby_2_2/thread_pthread.c#L204-L211
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,3 +1,5 @@
1
+ require 'functional/synchronization'
2
+
1
3
  module Functional
2
4
 
3
5
  # A tuple is a pure functional data strcture that is similar to an array but is
@@ -9,8 +11,6 @@ module Functional
9
11
  # contains, the less efficient it will become. A future version will use a fast,
10
12
  # immutable, persistent data structure such as a finger tree or a trie.
11
13
  #
12
- # @since 1.1.0
13
- #
14
14
  # @see http://en.wikipedia.org/wiki/Tuple
15
15
  # @see http://msdn.microsoft.com/en-us/library/system.tuple.aspx
16
16
  # @see http://www.tutorialspoint.com/python/python_tuples.htm
@@ -20,8 +20,16 @@ module Functional
20
20
  # @see http://www.erlang.org/doc/man/erlang.html#make_tuple-2
21
21
  # @see http://en.wikibooks.org/wiki/Haskell/Lists_and_tuples#Tuples
22
22
  #
23
- # @!macro thread_safe_immutable_object
24
- class Tuple
23
+ # @!macro [new] thread_safe_immutable_object
24
+ #
25
+ # @note This is a write-once, read-many, thread safe object that can be
26
+ # used in concurrent systems. Thread safety guarantees *cannot* be made
27
+ # about objects contained *within* this object, however. Ruby variables
28
+ # are mutable references to mutable objects. This cannot be changed. The
29
+ # best practice it to only encapsulate immutable, frozen, or thread safe
30
+ # objects. Ultimately, thread safety is the responsibility of the
31
+ # programmer.
32
+ class Tuple < Synchronization::Object
25
33
 
26
34
  # Create a new tuple with the given data items in the given order.
27
35
  #
@@ -29,8 +37,10 @@ module Functional
29
37
  # @raise [ArgumentError] if data is not an array or does not implement `to_a`
30
38
  def initialize(data = [])
31
39
  raise ArgumentError.new('data is not an array') unless data.respond_to?(:to_a)
40
+ super
32
41
  @data = data.to_a.dup.freeze
33
42
  self.freeze
43
+ ensure_ivar_visibility!
34
44
  end
35
45
 
36
46
  # Retrieve the item at the given index. Indices begin at zero and increment
@@ -3,8 +3,6 @@ module Functional
3
3
  # Supplies type-checking helpers whenever included.
4
4
  #
5
5
  # @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor/TypeCheck.html TypeCheck in Concurrent Ruby
6
- #
7
- # @since 1.0.0
8
6
  module TypeCheck
9
7
 
10
8
  # Performs an `is_a?` check of the given value object against the
@@ -1,4 +1,5 @@
1
- require_relative 'abstract_struct'
1
+ require 'functional/abstract_struct'
2
+ require 'functional/synchronization'
2
3
 
3
4
  module Functional
4
5
 
@@ -47,13 +48,10 @@ module Functional
47
48
  # Functional::Union::Suit.hearts('Queen')
48
49
  # #=> #<union Functional::Union::Suit :clubs=>nil, :diamonds=>nil, :hearts=>"Queen", :spades=>nil>
49
50
  #
50
- # @see Functional::AbstractStruct
51
51
  # @see Functional::Union
52
52
  # @see http://www.ruby-doc.org/core-2.1.2/Struct.html Ruby `Struct` class
53
53
  # @see http://en.wikipedia.org/wiki/Union_type "Union type" on Wikipedia
54
54
  #
55
- # @since 1.0.0
56
- #
57
55
  # @!macro thread_safe_immutable_object
58
56
  module Union
59
57
  extend self
@@ -127,6 +125,7 @@ module Functional
127
125
  # @return [Functional::AbstractStruct] the union class
128
126
  def define_initializer(union)
129
127
  union.send(:define_method, :initialize) do |field, value|
128
+ super()
130
129
  @field = field
131
130
  @value = value
132
131
  data = fields.reduce({}) do |memo, field|
@@ -135,6 +134,8 @@ module Functional
135
134
  end
136
135
  set_data_hash(data)
137
136
  set_values_array(data.values)
137
+ ensure_ivar_visibility!
138
+ self.freeze
138
139
  end
139
140
  union
140
141
  end
@@ -1,3 +1,5 @@
1
+ require 'functional/synchronization'
2
+
1
3
  module Functional
2
4
 
3
5
  # A variation on Ruby's `OpenStruct` in which all fields are immutable and
@@ -18,22 +20,22 @@ module Functional
18
20
  # name.last? #=> true
19
21
  # name.middle? #=> false
20
22
  #
21
- # @since 1.1.0
22
- #
23
23
  # @see Functional::Record
24
24
  # @see Functional::FinalStruct
25
25
  # @see http://www.ruby-doc.org/stdlib-2.1.2/libdoc/ostruct/rdoc/OpenStruct.html
26
26
  #
27
27
  # @!macro thread_safe_immutable_object
28
- class ValueStruct
28
+ class ValueStruct < Synchronization::Object
29
29
 
30
30
  def initialize(attributes)
31
31
  raise ArgumentError.new('attributes must be given as a hash') unless attributes.respond_to?(:each_pair)
32
+ super
32
33
  @attribute_hash = {}
33
34
  attributes.each_pair do |field, value|
34
35
  set_attribute(field, value)
35
36
  end
36
37
  @attribute_hash.freeze
38
+ ensure_ivar_visibility!
37
39
  self.freeze
38
40
  end
39
41
 
@@ -1,5 +1,5 @@
1
1
  module Functional
2
2
 
3
3
  # The current gem version.
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  end
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require 'ostruct'
3
2
 
4
3
  class Bar
@@ -194,7 +193,7 @@ describe 'complex pattern matching' do
194
193
 
195
194
  specify { expect(Fizzbuzz.new.who(5)).to eq 15 }
196
195
  specify { expect(Fizzbuzz.new.who()).to eq 0 }
197
- specify {
196
+ specify {
198
197
  expect {
199
198
  Fizzbuzz.new.who('Jerry', 'secret middle name', "D'Antonio")
200
199
  }.to raise_error(NoMethodError)
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  module Functional
4
2
 
5
3
  context Configuration do
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  module Functional
4
2
 
5
3
  describe Delay do
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require_relative 'abstract_struct_shared'
3
2
 
4
3
  module Functional
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require 'ostruct'
3
2
 
4
3
  module Functional
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  module Functional
4
2
 
5
3
  describe FinalVar do
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  module Functional
4
2
 
5
3
  describe Memo do
@@ -163,30 +161,29 @@ module Functional
163
161
 
164
162
  context 'thread safety' do
165
163
 
166
- let(:mutex){ Mutex.new }
164
+ let(:memoizer_factory){ Functional::Memo::ClassMethods.const_get(:Memoizer) }
165
+ let(:memoizer){ memoizer_factory.new(:func, 0) }
167
166
 
168
167
  before(:each) do
169
- allow(Mutex).to receive(:new).with(no_args).and_return(mutex)
170
- allow(mutex).to receive(:lock).with(no_args).and_return(mutex)
171
- allow(mutex).to receive(:unlock).with(no_args).and_return(mutex)
168
+ allow(memoizer_factory).to receive(:new).with(any_args).and_return(memoizer)
172
169
  end
173
170
 
174
171
  it 'locks a mutex whenever a memoized function is called' do
175
- expect(mutex).to receive(:lock).exactly(:once).with(no_args)
172
+ expect(memoizer).to receive(:synchronize).exactly(:once).with(no_args)
176
173
 
177
174
  subject.memoize(:increment)
178
175
  subject.increment(0)
179
176
  end
180
177
 
181
178
  it 'unlocks the mutex whenever a memoized function is called' do
182
- expect(mutex).to receive(:unlock).exactly(:once).with(no_args)
179
+ expect(memoizer).to receive(:synchronize).exactly(:once).with(no_args)
183
180
 
184
181
  subject.memoize(:increment)
185
182
  subject.increment(0)
186
183
  end
187
184
 
188
185
  it 'unlocks the mutex when the method call raises an exception' do
189
- expect(mutex).to receive(:unlock).exactly(:once).with(no_args)
186
+ expect(memoizer).to receive(:synchronize).exactly(:once).with(no_args)
190
187
 
191
188
  subject.memoize(:exception)
192
189
  begin
@@ -197,7 +194,7 @@ module Functional
197
194
  end
198
195
 
199
196
  it 'uses different mutexes for different functions' do
200
- expect(Mutex).to receive(:new).with(no_args).exactly(3).times.and_return(mutex)
197
+ expect(memoizer_factory).to receive(:new).with(any_args).exactly(3).times.and_return(memoizer)
201
198
  # once for memoize(:add) in the definition
202
199
  subject.memoize(:increment)
203
200
  subject.memoize(:exception)
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require_relative 'abstract_struct_shared'
3
2
  require 'securerandom'
4
3
 
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require 'ostruct'
3
2
 
4
3
  module Functional
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  module Functional
4
2
 
5
3
  describe ProtocolInfo do
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe 'protocol specification' do
4
2
 
5
3
  before(:each) do
@@ -96,7 +94,7 @@ describe 'protocol specification' do
96
94
  end
97
95
 
98
96
  expect(
99
- Functional::Protocol.Satisfy?('object', :foo)
97
+ Functional::Protocol.Satisfy?(clazz.new, :foo)
100
98
  ).to be false
101
99
  end
102
100
 
@@ -1,5 +1,5 @@
1
- require 'spec_helper'
2
1
  require_relative 'abstract_struct_shared'
2
+ require 'securerandom'
3
3
 
4
4
  module Functional
5
5
 
@@ -16,12 +16,49 @@ module Functional
16
16
 
17
17
  context 'definition' do
18
18
 
19
- it 'registers the new class with Record when given a string name' do
20
- Record.new('Foo', :foo, :bar, :baz)
21
- expect(defined?(Record::Foo)).to eq 'constant'
19
+ it 'does not register a new class when no name is given' do
20
+ Record.new(:foo, :bar, :baz)
21
+ expect(defined?(Record::Foo)).to be_falsey
22
22
  end
23
23
 
24
- it 'default all fields values to nil' do
24
+ it 'creates a new class when given an array of field names' do
25
+ clazz = Record.new(:foo, :bar, :baz)
26
+ expect(clazz).to be_a Class
27
+ expect(clazz.ancestors).to include(Functional::AbstractStruct)
28
+ end
29
+
30
+ it 'registers the new class with Record when given a string name and an array' do
31
+ Record.new('Bar', :foo, :bar, :baz)
32
+ expect(defined?(Record::Bar)).to eq 'constant'
33
+ end
34
+
35
+ it 'creates a new class when given a hash of field names and types/protocols' do
36
+ clazz = Record.new(foo: String, bar: String, baz: String)
37
+ expect(clazz).to be_a Class
38
+ expect(clazz.ancestors).to include(Functional::AbstractStruct)
39
+ end
40
+
41
+ it 'registers the new class with Record when given a string name and a hash' do
42
+ Record.new('Boom', foo: String, bar: String, baz: String)
43
+ expect(defined?(Record::Boom)).to eq 'constant'
44
+ end
45
+
46
+ it 'raises an exception when given a hash with an invalid type/protocol' do
47
+ expect {
48
+ Record.new(foo: 'String', bar: String, baz: String)
49
+ }.to raise_error(ArgumentError)
50
+ end
51
+
52
+ it 'raises an exception when given an invalid definition' do
53
+ expect {
54
+ Record.new(:foo, bar: String, baz: String)
55
+ }.to raise_error(ArgumentError)
56
+ end
57
+ end
58
+
59
+ context 'initialization' do
60
+
61
+ it 'sets all fields values to nil' do
25
62
  fields = [:foo, :bar, :baz]
26
63
  clazz = Record.new(*fields)
27
64
 
@@ -41,74 +78,156 @@ module Functional
41
78
  expect(record.baz).to eq 3
42
79
  end
43
80
 
44
- it 'defaults fields to values given during class creation' do
45
- clazz = Record.new(:foo, :bar, :baz) do
46
- default :foo, 42
47
- default :bar, 'w00t!'
81
+ context 'with default values' do
82
+
83
+ it 'defaults fields to values given during class creation' do
84
+ clazz = Record.new(:foo, :bar, :baz) do
85
+ default :foo, 42
86
+ default :bar, 'w00t!'
87
+ end
88
+
89
+ record = clazz.new
90
+ expect(record.foo).to eq 42
91
+ expect(record.bar).to eq 'w00t!'
92
+ expect(record.baz).to be_nil
48
93
  end
49
94
 
50
- record = clazz.new
51
- expect(record.foo).to eq 42
52
- expect(record.bar).to eq 'w00t!'
53
- expect(record.baz).to be_nil
54
- end
95
+ it 'overrides default values with values provided at object construction' do
96
+ clazz = Record.new(:foo, :bar, :baz) do
97
+ default :foo, 42
98
+ default :bar, 'w00t!'
99
+ default :baz, :bogus
100
+ end
55
101
 
56
- it 'overrides default values with values provided at object construction' do
57
- clazz = Record.new(:foo, :bar, :baz) do
58
- default :foo, 42
59
- default :bar, 'w00t!'
60
- default :baz, :bogus
102
+ record = clazz.new(foo: 1, bar: 2)
103
+
104
+ expect(record.foo).to eq 1
105
+ expect(record.bar).to eq 2
106
+ expect(record.baz).to eq :bogus
61
107
  end
62
108
 
63
- record = clazz.new(foo: 1, bar: 2)
109
+ it 'duplicates default values when assigning to a new object' do
110
+ original = 'Foo'
111
+ clazz = Record.new(:foo, :bar, :baz) do
112
+ default :foo, original
113
+ end
64
114
 
65
- expect(record.foo).to eq 1
66
- expect(record.bar).to eq 2
67
- expect(record.baz).to eq :bogus
115
+ record = clazz.new
116
+ expect(record.foo).to eq original
117
+ expect(record.foo.object_id).to_not eql original.object_id
118
+ end
119
+
120
+ it 'does not conflate defaults across record classes' do
121
+ clazz_foo = Record.new(:foo, :bar, :baz) do
122
+ default :foo, 42
123
+ end
124
+
125
+ clazz_matz = Record.new(:foo, :bar, :baz) do
126
+ default :foo, 'Matsumoto'
127
+ end
128
+
129
+ expect(clazz_foo.new.foo).to eq 42
130
+ expect(clazz_matz.new.foo).to eq 'Matsumoto'
131
+ end
68
132
  end
69
133
 
70
- it 'duplicates default values when assigning to a new object' do
71
- original = 'Foo'
72
- clazz = Record.new(:foo, :bar, :baz) do
73
- default :foo, original
134
+ context 'with mandatory fields' do
135
+
136
+ it 'raises an exception when values for requred field are not provided' do
137
+ clazz = Record.new(:foo, :bar, :baz) do
138
+ mandatory :foo
139
+ end
140
+
141
+ expect {
142
+ clazz.new(bar: 1)
143
+ }.to raise_exception(ArgumentError)
74
144
  end
75
145
 
76
- record = clazz.new
77
- expect(record.foo).to eq original
78
- expect(record.foo.object_id).to_not eql original.object_id
79
- end
146
+ it 'raises an exception when required values are nil' do
147
+ clazz = Record.new(:foo, :bar, :baz) do
148
+ mandatory :foo
149
+ end
80
150
 
81
- it 'does not conflate defaults across record classes' do
82
- clazz_foo = Record.new(:foo, :bar, :baz) do
83
- default :foo, 42
151
+ expect {
152
+ clazz.new(foo: nil, bar: 1)
153
+ }.to raise_exception(ArgumentError)
84
154
  end
85
155
 
86
- clazz_matz = Record.new(:foo, :bar, :baz) do
87
- default :foo, 'Matsumoto'
156
+ it 'allows multiple required fields to be specified together' do
157
+ clazz = Record.new(:foo, :bar, :baz) do
158
+ mandatory :foo, :bar, :baz
159
+ end
160
+
161
+ expect {
162
+ clazz.new(foo: 1, bar: 2)
163
+ }.to raise_exception(ArgumentError)
164
+
165
+ expect {
166
+ clazz.new(bar: 2, baz: 3)
167
+ }.to raise_exception(ArgumentError)
168
+
169
+ expect {
170
+ clazz.new(foo: 1, bar: 2, baz: 3)
171
+ }.to_not raise_exception
88
172
  end
89
173
 
90
- expect(clazz_foo.new.foo).to eq 42
91
- expect(clazz_matz.new.foo).to eq 'Matsumoto'
174
+ it 'does not conflate default values across record classes' do
175
+ clazz_foo = Record.new(:foo, :bar, :baz) do
176
+ mandatory :foo
177
+ end
178
+
179
+ clazz_baz = Record.new(:foo, :bar, :baz) do
180
+ mandatory :baz
181
+ end
182
+
183
+ expect {
184
+ clazz_foo.new(foo: 42)
185
+ }.to_not raise_error
186
+
187
+ expect {
188
+ clazz_baz.new(baz: 42)
189
+ }.to_not raise_error
190
+ end
92
191
  end
93
192
 
94
- it 'raises an exception when values for requred field are not provided' do
95
- clazz = Record.new(:foo, :bar, :baz) do
96
- mandatory :foo
193
+ context 'with field type specification' do
194
+
195
+ let(:type_safe_definition) do
196
+ {foo: String, bar: Fixnum, baz: protocol}
97
197
  end
98
198
 
99
- expect {
100
- clazz.new(bar: 1)
101
- }.to raise_exception(ArgumentError)
102
- end
199
+ let(:protocol){ SecureRandom.uuid.to_sym }
103
200
 
104
- it 'raises an exception when required values are nil' do
105
- clazz = Record.new(:foo, :bar, :baz) do
106
- mandatory :foo
201
+ let(:clazz_with_protocol) do
202
+ Class.new do
203
+ def foo() nil end
204
+ end
107
205
  end
108
206
 
109
- expect {
110
- clazz.new(foo: nil, bar: 1)
111
- }.to raise_exception(ArgumentError)
207
+ let(:record_clazz) do
208
+ Record.new(type_safe_definition)
209
+ end
210
+
211
+ before(:each) do
212
+ Functional::SpecifyProtocol(protocol){ instance_method(:foo) }
213
+ end
214
+
215
+ it 'raises an exception for a value with an invalid type' do
216
+ expect {
217
+ record_clazz.new(foo: 'foo', bar: 'bar', baz: clazz_with_protocol.new)
218
+ }.to raise_error(ArgumentError)
219
+ end
220
+
221
+ it 'raises an exception for a value that does not satisfy a protocol' do
222
+ expect {
223
+ record_clazz.new(foo: 'foo', bar: 42, baz: 'baz')
224
+ }.to raise_error(ArgumentError)
225
+ end
226
+
227
+ it 'creates the object when all values match the appropriate types and protocols' do
228
+ record = record_clazz.new(foo: 'foo', bar: 42, baz: clazz_with_protocol.new)
229
+ expect(record).to be_a record_clazz
230
+ end
112
231
  end
113
232
 
114
233
  it 'allows a field to be required and have a default value' do
@@ -124,24 +243,6 @@ module Functional
124
243
  expect(clazz.new.foo).to eq 42
125
244
  end
126
245
 
127
- it 'allows multiple required fields to be specified together' do
128
- clazz = Record.new(:foo, :bar, :baz) do
129
- mandatory :foo, :bar, :baz
130
- end
131
-
132
- expect {
133
- clazz.new(foo: 1, bar: 2)
134
- }.to raise_exception(ArgumentError)
135
-
136
- expect {
137
- clazz.new(bar: 2, baz: 3)
138
- }.to raise_exception(ArgumentError)
139
-
140
- expect {
141
- clazz.new(foo: 1, bar: 2, baz: 3)
142
- }.to_not raise_exception
143
- end
144
-
145
246
  it 'raises an exception if the default value for a require field is nil' do
146
247
  clazz = Record.new(:foo, :bar, :baz) do
147
248
  mandatory :foo
@@ -152,24 +253,6 @@ module Functional
152
253
  clazz.new
153
254
  }.to raise_exception(ArgumentError)
154
255
  end
155
-
156
- it 'does not conflate default values across record classes' do
157
- clazz_foo = Record.new(:foo, :bar, :baz) do
158
- mandatory :foo
159
- end
160
-
161
- clazz_baz = Record.new(:foo, :bar, :baz) do
162
- mandatory :baz
163
- end
164
-
165
- expect {
166
- clazz_foo.new(foo: 42)
167
- }.to_not raise_error
168
-
169
- expect {
170
- clazz_baz.new(baz: 42)
171
- }.to_not raise_error
172
- end
173
256
  end
174
257
 
175
258
  context 'subclassing' do