functional-ruby 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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