algebrick 0.1.0

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