lab42_data_class 0.7.1 → 0.8.1

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.
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