lab42_data_class 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c062e2633e5881141e65113d2d60878733611bece697c933876ef1d9f62270e
4
- data.tar.gz: bb5e524274338fda67063a8514cbe17a27c92ec8b62106061793456245729e30
3
+ metadata.gz: c6be60bd36bb2a9a538eb968067ff72eec653a4e9bc7dc3c8904558f803668e3
4
+ data.tar.gz: 4c1e75ab8907ce56ebcb71dd56d705afd9a1127e905ffe5344d846ddd0ade90a
5
5
  SHA512:
6
- metadata.gz: ddfaa83d7d62a4f3ccdb6df66cc1b3462ee626ac970b2654e138d338c3e0ba8e016882445552d8fff4ffb13636953f17757991625bc1c54b5b3883c1eed43f89
7
- data.tar.gz: 4dae3c50dc6b01e5f311f61e4f8aaf2b712db66a4ace5172b91df242411246e71dc2b30f29cded81ab5b95c77c0858f81282e72a481aff0921d4dbeea990d2f6
6
+ metadata.gz: 33d4995e0048d390486d01bc3f07ad1be0e0dd5efbcf9693f656162e8691645be2e0b11e60df41defc771f9fc7108f1b35cef58aa4e0102b4912136094e06202
7
+ data.tar.gz: 02e403af2357e1b6b06e34afe9909777ab3f64a27d51843387ed20d1d349d96f7c9383fe22da386b42e7e5510180e6bc9a6a3d17c0b86cf16d1ff3f6cd02c8c3
data/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
 
2
- [![Gem Version](http://img.shields.io/gem/v/lab42_data_class.svg)](https://rubygems.org/gems/lab42_data_class)
2
+ [![Issue Count](https://codeclimate.com/github/RobertDober/lab42_data_class/badges/issue_count.svg)](https://codeclimate.com/github/RobertDober/lab42_data_class)
3
3
  [![CI](https://github.com/robertdober/lab42_data_class/workflows/CI/badge.svg)](https://github.com/robertdober/lab42_data_class/actions)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/RobertDober/lab42_data_class/badge.svg?branch=main)](https://coveralls.io/github/RobertDober/lab42_data_class?branch=main)
5
+ [![Gem Version](http://img.shields.io/gem/v/lab42_data_class.svg)](https://rubygems.org/gems/lab42_data_class)
6
+ [![Gem Downloads](https://img.shields.io/gem/dt/lab42_data_class.svg)](https://rubygems.org/gems/lab42_data_class)
5
7
 
6
8
 
7
9
  # Lab42::DataClass
8
10
 
9
- An immutable dataclass
11
+ An immutable Dataclass, Tuples and Triples
10
12
 
11
13
  ## Usage
12
14
 
@@ -31,6 +33,8 @@ require 'lab42/data_class'
31
33
 
32
34
  Well let us [speculate about](https://github.com/RobertDober/speculate_about) it to find out:
33
35
 
36
+ ## Context `DataClass`
37
+
34
38
  ### Context: `DataClass` function
35
39
 
36
40
  Given
@@ -179,6 +183,159 @@ Then we can access the included method
179
183
  expect(class_level.new.humanize).to eq("my value is 1")
180
184
  ```
181
185
 
186
+ ### Context: Pattern Matching
187
+
188
+ A `DataClass` object behaves like the result of it's `to_h` in pattern matching
189
+
190
+ Given
191
+ ```ruby
192
+ let(:numbers) { DataClass(:name, values: []) }
193
+ let(:odds) { numbers.new(name: "odds", values: (1..4).map{ _1 + _1 + 1}) }
194
+ let(:evens) { numbers.new(name: "evens", values: (1..4).map{ _1 + _1}) }
195
+ ```
196
+
197
+ Then we can match accordingly
198
+ ```ruby
199
+ match = case odds
200
+ in {name: "odds", values: [1, *]}
201
+ :not_really
202
+ in {name: "evens"}
203
+ :still_naaah
204
+ in {name: "odds", values: [hd, *]}
205
+ hd
206
+ else
207
+ :strange
208
+ end
209
+ expect(match).to eq(3)
210
+ ```
211
+
212
+ And in `in` expressions
213
+ ```ruby
214
+ evens => {values: [_, second, *]}
215
+ expect(second).to eq(4)
216
+ ```
217
+
218
+ #### Context: In Case Statements
219
+
220
+ Given a nice little dataclass `Box`
221
+ ```ruby
222
+ let(:box) { DataClass content: nil }
223
+ ```
224
+
225
+ Then we can also use it in a case statement
226
+ ```ruby
227
+ value = case box.new
228
+ when box
229
+ 42
230
+ else
231
+ 0
232
+ end
233
+ expect(value).to eq(42)
234
+ ```
235
+
236
+ And all the associated methods
237
+ ```ruby
238
+ expect(box.new).to be_a(box)
239
+ expect(box === box.new).to be_truthy
240
+ ```
241
+
242
+ ### Context: Behaving like a `Proc`
243
+
244
+ It is useful to be able to filter heterogeneous lists of `DataClass` instances by means of `&to_proc`, therefore
245
+
246
+ Given two different `DataClass` objects
247
+ ```ruby
248
+ let(:class1) { DataClass :value }
249
+ let(:class2) { DataClass :value }
250
+ ```
251
+
252
+ And a list of instances
253
+ ```ruby
254
+ let(:list) {[class1.new(value: 1), class2.new(value: 2), class1.new(value: 3)]}
255
+ ```
256
+
257
+ Then we can filter
258
+ ```ruby
259
+ expect(list.filter(&class2)).to eq([class2.new(value: 2)])
260
+ ```
261
+
262
+ ### Context: Behaving like a `Hash`
263
+
264
+ We have already seen the `to_h` method, however if we want to pass an instance of `DataClass` as
265
+ keyword parameters we need an implementation of `to_hash`, which of course is just an alias
266
+
267
+ Given this keyword method
268
+ ```ruby
269
+ def extract_value(value:, **others)
270
+ [value, others]
271
+ end
272
+ ```
273
+ And this `DataClass`:
274
+ ```ruby
275
+ let(:my_class) { DataClass(value: 1, base: 2) }
276
+ ```
277
+
278
+ Then we can pass it as keyword arguments
279
+ ```ruby
280
+ expect(extract_value(**my_class.new)).to eq([1, base: 2])
281
+ ```
282
+
283
+ ## Context: `Pair` and `Triple`
284
+
285
+ Two special cases of a `DataClass` which behave like `Tuple` of size 2 and 3 in _Elixir_
286
+
287
+
288
+ They distinguish themselves from `DataClass` classes by accepting only positional arguments, and
289
+ cannot be converted to hashes.
290
+
291
+ These are actually two classes and not class factories as they have a fixed interface , but let us speculate about them to learn what they can do for us.
292
+
293
+ ### Context: Constructor functions
294
+
295
+ Given a pair
296
+ ```ruby
297
+ let(:token) { Pair("12", 12) }
298
+ let(:node) { Triple("42", 4, 2) }
299
+ ```
300
+
301
+ Then we can access their elements
302
+ ```ruby
303
+ expect(token.first).to eq("12")
304
+ expect(token.second).to eq(12)
305
+ expect(node.first).to eq("42")
306
+ expect(node.second).to eq(4)
307
+ expect(node.third).to eq(2)
308
+ ```
309
+
310
+ And we can treat them like _Indexable_
311
+ ```ruby
312
+ expect(token[1]).to eq(12)
313
+ expect(token[-2]).to eq("12")
314
+ expect(node[2]).to eq(2)
315
+ ```
316
+
317
+ And convert them to arrays of course
318
+ ```ruby
319
+ expect(token.to_a).to eq(["12", 12])
320
+ expect(node.to_a).to eq(["42", 4, 2])
321
+ ```
322
+
323
+ And they behave like arrays in pattern matching too
324
+ ```ruby
325
+ token => [str, int]
326
+ node => [root, lft, rgt]
327
+ expect(str).to eq("12")
328
+ expect(int).to eq(12)
329
+ expect(root).to eq("42")
330
+ expect(lft).to eq(4)
331
+ expect(rgt).to eq(2)
332
+ ```
333
+
334
+ And of course the factory functions are equivalent to the constructors
335
+ ```ruby
336
+ expect(token).to eq(Lab42::Pair.new("12", 12))
337
+ expect(node).to eq(Lab42::Triple.new("42", 4, 2))
338
+ ```
182
339
 
183
340
  # LICENSE
184
341
 
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class Proxy
6
+ module Mixin
7
+ def ==(other)
8
+ other.is_a?(self.class) &&
9
+ to_h == other.to_h
10
+ end
11
+
12
+ def deconstruct_keys(*)
13
+ to_h
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ # SPDX-License-Identifier: Apache-2.0
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
+ require_relative 'proxy/mixin'
4
5
  module Lab42
5
6
  module DataClass
6
7
  class Proxy
@@ -16,6 +17,8 @@ module Lab42
16
17
  klass.module_eval(&_define_attr_reader)
17
18
  klass.module_eval(&_define_initializer)
18
19
  _define_methods
20
+ klass.include(Mixin)
21
+ klass.module_eval(&block) if block
19
22
  klass
20
23
  end
21
24
 
@@ -47,15 +50,6 @@ module Lab42
47
50
  end
48
51
  end
49
52
 
50
- def _define_eql?
51
- ->(*) do
52
- define_method :== do |other|
53
- other.is_a?(self.class) &&
54
- to_h == other.to_h
55
- end
56
- end
57
- end
58
-
59
53
  def _define_freezing_constructor
60
54
  ->(*) do
61
55
  define_method :new do |*a, **p, &b|
@@ -87,10 +81,9 @@ module Lab42
87
81
 
88
82
  def _define_methods
89
83
  (class << klass; self end).module_eval(&_define_freezing_constructor)
84
+ (class << klass; self end).module_eval(&_define_to_proc)
90
85
  klass.module_eval(&_define_to_h)
91
86
  klass.module_eval(&_define_merge)
92
- klass.module_eval(&_define_eql?)
93
- klass.module_eval(&block) if block
94
87
  end
95
88
 
96
89
  def _define_to_h
@@ -99,6 +92,15 @@ module Lab42
99
92
  define_method :to_h do
100
93
  proxy.to_hash(self)
101
94
  end
95
+ alias_method :to_hash, :to_h
96
+ end
97
+ end
98
+
99
+ def _define_to_proc
100
+ ->(*) do
101
+ define_method :to_proc do
102
+ ->(other) { self === other }
103
+ end
102
104
  end
103
105
  end
104
106
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lab42
4
4
  module DataClass
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: Apache-2.0
@@ -1,12 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative './data_class/proxy'
4
+ require_relative './pair'
5
+ require_relative './triple'
4
6
 
5
7
  module Kernel
6
8
  def DataClass(*args, **kwds, &blk)
7
9
  proxy = Lab42::DataClass::Proxy.new(*args, **kwds, &blk)
8
10
  proxy.define_class!
9
11
  end
12
+
13
+ def Pair(first, second)
14
+ Lab42::Pair.new(first, second)
15
+ end
16
+
17
+ def Triple(first, second, third)
18
+ Lab42::Triple.new(first, second, third)
19
+ end
10
20
  end
11
21
 
12
22
  # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module EqAndPatterns
5
+ def [](idx)
6
+ to_a[idx]
7
+ end
8
+
9
+ def ==(other)
10
+ other.is_a?(self.class) &&
11
+ to_a == other.to_a
12
+ end
13
+
14
+ def deconstruct(*)
15
+ to_a
16
+ end
17
+ end
18
+ end
19
+ # SPDX-License-Identifier: Apache-2.0
data/lib/lab42/pair.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'eq_and_patterns'
4
+ module Lab42
5
+ class Pair
6
+ attr_reader :first, :second
7
+ include EqAndPatterns
8
+
9
+ def to_a
10
+ [first, second]
11
+ end
12
+
13
+ private
14
+
15
+ def initialize(first, second)
16
+ @first = first
17
+ @second = second
18
+ end
19
+ end
20
+ end
21
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'eq_and_patterns'
4
+ module Lab42
5
+ class Triple
6
+ attr_reader :first, :second, :third
7
+ include EqAndPatterns
8
+
9
+ def to_a
10
+ [first, second, third]
11
+ end
12
+
13
+ private
14
+
15
+ def initialize(first, second, third)
16
+ @first = first
17
+ @second = second
18
+ @third = third
19
+ end
20
+ end
21
+ end
22
+ # SPDX-License-Identifier: Apache-2.0
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.3.0
4
+ version: 0.4.0
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-01-22 00:00:00.000000000 Z
11
+ date: 2022-02-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  An Immutable DataClass for Ruby
@@ -24,7 +24,11 @@ files:
24
24
  - README.md
25
25
  - lib/lab42/data_class.rb
26
26
  - lib/lab42/data_class/proxy.rb
27
+ - lib/lab42/data_class/proxy/mixin.rb
27
28
  - lib/lab42/data_class/version.rb
29
+ - lib/lab42/eq_and_patterns.rb
30
+ - lib/lab42/pair.rb
31
+ - lib/lab42/triple.rb
28
32
  homepage: https://github.com/robertdober/lab42_data_class
29
33
  licenses:
30
34
  - Apache-2.0