algebrick 0.1.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 (6) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +19 -0
  3. data/README.md +7 -0
  4. data/README_FULL.md +102 -0
  5. data/lib/algebrick.rb +1003 -0
  6. metadata +135 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e6e3ca091c97766d3eb3524ce82f129558386865
4
+ data.tar.gz: b4c5547dd1dd6604f57db2b2360e11136f883226
5
+ SHA512:
6
+ metadata.gz: e1bdc83c04dde504c7d9360bea9f3599c2b8e8bd251bd42087c4c1eab574482258f762e1c0444f1b97ffefe49ac7f35beb0eec85ad69cdb443039733a225f7ce
7
+ data.tar.gz: 71d61898fdcf3e4b29ada2552ae90527a609355b52841e7236983adc2158c46085db2dfaba155f7eababadfcc268a0c327a8f3ff496738ae60c0ffc7628b29f4
data/MIT-LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Petr Chalupa <git@pitr.ch>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ ## Algebrick
2
+
3
+ Algebraic types and pattern matching for Ruby.
4
+
5
+ - Documentation: http://blog.pitr.ch/algebrick
6
+ - Source: https://github.com/pitr-ch/algebrick
7
+ - Blog: http://blog.pitr.ch
data/README_FULL.md ADDED
@@ -0,0 +1,102 @@
1
+ # Algebrick
2
+
3
+ Is a small gem providing algebraic types and pattern matching on them for Ruby.
4
+
5
+ - Documentation: {http://blog.pitr.ch/algebrick}
6
+ - Source: {https://github.com/pitr-ch/algebrick}
7
+ - Blog: {http://blog.pitr.ch}
8
+
9
+ ## Quick example with Maybe type
10
+
11
+ {include:file:doc/maybe.out.rb}
12
+
13
+ ## Algebraic types: what is it?
14
+
15
+ If you ever read something about Haskell that you probably know:
16
+
17
+ data Tree = Empty
18
+ | Leaf Int
19
+ | Node Tree Tree
20
+
21
+ which is an algebraic type. This snippet defines type `Tree` which has 3 possible values:
22
+ `Empty`, `Leaf` with and extra value of type `Int`, `Node` with two values of type `Tree`.
23
+ See {https://en.wikipedia.org/wiki/Algebraic_data_type}.
24
+
25
+ Same thing can be defined with this gem:
26
+
27
+ {include:file:doc/tree1.out.rb}
28
+
29
+ There are 4 kinds of algebraic types in Algebrick gem:
30
+
31
+ - **Atom** a type that has only one value e.g. `Empty`.
32
+ - **Product** a type that has a set nuber of fields with given type e.g. `Leaf(Integer)`
33
+ - **Variant** a type that does have set number of variants e.g. `Tree(Empty | Leaf(Integer) | Node(Tree, Tree)`.
34
+ It means that values of `Empty`, `Leaf[1]`, `Node[Empty, Empry]` have all type `Tree`.
35
+ - **ProductVariant** will be created when a recursive type like `list === empty | list(Object, list)` is defined.
36
+ `List` has two variants `Empty` and itself simultaneously it has fields as product type.
37
+
38
+ ### Type definition
39
+
40
+ {include:file:doc/type_def.out.rb}
41
+
42
+ ### Value creation
43
+
44
+ {include:file:doc/values.out.rb}
45
+
46
+ ### Extending behavior
47
+
48
+ {include:file:doc/extending_behavior.out.rb}
49
+
50
+ ### Pattern matching
51
+
52
+ Algebraic matchers are helper objects to match algebraic objects and others with
53
+ `#===` method based on theirs initialization values.
54
+
55
+ {include:file:doc/pattern_matching.out.rb}
56
+
57
+ ## What is it good for?
58
+
59
+ ### Defining data with a given structure
60
+
61
+ {include:file:doc/data.out.rb}
62
+
63
+ ### Serialization
64
+
65
+ Algebraic types also play nice with JSON serialization. So it is ideal for defining messegas
66
+ for cross-process comunication.
67
+
68
+ {include:file:doc/json.out.rb}
69
+
70
+ ### Null Object Pattern
71
+
72
+ see {http://en.wikipedia.org/wiki/Null_Object_pattern#Ruby}.
73
+
74
+ {include:file:doc/null.out.rb}
75
+
76
+ This has advantage over a classical approach that the methods are defined
77
+ on one place, no need to track methods in two separate classes `User` and `NullUser`.
78
+
79
+ ### Message matching in Actor pattern
80
+
81
+ Just small snippet from a gem I am still working on.
82
+
83
+ class Worker < AbstractActor
84
+ def initialize(executor)
85
+ super()
86
+ @executor = executor
87
+ end
88
+
89
+ def on_message(message)
90
+ match message,
91
+ Work.(~any, ~any) --> actor, work do
92
+ @executor.tell Finished[actor, work.call, self.reference]
93
+ end
94
+ end
95
+ end
96
+
97
+ ### TODO
98
+
99
+ - Menu model, TypedArray
100
+ - Pretty print example, see {http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf}
101
+ - update actor pattern when gem is done
102
+
data/lib/algebrick.rb ADDED
@@ -0,0 +1,1003 @@
1
+ # TODO method definition in variant type defines methods on variants based on match
2
+
3
+ require 'set'
4
+
5
+ class Module
6
+ # Return any modules we +extend+
7
+ def extended_modules
8
+ class << self
9
+ self
10
+ end.included_modules
11
+ end
12
+ end
13
+
14
+ module Algebrick
15
+
16
+ module TypeCheck
17
+ #def is_kind_of?(value, *types)
18
+ # a_type_check :kind_of?, false, value, *types
19
+ #end
20
+
21
+ def is_kind_of!(value, *types)
22
+ a_type_check :kind_of?, true, value, *types
23
+ end
24
+
25
+ #def is_matching?(value, *types)
26
+ # a_type_check :===, false, value, *types
27
+ #end
28
+
29
+ def is_matching!(value, *types)
30
+ a_type_check :===, true, value, *types
31
+ end
32
+
33
+ private
34
+
35
+ def a_type_check(which, bang, value, *types)
36
+ ok = types.any? do |t|
37
+ case which
38
+ when :===
39
+ t === value
40
+ when :kind_of?
41
+ value.kind_of? t
42
+ else
43
+ raise ArgumentError
44
+ end
45
+ end
46
+ raise TypeError, "value (#{value.class}) '#{value}' is not ##{which} any of #{types.join(', ')}" if bang && !ok
47
+ value
48
+ end
49
+ end
50
+
51
+ module Matching
52
+ def any
53
+ Matchers::Any.new
54
+ end
55
+
56
+ alias_method :_, :any # TODO make it optional
57
+
58
+ #match Empty,
59
+ # Empty -->() {},
60
+ # Leaf.(~any) >>-> value do
61
+ # value
62
+ # end
63
+ #match Empty,
64
+ # Node => ->() {},
65
+ # Empty => ->() {},
66
+ # Leaf.(~any) => ->(value) { value }
67
+ #match Empty,
68
+ # [Node, lambda {}],
69
+ # [Empty, lambda {}],
70
+ # [Leaf.(~any), lambda { |value| value }]
71
+ #match(Empty,
72
+ # Node.case {},
73
+ # Empty.case {},
74
+ # Leaf.(~any).case { |value| value })
75
+
76
+ def match(value, *cases)
77
+ cases = if cases.size == 1 && cases.first.is_a?(Hash)
78
+ cases.first
79
+ else
80
+ cases
81
+ end
82
+
83
+ cases.each do |matcher, block|
84
+ return match_value matcher, block if matcher === value
85
+ end
86
+ raise "no match for #{value} by any of #{cases.map(&:first).join ', '}"
87
+ end
88
+
89
+ private
90
+
91
+ def match_value(matcher, block)
92
+ if block.kind_of? Proc
93
+ if matcher.kind_of? Matchers::Abstract
94
+ matcher.assigns &block
95
+ else
96
+ block.call
97
+ end
98
+ else
99
+ block
100
+ end
101
+ end
102
+ end
103
+
104
+ include Matching
105
+ extend Matching
106
+
107
+ module MatcherDelegations
108
+ def ~
109
+ ~to_m
110
+ end
111
+
112
+ def &(other)
113
+ to_m & other
114
+ end
115
+
116
+ def |(other)
117
+ to_m | other
118
+ end
119
+
120
+ def !
121
+ !to_m
122
+ end
123
+ end
124
+
125
+ class Type < Module
126
+ include TypeCheck
127
+ include Matching
128
+ include MatcherDelegations
129
+
130
+ def to_m(*args)
131
+ raise NotImplementedError
132
+ end
133
+
134
+ def ==(other)
135
+ raise NotImplementedError
136
+ end
137
+
138
+ def be_kind_of(type)
139
+ raise NotImplementedError
140
+ end
141
+
142
+ def to_s
143
+ raise NotImplementedError
144
+ end
145
+
146
+ def inspect
147
+ to_s
148
+ end
149
+ end
150
+
151
+ module Value
152
+ include TypeCheck
153
+ include Matching
154
+
155
+ def ==(other)
156
+ raise NotImplementedError
157
+ end
158
+
159
+ def type
160
+ raise NotImplementedError
161
+ end
162
+
163
+ def to_hash
164
+ raise NotImplementedError
165
+ end
166
+
167
+ def to_s
168
+ raise NotImplementedError
169
+ end
170
+
171
+ def inspect
172
+ to_s
173
+ end
174
+ end
175
+
176
+ class Atom < Type
177
+ include Value
178
+
179
+ def initialize(&block)
180
+ super &block
181
+ extend self
182
+ end
183
+
184
+ def to_m
185
+ Matchers::Atom.new self
186
+ end
187
+
188
+ def be_kind_of(type)
189
+ extend type
190
+ end
191
+
192
+ def ==(other)
193
+ self.equal? other
194
+ end
195
+
196
+ def type
197
+ self
198
+ end
199
+
200
+ def to_s
201
+ name
202
+ end
203
+
204
+ def to_hash
205
+ { name => name }
206
+ end
207
+
208
+ def from_hash(hash)
209
+ if hash == to_hash
210
+ self
211
+ else
212
+ raise ArgumentError
213
+ end
214
+ end
215
+ end
216
+
217
+ class ProductConstructor
218
+ include Value
219
+ attr_reader :fields
220
+
221
+ def initialize(*fields)
222
+ if fields.size == 1 && fields.first.is_a?(Hash)
223
+ fields = type.field_names.map { |k| fields.first[k] }
224
+ end
225
+ @fields = fields.zip(self.class.type.fields).map do |field, type|
226
+ is_kind_of! field, type
227
+ end.freeze
228
+ end
229
+
230
+ def to_s
231
+ "#{self.class.type.name}[" +
232
+ if type.field_names
233
+ type.field_names.map { |name| "#{name}: #{self[name].to_s}" }.join(', ')
234
+ else
235
+ fields.map(&:to_s).join(',')
236
+ end + ']'
237
+ end
238
+
239
+ def to_ary
240
+ @fields
241
+ end
242
+
243
+ def to_a
244
+ @fields
245
+ end
246
+
247
+ def to_hash
248
+ { self.class.type.name =>
249
+ if type.field_names
250
+ type.field_names.inject({}) { |h, name| h.update name => hashize(self[name]) }
251
+ else
252
+ fields.map { |v| hashize v }
253
+ end }
254
+ end
255
+
256
+ def ==(other)
257
+ return false unless other.kind_of? self.class
258
+ @fields == other.fields
259
+ end
260
+
261
+ def self.type
262
+ @type || raise
263
+ end
264
+
265
+ def type
266
+ self.class.type
267
+ end
268
+
269
+ def self.type=(type)
270
+ raise if @type
271
+ @type = type
272
+ include type
273
+ end
274
+
275
+ private
276
+
277
+ def hashize(value)
278
+ (value.respond_to? :to_hash) ? value.to_hash : value
279
+ end
280
+ end
281
+
282
+ class AbstractProductVariant < Type
283
+ def be_kind_of(type = nil)
284
+ if initialized?
285
+ be_kind_of! type if type
286
+ if @to_be_kind_of
287
+ while (type = @to_be_kind_of.shift)
288
+ be_kind_of! type
289
+ end
290
+ end
291
+ else
292
+ @to_be_kind_of ||= []
293
+ @to_be_kind_of << type if type
294
+ end
295
+ self
296
+ end
297
+
298
+ protected
299
+
300
+ def be_kind_of!(type)
301
+ raise NotImplementedError
302
+ end
303
+
304
+ private
305
+
306
+ def initialized?
307
+ !!@initialized
308
+ end
309
+
310
+ def initialize(&block)
311
+ super &block
312
+ @initialized = true
313
+ be_kind_of
314
+ end
315
+
316
+ def set_fields(fields_or_hash)
317
+ fields = if fields_or_hash.size == 1 && fields_or_hash.first.is_a?(Hash)
318
+ keys = fields_or_hash.first.keys
319
+ fields_or_hash.first.values
320
+ else
321
+ fields_or_hash
322
+ end
323
+
324
+ if keys
325
+ @field_names = keys
326
+ keys.all? { |k| is_kind_of! k, Symbol }
327
+ dict = @field_indexes = keys.each_with_index.inject({}) { |h, (k, i)| h.update k => i }
328
+ define_method(:[]) { |key| @fields[dict[key]] }
329
+ end
330
+
331
+ fields.all? { |f| is_kind_of! f, Type, Class }
332
+ raise TypeError, 'there is no product with zero fields' unless fields.size > 0
333
+ define_method(:value) { @fields.first } if fields.size == 1
334
+ @fields = fields
335
+ @constructor = Class.new(ProductConstructor).tap { |c| c.type = self }
336
+ end
337
+
338
+ def set_variants(variants)
339
+ variants.all? { |v| is_kind_of! v, Type, Class }
340
+ @variants = variants
341
+ variants.each do |v|
342
+ if v.respond_to? :be_kind_of
343
+ v.be_kind_of self
344
+ else
345
+ v.send :include, self
346
+ end
347
+ end
348
+ end
349
+
350
+ def product_be_kind_of(type)
351
+ @constructor.send :include, type
352
+ end
353
+
354
+ def construct_product(*fields)
355
+ @constructor.new *fields
356
+ end
357
+
358
+ def product_to_s
359
+ fields_str = if field_names
360
+ field_names.zip(fields).map { |name, field| "#{name}: #{field.name}" }
361
+ else
362
+ fields.map(&:name)
363
+ end
364
+ "#{name}(#{fields_str.join ', '})"
365
+ end
366
+
367
+ def product_from_hash(hash)
368
+ raise ArgumentError, 'hash does not have size 1' unless hash.size == 1
369
+ type_name, fields = hash.first
370
+ raise ArgumentError, "#{type_name} is not #{name}" unless type_name == name
371
+ is_kind_of! fields, Hash, Array
372
+ case fields
373
+ when Array
374
+ self[*fields.map { |value| field_from_hash value }]
375
+ when Hash
376
+ self[fields.inject({}) do |h, (name, value)|
377
+ raise ArgumentError unless @field_names.map(&:to_s).include? name
378
+ h.update name.to_sym => field_from_hash(value)
379
+ end]
380
+ end
381
+ end
382
+
383
+ def field_from_hash(hash)
384
+ return hash unless Hash === hash
385
+ return hash unless hash.size == 1
386
+ type_name, value = hash.first
387
+ type = constantize type_name
388
+ if type.respond_to? :from_hash
389
+ type.from_hash hash
390
+ else
391
+ value
392
+ end
393
+ end
394
+
395
+ def constantize(camel_cased_word)
396
+ names = camel_cased_word.split('::')
397
+ names.shift if names.empty? || names.first.empty?
398
+
399
+ constant = Object
400
+ names.each do |name|
401
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
402
+ end
403
+ constant
404
+ end
405
+ end
406
+
407
+ class Product < AbstractProductVariant
408
+ attr_reader :fields, :field_names, :field_indexes
409
+
410
+ def initialize(*fields, &block)
411
+ set_fields fields
412
+ super(&block)
413
+ end
414
+
415
+ def [](*fields)
416
+ construct_product(*fields)
417
+ end
418
+
419
+ def be_kind_of!(type)
420
+ product_be_kind_of type
421
+ end
422
+
423
+ def call(*field_matchers)
424
+ Matchers::Product.new self, *field_matchers
425
+ end
426
+
427
+ def to_m
428
+ call *::Array.new(fields.size) { Algebrick.any }
429
+ end
430
+
431
+ def ==(other)
432
+ other.kind_of? Product and fields == other.fields
433
+ end
434
+
435
+ def to_s
436
+ product_to_s
437
+ end
438
+
439
+ def from_hash(hash)
440
+ product_from_hash hash
441
+ end
442
+ end
443
+
444
+ class Variant < AbstractProductVariant
445
+ attr_reader :variants
446
+
447
+ def initialize(*variants, &block)
448
+ set_variants(variants)
449
+ super &block
450
+ end
451
+
452
+ def be_kind_of!(type)
453
+ variants.each { |v| v.be_kind_of type }
454
+ end
455
+
456
+ def to_m
457
+ Matchers::Variant.new self
458
+ end
459
+
460
+ def ==(other)
461
+ other.kind_of? Variant and variants == other.variants
462
+ end
463
+
464
+ def to_s
465
+ "#{name}(#{variants.map(&:name).join ' | '})"
466
+ end
467
+
468
+ def from_hash(hash)
469
+ field_from_hash hash
470
+ end
471
+ end
472
+
473
+ class ProductVariant < AbstractProductVariant
474
+ attr_reader :fields, :field_names, :field_indexes, :variants
475
+
476
+ def initialize(fields, variants, &block)
477
+ set_fields fields
478
+ raise unless variants.include? self
479
+ set_variants variants
480
+ super &block
481
+ end
482
+
483
+ def be_kind_of!(type)
484
+ variants.each { |v| v.be_kind_of type unless v == self }
485
+ product_be_kind_of type
486
+ end
487
+
488
+ def call(*field_matchers)
489
+ Matchers::Product.new self, *field_matchers
490
+ end
491
+
492
+ def to_m
493
+ Matchers::Variant.new self
494
+ end
495
+
496
+ def [](*fields)
497
+ construct_product(*fields)
498
+ end
499
+
500
+ def ==(other)
501
+ other.kind_of? ProductVariant and
502
+ variants == other.variants and fields == other.fields
503
+ end
504
+
505
+ def to_s
506
+ name + '(' +
507
+ variants.map do |variant|
508
+ if variant == self
509
+ product_to_s
510
+ else
511
+ variant.name
512
+ end
513
+ end.join(' | ') +
514
+ ')'
515
+ end
516
+
517
+ def from_hash(hash)
518
+ product_from_hash hash
519
+ end
520
+ end
521
+
522
+ module Matchers
523
+
524
+ class Abstract
525
+ include TypeCheck
526
+ attr_reader :value
527
+
528
+ def initialize
529
+ @assign, @value = nil
530
+ end
531
+
532
+ def case(&block)
533
+ return self, block
534
+ end
535
+
536
+ def +(block)
537
+ return self, block
538
+ end
539
+
540
+ alias_method :-, :+
541
+ alias_method :>>, :+
542
+
543
+ def ~
544
+ @assign = true
545
+ self
546
+ end
547
+
548
+ def &(matcher)
549
+ And.new self, matcher
550
+ end
551
+
552
+ def |(matcher)
553
+ Or.new self, matcher
554
+ end
555
+
556
+ def !
557
+ Not.new self
558
+ end
559
+
560
+ def assign?
561
+ @assign
562
+ end
563
+
564
+ def children_including_self
565
+ children.unshift self
566
+ end
567
+
568
+ def assigns
569
+ mine = @assign && @value ? [@value] : []
570
+ mine = @assign ? [@value] : []
571
+ children.inject(mine) { |assigns, child| assigns + child.assigns }.tap do
572
+ return yield *assigns if block_given?
573
+ end
574
+ end
575
+
576
+ def ===(other)
577
+ matching?(other).tap { |matched| @value = other if matched }
578
+ end
579
+
580
+ def assign_to_s
581
+ assign? ? '~' : ''
582
+ end
583
+
584
+ def inspect
585
+ to_s
586
+ end
587
+
588
+ def children
589
+ raise NotImplementedError
590
+ end
591
+
592
+ def to_s
593
+ raise NotImplementedError
594
+ end
595
+
596
+ def ==(other)
597
+ raise NotImplementedError
598
+ end
599
+
600
+ protected
601
+
602
+ def matching?(other)
603
+ raise NotImplementedError
604
+ end
605
+
606
+ private
607
+
608
+ def matchable!(obj)
609
+ raise ArgumentError, 'object does not respond to :===' unless obj.respond_to? :===
610
+ obj
611
+ end
612
+
613
+ def find_children(collection)
614
+ collection.map do |matcher|
615
+ matcher if matcher.kind_of? Abstract
616
+ end.compact
617
+ end
618
+ end
619
+
620
+ class AbstractLogic < Abstract
621
+ def self.call(*matchers)
622
+ new *matchers
623
+ end
624
+
625
+ attr_reader :matchers
626
+
627
+ def initialize(*matchers)
628
+ @matchers = matchers.each { |m| matchable! m }
629
+ end
630
+
631
+ def children
632
+ find_children matchers
633
+ end
634
+
635
+ def ==(other)
636
+ other.kind_of? self.class and
637
+ self.matchers == other.matchers
638
+ end
639
+ end
640
+
641
+ class And < AbstractLogic
642
+ def to_s
643
+ matchers.join ' & '
644
+ end
645
+
646
+ protected
647
+
648
+ def matching?(other)
649
+ matchers.all? { |m| m === other }
650
+ end
651
+ end
652
+
653
+ class Or < AbstractLogic
654
+ def to_s
655
+ matchers.join ' | '
656
+ end
657
+
658
+ protected
659
+
660
+ def matching?(other)
661
+ matchers.any? { |m| m === other }
662
+ end
663
+ end
664
+
665
+ class Not < Abstract # TODO
666
+ attr_reader :matcher
667
+
668
+ def initialize(matcher)
669
+ @matcher = matcher
670
+ end
671
+
672
+ def children
673
+ []
674
+ end
675
+
676
+ def to_s
677
+ '!' + matcher.to_s
678
+ end
679
+
680
+ def ==(other)
681
+ other.kind_of? self.class and
682
+ self.matcher == other.matcher
683
+ end
684
+
685
+ protected
686
+
687
+ def matching?(other)
688
+ not matcher === other
689
+ end
690
+ end
691
+
692
+ class Any < Abstract
693
+ def children
694
+ []
695
+ end
696
+
697
+ def to_s
698
+ assign_to_s + 'any'
699
+ end
700
+
701
+ def ==(other)
702
+ other.kind_of? self.class
703
+ end
704
+
705
+ protected
706
+
707
+ def matching?(other)
708
+ true
709
+ end
710
+ end
711
+
712
+ class Wrapper < Abstract
713
+ def self.call(something)
714
+ new something
715
+ end
716
+
717
+ attr_reader :something
718
+
719
+ def initialize(something)
720
+ super()
721
+ @something = matchable! something
722
+ end
723
+
724
+ def children
725
+ find_children [@something]
726
+ end
727
+
728
+ def to_s
729
+ assign_to_s + "Wrapper.(#{@something})"
730
+ end
731
+
732
+ def ==(other)
733
+ other.kind_of? self.class and
734
+ self.something == other.something
735
+ end
736
+
737
+ protected
738
+
739
+ def matching?(other)
740
+ @something === other
741
+ end
742
+ end
743
+
744
+ class ::Object
745
+ def to_m
746
+ Wrapper.new(self)
747
+ end
748
+ end
749
+
750
+ class Array < Abstract
751
+ def self.call(*matchers)
752
+ new *matchers
753
+ end
754
+
755
+ attr_reader :matchers
756
+
757
+ def initialize(*matchers)
758
+ super()
759
+ @matchers = matchers
760
+ end
761
+
762
+ def children
763
+ find_children @matchers
764
+ end
765
+
766
+ def to_s
767
+ "#{assign_to_s}#{"Array.(#{matchers.join(',')})" if matchers}"
768
+ end
769
+
770
+ def ==(other)
771
+ other.kind_of? self.class and
772
+ self.matchers == other.matchers
773
+ end
774
+
775
+ protected
776
+
777
+ def matching?(other)
778
+ other.kind_of? ::Array and
779
+ matchers.size == other.size and
780
+ matchers.each_with_index.all? { |m, i| m === other[i] }
781
+ end
782
+ end
783
+
784
+ class ::Array
785
+ def self.call(*matchers)
786
+ Matchers::Array.new *matchers
787
+ end
788
+ end
789
+
790
+ # TODO Hash matcher
791
+ # TODO Method matcher (:size, matcher)
792
+
793
+ class Product < Abstract
794
+ attr_reader :algebraic_type, :field_matchers
795
+
796
+ def initialize(algebraic_type, *field_matchers)
797
+ super()
798
+ is_kind_of! algebraic_type, Algebrick::Product, Algebrick::ProductVariant
799
+ @algebraic_type = algebraic_type
800
+ field_matchers += ::Array.new(algebraic_type.fields.size) { Algebrick.any } if field_matchers.empty?
801
+ @field_matchers = field_matchers
802
+ raise ArgumentError unless algebraic_type.fields.size == field_matchers.size
803
+ end
804
+
805
+ def children
806
+ find_children @field_matchers
807
+ end
808
+
809
+ def to_s
810
+ assign_to_s + "#{@algebraic_type.name}.(#{@field_matchers.join(',')})"
811
+ end
812
+
813
+ def ==(other)
814
+ other.kind_of? self.class and
815
+ self.algebraic_type == other.algebraic_type and
816
+ self.field_matchers == other.field_matchers
817
+ end
818
+
819
+ protected
820
+
821
+ def matching?(other)
822
+ other.kind_of?(@algebraic_type) and other.kind_of?(ProductConstructor) and
823
+ @field_matchers.zip(other.fields).all? do |matcher, field|
824
+ matcher === field
825
+ end
826
+ end
827
+ end
828
+
829
+ class Variant < Wrapper
830
+ def initialize(something)
831
+ is_kind_of! something, Algebrick::Variant, Algebrick::ProductVariant
832
+ super something
833
+ end
834
+
835
+ def to_s
836
+ assign_to_s + "#{@something.name}.to_m"
837
+ end
838
+ end
839
+
840
+ class Atom < Wrapper
841
+ def initialize(something)
842
+ is_kind_of! something, Algebrick::Atom
843
+ super something
844
+ end
845
+
846
+ def to_s
847
+ assign_to_s + "#{@something.name}.to_m"
848
+ end
849
+ end
850
+ end
851
+
852
+ module DSL
853
+ class PreType
854
+ attr_reader :environment, :name, :fields, :variants, :definition
855
+
856
+ def initialize(environment, name)
857
+ @environment = environment
858
+ @name = name
859
+ @fields = []
860
+ @variants = nil
861
+ @definition = nil
862
+ end
863
+
864
+ def |(other)
865
+ [self, other]
866
+ end
867
+
868
+ def to_ary
869
+ [self]
870
+ end
871
+
872
+ def fields=(fields)
873
+ raise unless @fields.empty?
874
+ @fields += fields
875
+ end
876
+
877
+ def definition=(block)
878
+ raise if @definition
879
+ @definition = block
880
+ end
881
+
882
+ def is(variants)
883
+ raise if @variants
884
+ @variants = variants
885
+ self
886
+ end
887
+
888
+ alias_method :===, :is
889
+
890
+ def kind
891
+ if @variants
892
+ if @fields.empty?
893
+ Variant
894
+ else
895
+ ProductVariant
896
+ end
897
+ else
898
+ if @fields.empty?
899
+ Atom
900
+ else
901
+ Product
902
+ end
903
+ end
904
+ end
905
+ end
906
+
907
+ class Environment
908
+ attr_reader :pre_types
909
+ def initialize(base, &definition)
910
+ @base = if base.is_a?(Object) && base.to_s == 'main'
911
+ Object
912
+ else
913
+ base
914
+ end
915
+ @pre_types = {}
916
+ instance_eval &definition
917
+ end
918
+
919
+ def method_missing(method, *fields, &definition)
920
+ const_name = method.to_s.split('_').map { |s| s[0] = s[0].upcase; s }.join
921
+
922
+ @pre_types[const_name] ||= PreType.new(self, const_name)
923
+ @pre_types[const_name].fields = fields unless fields.empty?
924
+ @pre_types[const_name].definition = definition if definition
925
+ @pre_types[const_name]
926
+ end
927
+
928
+ def run
929
+ define_constants
930
+ define_fields_and_variants
931
+ eval_definitions
932
+ @pre_types.map { |name, _| get_class name }
933
+ end
934
+
935
+ private
936
+
937
+ def define_constants
938
+ @pre_types.each do |name, pre_type|
939
+ type = pre_type.kind.allocate
940
+ if @base.const_defined? name
941
+ defined = @base.const_get(name)
942
+ # #unless defined == type
943
+ raise "#{name} already defined as #{defined}"
944
+ # #end
945
+ else
946
+ #puts "defining #{name.to_sym.inspect} in #{@base}"
947
+ @base.const_set name.to_sym, type
948
+ end
949
+ end
950
+ end
951
+
952
+ def define_fields_and_variants
953
+ select = ->(klass, &block) do
954
+ @pre_types.select { |_, pre_type| pre_type.kind == klass }.
955
+ map { |name, pre_type| [name, get_class(name), pre_type] }.
956
+ each &block
957
+ end
958
+
959
+ select.(Atom) do |name, type, pre_type|
960
+ type.send :initialize
961
+ end
962
+
963
+ select.(Product) do |name, type, pre_type|
964
+ type.send :initialize, *pre_type.fields.map { |f| get_class f }
965
+ end
966
+
967
+ select.(Variant) do |name, type, pre_type|
968
+ type.send :initialize, *pre_type.variants.map { |v| get_class v }
969
+ end
970
+
971
+ select.(ProductVariant) do |name, type, pre_type|
972
+ type.send :initialize,
973
+ pre_type.fields.map { |f| get_class f },
974
+ pre_type.variants.map { |v| get_class v }
975
+ end
976
+ end
977
+
978
+ def eval_definitions
979
+ @pre_types.each do |name, pre_type|
980
+ next unless pre_type.definition
981
+ type = get_class name
982
+ type.module_eval &pre_type.definition
983
+ end
984
+ end
985
+
986
+ def get_class(key)
987
+ if key.kind_of? String
988
+ @base.const_get key
989
+ elsif key.kind_of? PreType
990
+ @base.const_get key.name
991
+ elsif key.kind_of? Hash
992
+ key.each { |k, v| key[k] = get_class v }
993
+ else
994
+ key
995
+ end
996
+ end
997
+ end
998
+
999
+ def type_def(&definition)
1000
+ Environment.new(self, &definition).run
1001
+ end
1002
+ end
1003
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: algebrick
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Petr Chalupa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: turn
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: kramdown
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: multi_json
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Provides algebraic type definitions and pattern matching
98
+ email: git@pitr.ch
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files:
102
+ - MIT-LICENSE
103
+ - README.md
104
+ - README_FULL.md
105
+ files:
106
+ - lib/algebrick.rb
107
+ - MIT-LICENSE
108
+ - README.md
109
+ - README_FULL.md
110
+ homepage: https://github.com/pitr-ch/algebrick
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.0.0
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Algebraic types and pattern matching for Ruby
134
+ test_files: []
135
+ has_rdoc: