algebrick 0.3.3 → 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 +4 -4
- data/README.md +7 -7
- data/README_FULL.md +53 -31
- data/VERSION +1 -1
- data/lib/algebrick.rb +33 -54
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3e1690d46e7e9d3c9332eed298a46d35ec628ce
|
4
|
+
data.tar.gz: 7cf4ccef5ae69a055e9ac7806b84e0a6724d7c1b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae2f4b9cd4bebbb889b8d91383cbe9380d8b2c440104c0302b4b128b589fbdb6d1bdae886a49cf790022a76f1fe304180bc8b6ed77da6ce60d6aba038fac67fd
|
7
|
+
data.tar.gz: becadc540cc08d5002060e0fc1e64eac87d1848ff11bc44b6e628a45b61bc705fcce870741e568b2e6803e39b08b7f789d42e2926d176f8c40c8083bea3db21c
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/pitr-ch/algebrick)
|
4
4
|
|
5
|
-
It's a
|
5
|
+
It's a gem providing **algebraic types** and **pattern matching** seamlessly integrates with standard features Ruby.
|
6
6
|
|
7
7
|
- Documentation: <http://blog.pitr.ch/algebrick>
|
8
8
|
- Source: <https://github.com/pitr-ch/algebrick>
|
@@ -27,24 +27,24 @@ end
|
|
27
27
|
```
|
28
28
|
|
29
29
|
Now types `Tree(Empty | Leaf | Node)`, `Empty`, `Leaf(Integer)` and `Node(Tree, Tree)` are defined.
|
30
|
-
|
30
|
+
Let's add a method, don't miss the **pattern matching** example.
|
31
31
|
|
32
32
|
```ruby
|
33
33
|
module Tree
|
34
34
|
# compute depth of a tree
|
35
35
|
def depth
|
36
36
|
match self,
|
37
|
-
Empty
|
38
|
-
Leaf
|
37
|
+
(on Empty, 0),
|
38
|
+
(on Leaf, 1),
|
39
39
|
# ~ will store and pass matched parts to variables left and right
|
40
|
-
Node.(~any, ~any)
|
40
|
+
(on Node.(~any, ~any) do |left, right|
|
41
41
|
1 + [left.depth, right.depth].max
|
42
|
-
end
|
42
|
+
end)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
```
|
46
46
|
|
47
|
-
|
47
|
+
Method defined in module `Tree` are passed down to **all** values of type Tree
|
48
48
|
|
49
49
|
```ruby
|
50
50
|
Empty.depth # => 0
|
data/README_FULL.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Algebrick
|
2
2
|
|
3
|
-
|
3
|
+
It's a gem providing **algebraic types** and **pattern matching** and seamlessly integrating with standard features of Ruby.
|
4
4
|
|
5
5
|
- Documentation: <http://blog.pitr.ch/algebrick>
|
6
6
|
- Source: <https://github.com/pitr-ch/algebrick>
|
@@ -10,30 +10,46 @@ Is a small gem providing **algebraic types** and **pattern matching** on them fo
|
|
10
10
|
|
11
11
|
{include:file:doc/quick_example.out.rb}
|
12
12
|
|
13
|
-
## Algebraic types
|
13
|
+
## Algebraic types
|
14
14
|
|
15
|
-
|
15
|
+
Algebraic type is a kind of composite type, i.e. a type formed by combining other types.
|
16
|
+
In Haskell algebraic type looks like this:
|
16
17
|
|
17
18
|
data Tree = Empty
|
18
19
|
| Leaf Int
|
19
20
|
| Node Tree Tree
|
21
|
+
|
22
|
+
depth :: Tree -> Int
|
23
|
+
depth Empty = 0
|
24
|
+
depth (Leaf n) = 1
|
25
|
+
depth (Node l r) = 1 + max (depth l) (depth r)
|
26
|
+
|
27
|
+
depth (Node Empty (Leaf 5)) -- => 2
|
20
28
|
|
21
|
-
|
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}.
|
29
|
+
This snippet defines type `Tree` which has 3 possible values:
|
24
30
|
|
25
|
-
|
31
|
+
- `Empty`
|
32
|
+
- `Leaf` with and extra value of type `Int`
|
33
|
+
- `Node` with two values of type `Tree`
|
26
34
|
|
27
|
-
|
35
|
+
and function `depth` to calculate depth of a tree which is called on the last line and evaluates to `2`.
|
28
36
|
|
29
|
-
|
37
|
+
Same `Tree` type and `depth` method can be also defined with this gem as it was shown in {file:README_FULL.md#quick-example Quick Example}.
|
30
38
|
|
31
|
-
|
32
|
-
2. **Product** a type that has a set number of fields with given type e.g. `Leaf(Integer)`
|
33
|
-
3. **Variant** a type that does have set number of variants e.g. `Tree(Empty | Leaf(Integer) | Node(Tree, Tree)`. It means that values of `Empty`, `Leaf[1]`, `Node[Empty, Empry]` are all of type `Tree`.
|
34
|
-
4. **ProductVariant** will be created when a recursive type like `List(Empty | List(Integer, List))` is defined. `List` has two variants `Empty` and itself, and simultaneously it has fields as product type.
|
39
|
+
### Algebrick implementation
|
35
40
|
|
36
|
-
|
41
|
+
Algebrick distinguishes 4 kinds of algebraic types:
|
42
|
+
|
43
|
+
1. **Atom** - type that has only one value, e.g. `Empty`.
|
44
|
+
2. **Product** - type that has a set number of fields with given type, e.g. `Leaf(Integer)`
|
45
|
+
3. **Variant** - type that is one of the set variants, e.g. `Tree(Empty | Leaf(Integer) | Node(Tree, Tree)`, meaning that values `Empty`, `Leaf[1]`, `Node[Empty, Empry]` have all type `Tree`.
|
46
|
+
4. **ProductVariant** - will be created when a recursive type like `List(Empty | List(Integer, List))` is defined. `List` has two variants: `Empty` and itself. Simultaneously it has fields as a product type.
|
47
|
+
|
48
|
+
Atom type is implemented with {Algebrick::Atom} and the rest is implemented with {Algebrick::ProductVariant} which behaves differently based on what is set: fields, variants, or both.
|
49
|
+
|
50
|
+
More information can be found at <https://en.wikipedia.org/wiki/Algebraic_data_type>.
|
51
|
+
|
52
|
+
## Documentation
|
37
53
|
|
38
54
|
### Type definition
|
39
55
|
|
@@ -43,14 +59,14 @@ Atom type is implemented with {Algebrick::Atom} and the rest is implemented with
|
|
43
59
|
|
44
60
|
{include:file:doc/values.out.rb}
|
45
61
|
|
46
|
-
###
|
62
|
+
### Behaviour extending
|
47
63
|
|
48
64
|
{include:file:doc/extending_behavior.out.rb}
|
49
65
|
|
50
66
|
### Pattern matching
|
51
67
|
|
52
|
-
|
53
|
-
`#===` method
|
68
|
+
Pattern matching is implemented with helper objects defined in `ALgebrick::Matchers`.
|
69
|
+
They use standard `#===` method to match against values.
|
54
70
|
|
55
71
|
{include:file:doc/pattern_matching.out.rb}
|
56
72
|
|
@@ -60,29 +76,24 @@ Algebraic matchers are helper objects to match algebraic values and others with
|
|
60
76
|
|
61
77
|
## What is it good for?
|
62
78
|
|
63
|
-
### Defining data
|
64
|
-
|
65
|
-
{include:file:doc/data.out.rb}
|
66
|
-
|
67
|
-
### Serialization
|
79
|
+
### Defining data structures
|
68
80
|
|
69
|
-
|
70
|
-
messege-based cross-process comunication.
|
81
|
+
<!-- {include:file:doc/data.out.rb} -->
|
71
82
|
|
72
|
-
|
83
|
+
- Simple data structures like trees
|
84
|
+
- Whenever you find yourself to pass around too many fragile Hash-Array structures
|
73
85
|
|
74
|
-
|
86
|
+
_Examples are coming shortly._
|
75
87
|
|
76
|
-
|
88
|
+
### Serialization
|
77
89
|
|
78
|
-
|
90
|
+
Algebraic types also play nice with JSON serialization and de-serialization making it ideal for defining message-based cross-process communication.
|
79
91
|
|
80
|
-
|
81
|
-
on one place, no need to track methods in two separate classes `User` and `NullUser`.
|
92
|
+
{include:file:doc/json.out.rb}
|
82
93
|
|
83
94
|
### Message matching in Actor pattern
|
84
95
|
|
85
|
-
Just small snippet
|
96
|
+
Just a small snippet how it can be used in Actor model world.
|
86
97
|
|
87
98
|
class Worker < AbstractActor
|
88
99
|
def initialize(executor)
|
@@ -97,3 +108,14 @@ Just small snippet from a gem I am still working on.
|
|
97
108
|
end
|
98
109
|
end
|
99
110
|
end
|
111
|
+
|
112
|
+
<!--
|
113
|
+
### Null Object Pattern
|
114
|
+
|
115
|
+
see {http://en.wikipedia.org/wiki/Null_Object_pattern#Ruby}.
|
116
|
+
|
117
|
+
{include:file:doc/null.out.rb}
|
118
|
+
|
119
|
+
This has advantage over a classical approach that the methods are defined
|
120
|
+
on one place, no need to track methods in two separate classes `User` and `NullUser`.
|
121
|
+
-->
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/algebrick.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2013 Petr Chalupa <git@pitr.ch>
|
1
|
+
# Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -59,6 +59,7 @@ module Algebrick
|
|
59
59
|
|
60
60
|
module TypeCheck
|
61
61
|
# FIND: type checking of collections?
|
62
|
+
|
62
63
|
def Type?(value, *types)
|
63
64
|
types.any? { |t| value.is_a? t }
|
64
65
|
end
|
@@ -94,31 +95,7 @@ module Algebrick
|
|
94
95
|
|
95
96
|
def self.error(value, message, types)
|
96
97
|
raise TypeError,
|
97
|
-
"
|
98
|
-
end
|
99
|
-
|
100
|
-
public
|
101
|
-
|
102
|
-
raise 'remove deprecations' if Algebrick.version >= Gem::Version.new('0.4')
|
103
|
-
|
104
|
-
def is_kind_of?(value, *types)
|
105
|
-
warn "is_kind_of? is deprecated use Type?\n#{caller[0]}"
|
106
|
-
Type? value, *types
|
107
|
-
end
|
108
|
-
|
109
|
-
def is_kind_of!(value, *types)
|
110
|
-
warn "Type! is deprecated use Type!\n#{caller[0]}"
|
111
|
-
Type! value, *types
|
112
|
-
end
|
113
|
-
|
114
|
-
def is_matching?(value, *types)
|
115
|
-
warn "is_matching? is deprecated use Match?\n#{caller[0]}"
|
116
|
-
Match? value, *types
|
117
|
-
end
|
118
|
-
|
119
|
-
def is_matching!(value, *types)
|
120
|
-
warn "is_matching! is deprecated use Match!\n#{caller[0]}"
|
121
|
-
Match! value, *types
|
98
|
+
"Value (#{value.class}) '#{value}' #{message} any of: #{types.join('; ')}."
|
122
99
|
end
|
123
100
|
end
|
124
101
|
|
@@ -128,8 +105,6 @@ module Algebrick
|
|
128
105
|
Matchers::Any.new
|
129
106
|
end
|
130
107
|
|
131
|
-
alias_method :_, :any
|
132
|
-
|
133
108
|
def match(value, *cases)
|
134
109
|
cases = if cases.size == 1 && cases.first.is_a?(Hash)
|
135
110
|
cases.first
|
@@ -153,7 +128,7 @@ module Algebrick
|
|
153
128
|
[matcher, value || block]
|
154
129
|
end
|
155
130
|
|
156
|
-
#
|
131
|
+
# FIND: #match! raise when match is not complete on a given type
|
157
132
|
|
158
133
|
private
|
159
134
|
|
@@ -186,10 +161,6 @@ module Algebrick
|
|
186
161
|
to_m | other
|
187
162
|
end
|
188
163
|
|
189
|
-
def ^(other)
|
190
|
-
to_m ^ other
|
191
|
-
end
|
192
|
-
|
193
164
|
def !
|
194
165
|
!to_m
|
195
166
|
end
|
@@ -397,6 +368,14 @@ module Algebrick
|
|
397
368
|
self.class.type
|
398
369
|
end
|
399
370
|
|
371
|
+
def self.name
|
372
|
+
@type.to_s
|
373
|
+
end
|
374
|
+
|
375
|
+
def self.to_s
|
376
|
+
name
|
377
|
+
end
|
378
|
+
|
400
379
|
def self.type=(type)
|
401
380
|
raise if @type
|
402
381
|
@type = type
|
@@ -511,7 +490,7 @@ module Algebrick
|
|
511
490
|
end
|
512
491
|
|
513
492
|
def call(*field_matchers)
|
514
|
-
raise TypeError unless @fields
|
493
|
+
raise TypeError, "#{self} does not have any fields" unless @fields
|
515
494
|
Matchers::Product.new self, *field_matchers
|
516
495
|
end
|
517
496
|
|
@@ -863,10 +842,6 @@ module Algebrick
|
|
863
842
|
Not.new self
|
864
843
|
end
|
865
844
|
|
866
|
-
def ^(matcher)
|
867
|
-
Xor.new self, matcher
|
868
|
-
end
|
869
|
-
|
870
845
|
def assign?
|
871
846
|
@assign
|
872
847
|
end
|
@@ -981,12 +956,6 @@ module Algebrick
|
|
981
956
|
def matching?(other)
|
982
957
|
matchers.any? { |m| m === other }
|
983
958
|
end
|
984
|
-
end
|
985
|
-
|
986
|
-
class Xor < Or
|
987
|
-
def to_s
|
988
|
-
matchers.join ' ^ '
|
989
|
-
end
|
990
959
|
|
991
960
|
alias_method :super_children, :children
|
992
961
|
private :super_children
|
@@ -1158,15 +1127,6 @@ module Algebrick
|
|
1158
1127
|
field_matchers.key?(field) ? field_matchers[field] : Algebrick.any
|
1159
1128
|
end
|
1160
1129
|
|
1161
|
-
# AProduct.(:field_name)
|
1162
|
-
when field_matchers.all? { |v| v.is_a? Symbol }
|
1163
|
-
unless (dif = field_matchers - algebraic_type.field_names).empty?
|
1164
|
-
raise ArgumentError, "no #{dif} fields in #{algebraic_type}"
|
1165
|
-
end
|
1166
|
-
algebraic_type.field_names.map do |field|
|
1167
|
-
field_matchers.include?(field) ? ~Algebrick.any : Algebrick.any
|
1168
|
-
end
|
1169
|
-
|
1170
1130
|
# normal
|
1171
1131
|
else
|
1172
1132
|
field_matchers
|
@@ -1181,9 +1141,11 @@ module Algebrick
|
|
1181
1141
|
end
|
1182
1142
|
|
1183
1143
|
def to_s
|
1184
|
-
assign_to_s + "#{@algebraic_type.name}.(#{@field_matchers.join(',')})"
|
1144
|
+
assign_to_s + "#{@algebraic_type.name}.(#{@field_matchers.join(', ')})"
|
1185
1145
|
end
|
1186
1146
|
|
1147
|
+
# TODO prety_print for all matchers
|
1148
|
+
|
1187
1149
|
def ==(other)
|
1188
1150
|
other.kind_of? self.class and
|
1189
1151
|
self.algebraic_type == other.algebraic_type and
|
@@ -1224,4 +1186,21 @@ module Algebrick
|
|
1224
1186
|
end
|
1225
1187
|
end
|
1226
1188
|
|
1189
|
+
module Types
|
1190
|
+
Maybe = Algebrick.type(:v) do
|
1191
|
+
variants None = atom,
|
1192
|
+
Some = type(:v) { fields :v }
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
module Maybe
|
1196
|
+
def maybe
|
1197
|
+
match self,
|
1198
|
+
None >> nil,
|
1199
|
+
Some >-> { yield value }
|
1200
|
+
end
|
1201
|
+
end
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
include Types
|
1205
|
+
|
1227
1206
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: algebrick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Petr Chalupa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|