lab42_data_class 0.7.1 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fdfa61b721a7e006b42dd59c3dc61c4f3442faaa0d50feeaa5ed4b20ea5838c
4
- data.tar.gz: fa5d7ab0e6133c398caaaa1762e75a5820123e67088acc4750c36e9246ff2dc0
3
+ metadata.gz: 6534869faa807d9d3f774d0fd1deef2d12d23475f06ab07208a46f7ef998056b
4
+ data.tar.gz: f099a45edac8b2daf5d0637e7345ba3301c1ac379f2d0f4319b34172f847e431
5
5
  SHA512:
6
- metadata.gz: 53af67931648a621cd0ff18912ce191dc2d0a005eacfd4df741bbdf8a58a9c695e168055129c76be08efcb4810d3910a5df362512996c8d2375da6ff17b61be8
7
- data.tar.gz: c65a655f447f154b1a4e7584393fe3058403ef53cf5651a51fd39e5af7ebbd88768e90dc9b8d1a7d582316a35d2786abff6c6a04cc2f445f8d725657773c9a21
6
+ metadata.gz: 005cd3a1d4a52bfabaa371fb36769bce9530ed061d331f0009d39342a5af70c9126373eda6164471c0a58a14e27039261568439d40742b35b18e426b1fa61733
7
+ data.tar.gz: ed3c5a31cc163448ba912e76284946c0f4ed47bd7b2ee3841e7e5918ad350f5763656da819bea96826da614285de7b981092ae4ee924dce574e638dd684919af
data/README.md CHANGED
@@ -235,6 +235,57 @@ And of course the factory functions are equivalent to the constructors
235
235
  expect(node).to eq(Lab42::Triple.new("42", 4, 2))
236
236
  ```
237
237
 
238
+ #### Context: Pseudo Assignments
239
+
240
+ ... in reality return a new object
241
+
242
+ Given an instance of `Pair`
243
+ ```ruby
244
+ let(:original) { Pair(1, 1) }
245
+ ```
246
+
247
+ And one of `Triple`
248
+ ```ruby
249
+ let(:xyz) { Triple(1, 1, 1) }
250
+ ```
251
+
252
+ Then
253
+ ```ruby
254
+ second = original.set_first(2)
255
+ third = second.set_second(2)
256
+ expect(original).to eq( Pair(1, 1) )
257
+ expect(second).to eq(Pair(2, 1))
258
+ expect(third).to eq(Pair(2, 2))
259
+ ```
260
+
261
+ And also
262
+ ```ruby
263
+ second = xyz.set_first(2)
264
+ third = second.set_second(2)
265
+ fourth = third.set_third(2)
266
+ expect(xyz).to eq(Triple(1, 1, 1))
267
+ expect(second).to eq(Triple(2, 1, 1))
268
+ expect(third).to eq(Triple(2, 2, 1))
269
+ expect(fourth).to eq(Triple(2, 2, 2))
270
+ ```
271
+
272
+ ## Context: `List`
273
+
274
+ A `List` is what a _list_ is in Lisp or Elixir it exposes the following API
275
+
276
+ Given such a _list_
277
+ ```ruby
278
+ let(:three) { List(*%w[a b c]) }
279
+ ```
280
+
281
+ Then this becomes really a _linked_list_
282
+ ```ruby
283
+ expect(three.car).to eq("a")
284
+ expect(three.cdr).to eq(List(*%w[b c]))
285
+ ```
286
+
287
+ For all details please consult the [List speculations](speculations/LIST.md)
288
+
238
289
  # LICENSE
239
290
 
240
291
  Copyright 2022 Robert Dober robert.dober@gmail.com
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constraints/constraint"
4
+ require_relative "constraints/list_of_constraint"
5
+ require_relative "constraints/pair_of_constraint"
6
+ require_relative "constraints/triple_of_constraint"
7
+ require_relative "constraints/kernel"
8
+
9
+ module Lab42
10
+ module DataClass
11
+ module BuiltinConstraints
12
+ extend self
13
+ end
14
+ end
15
+ end
16
+
17
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ module Constraints
6
+ module AttributeSetters
7
+ module AttributeSetter
8
+ attr_reader :attribute, :constraint, :instance
9
+
10
+ private
11
+
12
+ def initialize(attribute:, constraint:, instance:)
13
+ @attribute = attribute
14
+ @constraint = constraint
15
+ @instance = instance
16
+ end
17
+
18
+ def _set_attr!(value)
19
+ new_values = instance.to_h.merge(attribute => value)
20
+ instance.class.send(:_new_from_merge, {}, new_values)
21
+ end
22
+
23
+ def _value = @___value__ ||= instance[attribute]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "attribute_setter"
4
+ module Lab42
5
+ module DataClass
6
+ module Constraints
7
+ module AttributeSetters
8
+ class ListOfAttributeSetter
9
+ include AttributeSetter
10
+
11
+ def cons(value)
12
+ constraint.constraint.(value) or raise ConstraintError,
13
+ "cannot set value #{value} in set(#{attribute}).cons"
14
+
15
+ _set_attr!(_value.cons(value))
16
+ end
17
+
18
+ def cdr
19
+ _set_attr!(_value.cdr)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "attribute_setter"
4
+ module Lab42
5
+ module DataClass
6
+ module Constraints
7
+ module AttributeSetters
8
+ class PairOfAttributeSetter
9
+ include AttributeSetter
10
+
11
+ def first_element(value)
12
+ constraint.constraint.first.(value) or raise ConstraintError,
13
+ "cannot set value #{value} in set(#{attribute}).first_element"
14
+
15
+ _set_attr!(_value.set_first(value))
16
+ end
17
+
18
+ def second_element(value)
19
+ constraint.constraint.last.(value) or raise ConstraintError,
20
+ "cannot set value #{value} in set(#{attribute}).second_element"
21
+ _set_attr!(_value.set_second(value))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "attribute_setter"
4
+ module Lab42
5
+ module DataClass
6
+ module Constraints
7
+ module AttributeSetters
8
+ class TripleOfAttributeSetter
9
+ include AttributeSetter
10
+
11
+ def first_element(value)
12
+ constraint.constraint.first.(value) or raise ConstraintError,
13
+ "cannot set value #{value} in set(#{attribute}).first_element"
14
+
15
+ _set_attr!(_value.set_first(value))
16
+ end
17
+
18
+ def second_element(value)
19
+ constraint.constraint[1].(value) or raise ConstraintError,
20
+ "cannot set value #{value} in set(#{attribute}).second_element"
21
+ _set_attr!(_value.set_second(value))
22
+ end
23
+
24
+ def third_element(value)
25
+ constraint.constraint.last.(value) or raise ConstraintError,
26
+ "cannot set value #{value} in set(#{attribute}).third_element"
27
+ _set_attr!(_value.set_third(value))
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lab42/data_class"
4
+ module Lab42
5
+ module DataClass
6
+ module Constraints
7
+ class Constraint
8
+ attr_reader :name, :function
9
+
10
+ def call(value) = function.(value)
11
+ def setter_constraint? = false
12
+ def to_s = "Constraint<#{name}>"
13
+
14
+ private
15
+ def initialize(name:, function:)
16
+ raise ArgumentError, "name not a String, but #{name}" unless String === name
17
+ unless function.respond_to?(:arity) && function.arity == 1
18
+ raise ArgumentError, "function not a callable with arity 1 #{function}"
19
+ end
20
+
21
+ @name = name
22
+ @function = function
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kernel
4
+ Constraints = Lab42::DataClass::Constraints
5
+ Constraint = Constraints::Constraint
6
+ ListOfConstraint = Constraints::ListOfConstraint
7
+ PairOfConstraint = Constraints::PairOfConstraint
8
+ TripleOfConstraint = Constraints::TripleOfConstraint
9
+
10
+ Maker = Lab42::DataClass::Proxy::Constraints::Maker # TODO: Move Maker to Lab42::DataClass:ConstraintMaker
11
+ Anything = Constraint.new(name: "Anything", function: ->(_) { true })
12
+ Boolean = Constraint.new(name: "Boolean", function: -> { [false, true].member?(_1) })
13
+
14
+ def All?(constraint = nil, &blk)
15
+ constraint = Maker.make_constraint(constraint, &blk)
16
+ f = -> do
17
+ _1.all?(&constraint)
18
+ end
19
+ Constraint.new(name: "All?(#{constraint})", function: f)
20
+ end
21
+
22
+ def Any?(constraint = nil, &blk)
23
+ constraint = Maker.make_constraint(constraint, &blk)
24
+ f = -> do
25
+ _1.any?(&constraint)
26
+ end
27
+ Constraint.new(name: "Any?(#{constraint})", function: f)
28
+ end
29
+
30
+ def Choice(*constraints)
31
+ constraints = constraints.map{ Maker.make_constraint _1 }
32
+ f = ->(value) do
33
+ constraints.any?{ _1.(value) }
34
+ end
35
+ Constraint.new(name: "Choice(#{constraints.join(', ')})", function: f)
36
+ end
37
+
38
+ def Contains(str)
39
+ f = -> { _1.include?(str) rescue false }
40
+ Constraint.new(name: "Contains(#{str})", function: f)
41
+ end
42
+
43
+ def EndsWith(str)
44
+ f = -> { _1.end_with?(str) rescue false }
45
+ Constraint.new(name: "EndsWith(#{str})", function: f)
46
+ end
47
+
48
+ def Lambda(arity)
49
+ function = -> do
50
+ _1.arity == arity rescue false
51
+ end
52
+ Constraint.new(name: "Lambda(#{arity})", function:)
53
+ end
54
+
55
+ def ListOf(constraint, &blk)
56
+ constraint = Maker.make_constraint(constraint, &blk)
57
+ function = -> do
58
+ (Lab42::List === _1 || Lab42::Nil == _1) &&
59
+ _1.all?(&constraint)
60
+ end
61
+ ListOfConstraint.new(name: "ListOf(#{constraint})", constraint:, function:)
62
+ end
63
+
64
+ def NilOr(constraint = nil, &blk)
65
+ constraint = Maker.make_constraint(constraint, &blk)
66
+ f = -> { _1.nil? || constraint.(_1) }
67
+ Constraint.new(name: "NilOr(#{constraint})", function: f)
68
+ end
69
+
70
+ def Not(constraint = nil, &blk)
71
+ constraint = Maker.make_constraint(constraint, &blk)
72
+ f = -> { !constraint.(_1) }
73
+ Constraint.new(name: "Not(#{constraint})", function: f)
74
+ end
75
+
76
+ def PairOf(fst, snd)
77
+ fst_constraint = Maker.make_constraint(fst)
78
+ snd_constraint = Maker.make_constraint(snd)
79
+ constraint = [fst_constraint, snd_constraint]
80
+ f = -> do
81
+ Lab42::Pair === _1 && fst_constraint.(_1.first) && snd_constraint.(_1.second)
82
+ end
83
+ PairOfConstraint.new(name: "PairOf(#{fst_constraint}, #{snd_constraint})", function: f, constraint:)
84
+ end
85
+
86
+ def StartsWith(str)
87
+ f = -> { _1.start_with?(str) rescue false }
88
+ Constraint.new(name: "StartsWith(#{str})", function: f)
89
+ end
90
+
91
+ def TripleOf(fst, snd, trd)
92
+ fst_constraint = Maker.make_constraint(fst)
93
+ snd_constraint = Maker.make_constraint(snd)
94
+ trd_constraint = Maker.make_constraint(trd)
95
+ constraint = [fst_constraint, snd_constraint, trd_constraint]
96
+ f = -> do
97
+ Lab42::Triple === _1 && fst_constraint.(_1.first) && snd_constraint.(_1.second) && trd_constraint.(_1.third)
98
+ end
99
+ TripleOfConstraint.new(
100
+ name: "TripleOf(#{fst_constraint}, #{snd_constraint}, #{trd_constraint})",
101
+ function: f,
102
+ constraint:
103
+ )
104
+ end
105
+ end
106
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constraint"
4
+ require_relative "setter_constraint"
5
+ require_relative "attribute_setters/list_of_attribute_setter"
6
+ module Lab42
7
+ module DataClass
8
+ module Constraints
9
+ class ListOfConstraint < Constraint
10
+ include SetterConstraint
11
+
12
+ def attribute_setter = AttributeSetters::ListOfAttributeSetter
13
+ end
14
+ end
15
+ end
16
+ end
17
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constraint"
4
+ require_relative "setter_constraint"
5
+ require_relative "attribute_setters/pair_of_attribute_setter"
6
+ module Lab42
7
+ module DataClass
8
+ module Constraints
9
+ class PairOfConstraint < Constraint
10
+ include SetterConstraint
11
+
12
+ def attribute_setter = AttributeSetters::PairOfAttributeSetter
13
+ end
14
+ end
15
+ end
16
+ end
17
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ module Constraints
6
+ module SetterConstraint
7
+ attr_reader :constraint
8
+
9
+ def setter_constraint? = true
10
+
11
+ def setter_for(attribute:, instance:)
12
+ attribute_setter.new(
13
+ attribute:,
14
+ constraint: self,
15
+ instance:
16
+ )
17
+ end
18
+ private
19
+
20
+ def initialize(constraint:, **other)
21
+ super(**other)
22
+ @constraint = constraint
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constraint"
4
+ require_relative "setter_constraint"
5
+ require_relative "attribute_setters/triple_of_attribute_setter"
6
+ module Lab42
7
+ module DataClass
8
+ module Constraints
9
+ class TripleOfConstraint < Constraint
10
+ include SetterConstraint
11
+
12
+ def attribute_setter = AttributeSetters::TripleOfAttributeSetter
13
+ end
14
+ end
15
+ end
16
+ end
17
+ # SPDX-License-Identifier: Apache-2.0
@@ -1,11 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../nil'
3
4
  module Kernel
5
+ Nil = Lab42::Nil
6
+
4
7
  def DataClass(*args, **kwds, &blk)
5
8
  proxy = Lab42::DataClass::Proxy.new(*args, **kwds, &blk)
6
9
  proxy.define_class!
7
10
  end
8
11
 
12
+ def List(*elements)
13
+ Lab42::List.new(*elements)
14
+ end
15
+
9
16
  def Pair(first, second)
10
17
  Lab42::Pair.new(first, second)
11
18
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "lab42/data_class/constraints/constraint"
3
4
  module Lab42
4
5
  module DataClass
5
6
  class Proxy
@@ -7,9 +8,17 @@ module Lab42
7
8
  module Maker
8
9
  extend self
9
10
 
10
- def make_constraint(constraint)
11
+ def make_constraint(constraint, &blk)
12
+ raise ArgumentError, "must not pass a callable #{constraint} and a block" if constraint && blk
13
+
14
+ _make_constraint(constraint || blk)
15
+ end
16
+
17
+ private
18
+
19
+ def _make_constraint(constraint)
11
20
  case constraint
12
- when Proc, Method
21
+ when Lab42::DataClass::Constraints::Constraint, Proc, Method
13
22
  constraint
14
23
  when Symbol
15
24
  -> { _1.send(constraint) }
@@ -26,8 +35,6 @@ module Lab42
26
35
  end
27
36
  end
28
37
 
29
- private
30
-
31
38
  def _make_member_constraint(constraint)
32
39
  if constraint.respond_to?(:member?)
33
40
  -> { constraint.member?(_1) }
@@ -12,19 +12,8 @@ module Lab42
12
12
  raise ConstraintError, errors.join("\n\n") unless errors.empty?
13
13
  end
14
14
 
15
- def define_constraint
16
- ->((attr, constraint)) do
17
- if members!.member?(attr)
18
- constraints[attr] << Maker.make_constraint(constraint)
19
- nil
20
- else
21
- attr
22
- end
23
- end
24
- end
25
-
26
15
  def define_constraints(constraints)
27
- errors = constraints.map(&define_constraint).compact
16
+ errors = constraints.map(&_define_constraint).compact
28
17
  unless errors.empty?
29
18
  raise UndefinedAttributeError,
30
19
  "constraints cannot be defined for undefined attributes #{errors.inspect}"
@@ -74,6 +63,19 @@ module Lab42
74
63
  raise ConstraintError, errors.join("\n\n") unless errors.empty?
75
64
  end
76
65
 
66
+ def _define_constraint
67
+ ->((attr, constraint)) do
68
+ if members!.member?(attr)
69
+ constraint = Maker.make_constraint(constraint)
70
+ _maybe_define_setter_constraint(attr, constraint)
71
+ constraints[attr] << constraint
72
+ nil
73
+ else
74
+ attr
75
+ end
76
+ end
77
+ end
78
+
77
79
  def _define_with_constraint
78
80
  proxy = self
79
81
  ->(*) do
@@ -83,6 +85,12 @@ module Lab42
83
85
  end
84
86
  end
85
87
  end
88
+
89
+ def _maybe_define_setter_constraint(attr, constraint)
90
+ if Lab42::DataClass::Constraints::Constraint === constraint && constraint.setter_constraint?
91
+ setter_attributes.update(attr => constraint)
92
+ end
93
+ end
86
94
  end
87
95
  end
88
96
  end
@@ -38,6 +38,8 @@ module Lab42
38
38
  @__validations__ ||= []
39
39
  end
40
40
 
41
+ def setter_attributes = @__setter_attributes__ ||= {}
42
+
41
43
  private
42
44
 
43
45
  def _missing_initializers
@@ -30,12 +30,11 @@ module Lab42
30
30
  end
31
31
  end
32
32
 
33
- def check!(**params)
34
- @actual_params = params
33
+ def check!(params, merge_with = defaults)
35
34
  raise ArgumentError, "missing initializers for #{_missing_initializers}" unless _missing_initializers.empty?
36
35
  raise ArgumentError, "illegal initializers #{_illegal_initializers}" unless _illegal_initializers.empty?
37
36
 
38
- _check_constraints!(defaults.merge(params))
37
+ _check_constraints!(merge_with.merge(params))
39
38
  end
40
39
 
41
40
  def define_class!
@@ -63,6 +62,10 @@ module Lab42
63
62
  _init(data_class, defaults.merge(params))
64
63
  end
65
64
 
65
+ def set_actual_params(params)
66
+ @actual_params = params
67
+ end
68
+
66
69
  def to_hash(data_class_instance)
67
70
  all_attributes
68
71
  .inject({}) { |result, (k, _)| result.merge(k => data_class_instance[k]) }
@@ -96,15 +99,40 @@ module Lab42
96
99
 
97
100
  def _define_derived_attribute(name, &blk)
98
101
  ->(*) do
102
+ if instance_methods.include?(name)
103
+ begin
104
+ remove_method(name)
105
+ rescue StandardError
106
+ nil
107
+ end
108
+ end
99
109
  define_method(name) { blk.call(self) }
100
110
  end
101
111
  end
102
112
 
103
113
  def _define_freezing_constructor
114
+ proxy = self
115
+ ->(*) do
116
+ define_method :new do |**params, &b|
117
+ allocate.tap do |o|
118
+ proxy.set_actual_params(params)
119
+ proxy.check!(params)
120
+ o.send(:initialize, **params, &b)
121
+ end.freeze
122
+ end
123
+ end
124
+ end
125
+
126
+ def _define_merging_constructor
127
+ proxy = self
104
128
  ->(*) do
105
- define_method :new do |*a, **p, &b|
106
- super(*a, **p, &b).freeze
129
+ define_method :_new_from_merge do |new_params, params|
130
+ allocate.tap do |o|
131
+ proxy.check!(new_params, {})
132
+ o.send(:initialize, **params)
133
+ end.freeze
107
134
  end
135
+ private :_new_from_merge
108
136
  end
109
137
  end
110
138
 
@@ -112,7 +140,6 @@ module Lab42
112
140
  proxy = self
113
141
  ->(*) do
114
142
  define_method :initialize do |**params|
115
- proxy.check!(**params)
116
143
  proxy.init(self, **params)
117
144
  proxy.validate!(self)
118
145
  end
@@ -123,7 +150,7 @@ module Lab42
123
150
  ->(*) do
124
151
  define_method :merge do |**params|
125
152
  values = to_h.merge(params)
126
- self.class.new(**values)
153
+ self.class.send(:_new_from_merge, params, values)
127
154
  end
128
155
  end
129
156
  end
@@ -133,10 +160,12 @@ module Lab42
133
160
  klass.module_eval(&_define_access)
134
161
  klass.module_eval(&_define_to_h)
135
162
  klass.module_eval(&_define_merge)
163
+ klass.module_eval(&_define_set)
136
164
  end
137
165
 
138
166
  def _define_singleton_methods(singleton)
139
167
  singleton.module_eval(&_define_freezing_constructor)
168
+ singleton.module_eval(&_define_merging_constructor)
140
169
  singleton.module_eval(&_define_to_proc)
141
170
  singleton.module_eval(&_define_with_constraint)
142
171
  singleton.module_eval(&_define_derived)
@@ -170,9 +199,21 @@ module Lab42
170
199
  end
171
200
  end
172
201
 
202
+ def _define_set
203
+ proxy = self
204
+ ->(*) do
205
+ define_method :set do |attribute|
206
+ setter_constraint = proxy.setter_attributes.fetch(attribute) do
207
+ raise UndefinedSetterError, "There is no constraint implementing a setter for attribute #{attribute}"
208
+ end
209
+ setter_constraint.setter_for(attribute:, instance: self)
210
+ end
211
+ end
212
+ end
213
+
173
214
  def _init(data_class_instance, params)
174
215
  params.each do |key, value|
175
- data_class_instance.instance_variable_set("@#{key}", value)
216
+ data_class_instance.instance_variable_set("@#{key}", value.freeze)
176
217
  end
177
218
  end
178
219
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class UndefinedSetterError < RuntimeError
6
+ end
7
+ end
8
+ end
9
+ # SPDX-License-Identifier: Apache-2.0
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lab42
4
4
  module DataClass
5
- VERSION = "0.7.1"
5
+ VERSION = "0.8.1"
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: Apache-2.0
@@ -4,8 +4,11 @@ require_relative './data_class/constraint_error'
4
4
  require_relative './data_class/duplicate_definition_error'
5
5
  require_relative './data_class/kernel'
6
6
  require_relative './data_class/undefined_attribute_error'
7
+ require_relative './data_class/undefined_setter_error'
7
8
  require_relative './data_class/validation_error'
8
9
  require_relative './data_class/proxy'
10
+ require_relative './list'
11
+ require_relative './nil'
9
12
  require_relative './pair'
10
13
  require_relative './triple'
11
14
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ class List
5
+ module ClassMethods
6
+ def cons(element, list)
7
+ raise ArgumentError, "list needs to be a list instance" unless list?(list)
8
+
9
+ allocate.tap do |o|
10
+ o.instance_variable_set("@car", element)
11
+ o.instance_variable_set("@cdr", list)
12
+ o.instance_variable_set("@length", list.length.succ)
13
+ o.freeze
14
+ end
15
+ end
16
+
17
+ def each(subject, &blk)
18
+ unless subject.empty?
19
+ blk.(subject.car)
20
+ each(subject.cdr, &blk)
21
+ end
22
+ end
23
+
24
+ def list?(subject)
25
+ self === subject || Nil == subject
26
+ end
27
+
28
+ def new(*elements)
29
+ elements.reverse.inject(Nil) do |list, element|
30
+ cons(element, list)
31
+ end
32
+ end
33
+ end
34
+ extend ClassMethods
35
+ end
36
+ end
37
+ # SPDX-License-Identifier: Apache-2.0
data/lib/lab42/list.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "nil"
4
+ require_relative "list/class_methods"
5
+ module Lab42
6
+ class List
7
+ include Enumerable
8
+ attr_reader :car, :cdr, :length
9
+
10
+ def ==(other) =
11
+ self.class.list?(other) &&
12
+ length == other.length &&
13
+ car == other.car &&
14
+ cdr == other.cdr
15
+
16
+ def cadr = cdr.car
17
+
18
+ def caddr = cdr.cdr.car
19
+
20
+ def cddr = cdr.cdr
21
+
22
+ def cdddr = cdr.cdr.cdr
23
+
24
+ def cons(new_head) = self.class.cons(new_head, self)
25
+
26
+ def deconstruct = [car, cdr]
27
+
28
+ def each(&blk) = self.class.each(self, &blk)
29
+
30
+ def empty? = false
31
+ end
32
+ end
33
+ # SPDX-License-Identifier: Apache-2.0
data/lib/lab42/nil.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module Nil
5
+ extend self, Enumerable
6
+
7
+ def deconstruct = []
8
+ def each = self
9
+ def empty? = true
10
+ def length = 0
11
+ end
12
+ end
13
+ # SPDX-License-Identifier: Apache-2.0
data/lib/lab42/pair.rb CHANGED
@@ -10,6 +10,9 @@ module Lab42
10
10
  [first, second]
11
11
  end
12
12
 
13
+ def set_first(new_first) = self.class.new(new_first, second)
14
+ def set_second(new_second) = self.class.new(first, new_second)
15
+
13
16
  private
14
17
 
15
18
  def initialize(first, second)
data/lib/lab42/triple.rb CHANGED
@@ -10,6 +10,10 @@ module Lab42
10
10
  [first, second, third]
11
11
  end
12
12
 
13
+ def set_first(new_first) = self.class.new(new_first, second, third)
14
+ def set_second(new_second) = self.class.new(first, new_second, third)
15
+ def set_third(new_third) = self.class.new(first, second, new_third)
16
+
13
17
  private
14
18
 
15
19
  def initialize(first, second, third)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab42_data_class
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-28 00:00:00.000000000 Z
11
+ date: 2022-03-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  An Immutable DataClass for Ruby
@@ -24,7 +24,18 @@ files:
24
24
  - LICENSE
25
25
  - README.md
26
26
  - lib/lab42/data_class.rb
27
+ - lib/lab42/data_class/builtin_constraints.rb
27
28
  - lib/lab42/data_class/constraint_error.rb
29
+ - lib/lab42/data_class/constraints/attribute_setters/attribute_setter.rb
30
+ - lib/lab42/data_class/constraints/attribute_setters/list_of_attribute_setter.rb
31
+ - lib/lab42/data_class/constraints/attribute_setters/pair_of_attribute_setter.rb
32
+ - lib/lab42/data_class/constraints/attribute_setters/triple_of_attribute_setter.rb
33
+ - lib/lab42/data_class/constraints/constraint.rb
34
+ - lib/lab42/data_class/constraints/kernel.rb
35
+ - lib/lab42/data_class/constraints/list_of_constraint.rb
36
+ - lib/lab42/data_class/constraints/pair_of_constraint.rb
37
+ - lib/lab42/data_class/constraints/setter_constraint.rb
38
+ - lib/lab42/data_class/constraints/triple_of_constraint.rb
28
39
  - lib/lab42/data_class/duplicate_definition_error.rb
29
40
  - lib/lab42/data_class/kernel.rb
30
41
  - lib/lab42/data_class/proxy.rb
@@ -35,9 +46,13 @@ files:
35
46
  - lib/lab42/data_class/proxy/mixin.rb
36
47
  - lib/lab42/data_class/proxy/validations.rb
37
48
  - lib/lab42/data_class/undefined_attribute_error.rb
49
+ - lib/lab42/data_class/undefined_setter_error.rb
38
50
  - lib/lab42/data_class/validation_error.rb
39
51
  - lib/lab42/data_class/version.rb
40
52
  - lib/lab42/eq_and_patterns.rb
53
+ - lib/lab42/list.rb
54
+ - lib/lab42/list/class_methods.rb
55
+ - lib/lab42/nil.rb
41
56
  - lib/lab42/pair.rb
42
57
  - lib/lab42/triple.rb
43
58
  homepage: https://github.com/robertdober/lab42_data_class