myrrha 1.2.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +62 -29
- data/README.md +117 -121
- data/Rakefile +0 -12
- data/examples/sbyc_domain.rb +1 -1
- data/lib/myrrha.rb +11 -364
- data/lib/myrrha/coerce.rb +22 -25
- data/lib/myrrha/coercions.rb +265 -0
- data/lib/myrrha/domain.rb +18 -0
- data/lib/myrrha/domain/coercion_methods.rb +17 -0
- data/lib/myrrha/domain/impl.rb +50 -0
- data/lib/myrrha/domain/sbyc.rb +78 -0
- data/lib/myrrha/domain/value_methods.rb +60 -0
- data/lib/myrrha/errors.rb +12 -0
- data/lib/myrrha/loader.rb +0 -1
- data/lib/myrrha/to_ruby_literal.rb +1 -1
- data/lib/myrrha/version.rb +3 -3
- data/myrrha.noespec +3 -3
- data/spec/coercions/test_append.rb +3 -3
- data/spec/coercions/test_belongs_to.rb +12 -6
- data/spec/coercions/test_convert.rb +5 -5
- data/spec/coercions/test_delegate.rb +35 -0
- data/spec/coercions/test_subdomain.rb +9 -3
- data/spec/domain/impl/test_behavior.rb +41 -0
- data/spec/domain/impl/test_value_methods.rb +101 -0
- data/spec/domain/sbyc/test_behavior.rb +46 -0
- data/spec/domain/test_sbyc.rb +81 -0
- data/spec/myrrha/test_coercions.rb +1 -1
- data/spec/test_myrrha.rb +0 -3
- data/tasks/examples.rake +2 -1
- data/tasks/gem.rake +9 -4
- metadata +83 -43
- data/spec/myrrha/test_domain.rb +0 -116
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,41 @@
|
|
1
|
+
# 2.0.0 / 2012-09-26
|
2
|
+
|
3
|
+
* Removed support for ruby 1.8.7
|
4
|
+
* Coercions#subdomain? and Coercions#belongs_to? are now protected
|
5
|
+
* In case of coercion failure, Myrrha::Error keeps the first coercion error under `cause`
|
6
|
+
(that might be nil if no rule was triggered or no rule explcitely failed).
|
7
|
+
* Added Coercions#delegate as a shortcut for upon rules that work by delegation to the
|
8
|
+
value.
|
9
|
+
|
10
|
+
* Defining domains through subclassing and specialization by constraints must now be made
|
11
|
+
as shown below. Factored domains gain a Coercions instance under `coercions`.
|
12
|
+
|
13
|
+
class NegInt < Integer
|
14
|
+
extend Myrrha::Domain::SByC.new(Integer, [], lambda{|i| i<0})
|
15
|
+
|
16
|
+
coercions do |c|
|
17
|
+
c.coercion(String){|v,t| ...}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
* Added a Domain::Impl module for implementing domains from scratch (vs. sbyc). Factored
|
22
|
+
domains have a default constructor taking components as parameters, an attribute reader
|
23
|
+
for each component as well as hash and equality methods properly defined. They also have
|
24
|
+
a Coercions instance under `coercions`.
|
25
|
+
|
26
|
+
class Point
|
27
|
+
include Domain::Impl.new(:x, :y)
|
28
|
+
|
29
|
+
coercions do |c|
|
30
|
+
c.coercion(String){|v,t| ...}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
1
34
|
# 1.2.2 / 2012-01-26
|
2
35
|
|
3
36
|
* Ensure that inheritance intuitively applies when duplicating a set of coercion
|
4
|
-
rules. Rules that, in the parent, rely on the recursive application of other
|
5
|
-
rules (such as recursively applying coercions on arrays) will now correctly
|
37
|
+
rules. Rules that, in the parent, rely on the recursive application of other
|
38
|
+
rules (such as recursively applying coercions on arrays) will now correctly
|
6
39
|
use the rules defined on the duplicated Coercions object.
|
7
40
|
|
8
41
|
In particular, this means that the following scenario now correctly works:
|
@@ -12,7 +45,7 @@
|
|
12
45
|
end
|
13
46
|
Dupped.apply([1, Foo.new])
|
14
47
|
|
15
|
-
In the scenario above, Foo was marshalled as the new rules was not used by
|
48
|
+
In the scenario above, Foo was marshalled as the new rules was not used by
|
16
49
|
the Array rule, defined on the parent.
|
17
50
|
|
18
51
|
# 1.2.1 / 2011-08-31
|
@@ -25,16 +58,16 @@
|
|
25
58
|
* Added the ability to created SByC domains through simple module extension:
|
26
59
|
|
27
60
|
NegInt = Myrrha.domain(Integer){|i| i < 0}
|
28
|
-
|
61
|
+
|
29
62
|
can also be built the following way:
|
30
63
|
|
31
64
|
class NegInt < Integer
|
32
65
|
extend Myrrha::Domain
|
33
|
-
|
66
|
+
|
34
67
|
def self.predicate
|
35
68
|
@predicate ||= lambda{|i| i < 0}
|
36
69
|
end
|
37
|
-
|
70
|
+
|
38
71
|
end
|
39
72
|
|
40
73
|
* Cleaned the development dependencies, travis-ci.org continuous integration,
|
@@ -53,57 +86,57 @@
|
|
53
86
|
|
54
87
|
* Added following coercion rules for Booleans
|
55
88
|
|
56
|
-
coerce("true", TrueClass) # => true
|
57
|
-
coerce("false", FalseClass) # => false
|
89
|
+
coerce("true", TrueClass) # => true
|
90
|
+
coerce("false", FalseClass) # => false
|
58
91
|
|
59
|
-
* Added coercion rule from any Object to String through ruby's String(). Note
|
60
|
-
that even with this coercion rule, coerce(nil, String) returns nil as that
|
92
|
+
* Added coercion rule from any Object to String through ruby's String(). Note
|
93
|
+
that even with this coercion rule, coerce(nil, String) returns nil as that
|
61
94
|
rule has higher priority.
|
62
|
-
|
63
|
-
* require('time') is automatically issued when trying to coerce a String to
|
64
|
-
a Time. Time.parse is obviously needed.
|
95
|
+
|
96
|
+
* require('time') is automatically issued when trying to coerce a String to
|
97
|
+
a Time. Time.parse is obviously needed.
|
65
98
|
|
66
99
|
* Myrrha::Boolean (Boolean with core extensions) is now a factored domain (see
|
67
|
-
below). Therefore, it is now a true Class instance.
|
100
|
+
below). Therefore, it is now a true Class instance.
|
68
101
|
|
69
102
|
## Enhancements to the general coercion mechanism
|
70
103
|
|
71
|
-
* An optimistic coercion is tried when a rule is encountered whose target
|
104
|
+
* An optimistic coercion is tried when a rule is encountered whose target
|
72
105
|
domain is a super domain of the requested one. Coercion only succeeds if
|
73
106
|
the coerced value correctly belongs to the latter domain. Example:
|
74
|
-
|
107
|
+
|
75
108
|
rules = Myrrha.coercions do |r|
|
76
|
-
r.coercion String, Numeric, lambda{|s,t| Integer(s)}
|
77
|
-
end
|
109
|
+
r.coercion String, Numeric, lambda{|s,t| Integer(s)}
|
110
|
+
end
|
78
111
|
rules.coerce("12", Integer) # => 12 in 1.1.0 while it failed in 1.0.0
|
79
112
|
rules.coerce("12", Float) # => Myrrha::Error
|
80
113
|
|
81
|
-
* You can now specify a coercion path, through an array of domains. For
|
114
|
+
* You can now specify a coercion path, through an array of domains. For
|
82
115
|
example (completely contrived, of course):
|
83
116
|
|
84
117
|
rules = Myrrha.coercions do |r|
|
85
118
|
r.coercion String, Symbol, lambda{|s,t| s.to_sym }
|
86
119
|
r.coercion Float, String, lambda{|s,t| s.to_s }
|
87
120
|
r.coercion Integer, Float, lambda{|s,t| Float(s) }
|
88
|
-
r.coercion Integer, Symbol, [Float, String]
|
121
|
+
r.coercion Integer, Symbol, [Float, String]
|
89
122
|
end
|
90
123
|
rules.coerce(12, Symbol) # => :"12.0" as Symbol(String(Float(12)))
|
91
124
|
|
92
|
-
* You can now define domains through specialization by constraint (sbyc) on ruby
|
125
|
+
* You can now define domains through specialization by constraint (sbyc) on ruby
|
93
126
|
classes, using Myrrha.domain:
|
94
|
-
|
127
|
+
|
95
128
|
# Create a positive integer domain, as ... positive integers
|
96
129
|
PosInt = Myrrha.domain(Integer){|i| i > 0 }
|
97
|
-
|
98
|
-
Created domain is a real Class instance, that correctly responds to :===
|
99
|
-
and :superclass. The feature is mainly introduced for supporting the following
|
130
|
+
|
131
|
+
Created domain is a real Class instance, that correctly responds to :===
|
132
|
+
and :superclass. The feature is mainly introduced for supporting the following
|
100
133
|
kind of coercion scenarios (see README for more about this):
|
101
|
-
|
134
|
+
|
102
135
|
rules = Myrrha.coercions do |r|
|
103
|
-
r.coercion String, Integer, lambda{|s,t| Integer(s)}
|
104
|
-
end
|
136
|
+
r.coercion String, Integer, lambda{|s,t| Integer(s)}
|
137
|
+
end
|
105
138
|
rules.coerce("12", PosInt) # => 12
|
106
|
-
rules.coerce("-12", PosInt) # => ArgumentError, "Invalid value -12 for PosInt"
|
139
|
+
rules.coerce("-12", PosInt) # => ArgumentError, "Invalid value -12 for PosInt"
|
107
140
|
|
108
141
|
## Bug fixes
|
109
142
|
|
data/README.md
CHANGED
@@ -8,17 +8,17 @@
|
|
8
8
|
Myrrha provides the coercion framework which is missing to Ruby, IMHO. Coercions
|
9
9
|
are simply defined as a set of rules for converting values from source to target
|
10
10
|
domains (in an abstract sense). As a typical and useful example, it comes bundled
|
11
|
-
with a coerce() method providing a unique entry point for converting a string to
|
12
|
-
a numeric, a boolean, a date, a time, an URI, and so on.
|
11
|
+
with a coerce() method providing a unique entry point for converting a string to
|
12
|
+
a numeric, a boolean, a date, a time, an URI, and so on.
|
13
13
|
|
14
14
|
### Install
|
15
15
|
|
16
16
|
% [sudo] gem install myrrha
|
17
17
|
|
18
|
-
### Bundler & Require
|
18
|
+
### Bundler & Require
|
19
19
|
|
20
|
-
# Bug fixes (tiny) do not even add new default rules to coerce and
|
21
|
-
# to\_ruby\_literal. Minor version can, which could break your code.
|
20
|
+
# Bug fixes (tiny) do not even add new default rules to coerce and
|
21
|
+
# to\_ruby\_literal. Minor version can, which could break your code.
|
22
22
|
# Therefore, please always use:
|
23
23
|
gem "myrrha", "~> 1.2.2"
|
24
24
|
|
@@ -37,13 +37,13 @@ a numeric, a boolean, a date, a time, an URI, and so on.
|
|
37
37
|
|
38
38
|
Having a single entry point for coercing values from one data-type (typically
|
39
39
|
a String) to another one is very useful. Unfortunately, Ruby does not provide
|
40
|
-
such a unique entry point... Thanks to Myrrah, the following scenario is
|
40
|
+
such a unique entry point... Thanks to Myrrah, the following scenario is
|
41
41
|
possible and even straightforward:
|
42
42
|
|
43
43
|
require 'myrrha/with_core_ext'
|
44
44
|
require 'myrrha/coerce'
|
45
45
|
require 'date'
|
46
|
-
|
46
|
+
|
47
47
|
values = ["12", "true", "2011-07-20"]
|
48
48
|
types = [Integer, Boolean, Date]
|
49
49
|
values.zip(types).collect do |value,domain|
|
@@ -53,27 +53,27 @@ possible and even straightforward:
|
|
53
53
|
|
54
54
|
### Implemented coercions
|
55
55
|
|
56
|
-
Implemented coercions are somewhat conservative, and only use a subset of what
|
57
|
-
ruby provides here and there. This is to avoid strangeness ala PHP... The
|
56
|
+
Implemented coercions are somewhat conservative, and only use a subset of what
|
57
|
+
ruby provides here and there. This is to avoid strangeness ala PHP... The
|
58
58
|
general philosophy is to provide the natural coercions we apply everyday.
|
59
59
|
|
60
60
|
The master rules are
|
61
61
|
|
62
|
-
* <code>coerce(value, Domain)</code>
|
62
|
+
* <code>coerce(value, Domain)</code> returns <code>value</code> if
|
63
63
|
<code>belongs_to?(value, Domain)</code> is true (see last section below)
|
64
64
|
* <code>coerce(value, Domain)</code> returns <code>Domain.coerce(value)</code>
|
65
65
|
if the latter method exists.
|
66
66
|
* <code>coerce("any string", Domain)</code> returns <code>Domain.parse(value)</code>
|
67
67
|
if the latter method exists.
|
68
68
|
|
69
|
-
The specific implemented rules are
|
69
|
+
The specific implemented rules are
|
70
70
|
|
71
71
|
require 'myrrha/with_core_ext'
|
72
72
|
require 'myrrha/coerce'
|
73
|
-
|
73
|
+
|
74
74
|
# NilClass -> _Anything_ returns nil, always
|
75
75
|
coerce(nil, Integer) # => nil
|
76
|
-
|
76
|
+
|
77
77
|
# Object -> String, via ruby's String()
|
78
78
|
coerce("hello", String) # => "hello"
|
79
79
|
coerce(:hello, String) # => "hello"
|
@@ -81,38 +81,38 @@ The specific implemented rules are
|
|
81
81
|
# String -> Numeric, through ruby's Integer() and Float()
|
82
82
|
coerce("12", Integer) # => 12
|
83
83
|
coerce("12.0", Float) # => 12.0
|
84
|
-
|
84
|
+
|
85
85
|
# String -> Numeric is smart enough:
|
86
86
|
coerce("12", Numeric) # => 12 (Integer)
|
87
87
|
coerce("12.0", Numeric) # => 12.0 (Float)
|
88
|
-
|
88
|
+
|
89
89
|
# String -> Regexp, through Regexp.compile
|
90
90
|
coerce("[a-z]+", Regexp) # => /[a-z]+/
|
91
|
-
|
91
|
+
|
92
92
|
# String -> Symbol, through to_sym
|
93
|
-
coerce("hello", Symbol) # => :hello
|
94
|
-
|
93
|
+
coerce("hello", Symbol) # => :hello
|
94
|
+
|
95
95
|
# String -> Boolean (hum, sorry Matz!)
|
96
96
|
coerce("true", Boolean) # => true
|
97
97
|
coerce("false", Boolean) # => false
|
98
98
|
coerce("true", TrueClass) # => true
|
99
99
|
coerce("false", FalseClass) # => false
|
100
|
-
|
101
|
-
# String -> Date, through Date.parse
|
100
|
+
|
101
|
+
# String -> Date, through Date.parse
|
102
102
|
require 'date'
|
103
|
-
coerce("2011-07-20", Date) # => #<Date: 2011-07-20 (4911525/2,0,2299161)>
|
104
|
-
|
103
|
+
coerce("2011-07-20", Date) # => #<Date: 2011-07-20 (4911525/2,0,2299161)>
|
104
|
+
|
105
105
|
# String -> Time, through Time.parse (just in time issuing of require('time'))
|
106
106
|
coerce("2011-07-20 10:57", Time) # => 2011-07-20 10:57:00 +0200
|
107
|
-
|
107
|
+
|
108
108
|
# String -> URI, through URI.parse
|
109
109
|
require 'uri'
|
110
|
-
coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
|
110
|
+
coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
|
111
111
|
|
112
112
|
# String -> Class and Module through constant lookup
|
113
113
|
coerce("Integer", Class) # => Integer
|
114
114
|
coerce("Myrrha::Version", Module) # => Myrrha::Version
|
115
|
-
|
115
|
+
|
116
116
|
# Symbol -> Class and Module through constant lookup
|
117
117
|
coerce(:Integer, Class) # => Integer
|
118
118
|
coerce(:Enumerable, Module) # => Enumerable
|
@@ -120,10 +120,10 @@ The specific implemented rules are
|
|
120
120
|
### No core extension? no problem!
|
121
121
|
|
122
122
|
require 'myrrha/coerce'
|
123
|
-
|
123
|
+
|
124
124
|
Myrrha.coerce("12", Integer) # => 12
|
125
125
|
Myrrha.coerce("12.0", Float) # => 12.0
|
126
|
-
|
126
|
+
|
127
127
|
Myrrha.coerce("true", Myrrha::Boolean) # => true
|
128
128
|
# [... and so on ...]
|
129
129
|
|
@@ -140,12 +140,12 @@ method on you class; it will be used in priority.
|
|
140
140
|
Foo.new(arg)
|
141
141
|
end
|
142
142
|
end
|
143
|
-
|
144
|
-
Myrrha.coerce(:hello, Foo)
|
143
|
+
|
144
|
+
Myrrha.coerce(:hello, Foo)
|
145
145
|
# => #<Foo:0x869eee0 @arg=:hello>
|
146
146
|
|
147
|
-
If <code>Foo</code> is not your code and you don't want to
|
148
|
-
by adding a <code>coerce</code> class method, you can simply add new rules to
|
147
|
+
If <code>Foo</code> is not your code and you don't want to monkey patch the class
|
148
|
+
by adding a <code>coerce</code> class method, you can simply add new rules to
|
149
149
|
Myrrha itself:
|
150
150
|
|
151
151
|
Myrrha::Coerce.append do |r|
|
@@ -153,47 +153,47 @@ Myrrha itself:
|
|
153
153
|
Foo.new(value)
|
154
154
|
end
|
155
155
|
end
|
156
|
-
|
157
|
-
Myrrha.coerce(:hello, Foo)
|
156
|
+
|
157
|
+
Myrrha.coerce(:hello, Foo)
|
158
158
|
# => #<Foo:0x8866f84 @arg=:hello>
|
159
159
|
|
160
|
-
Now, doing so, the new coercion rule will be shared with all Myrrha users, which
|
160
|
+
Now, doing so, the new coercion rule will be shared with all Myrrha users, which
|
161
161
|
might be intrusive. Why not using your own set of coercion rules?
|
162
162
|
|
163
163
|
MyRules = Myrrha::Coerce.dup.append do |r|
|
164
164
|
r.coercion(Symbol, Foo) do |value, _|
|
165
165
|
Foo.new(value)
|
166
166
|
end
|
167
|
-
end
|
168
|
-
|
167
|
+
end
|
168
|
+
|
169
169
|
# Myrrha.coerce is actually a shortcut for:
|
170
170
|
Myrrha::Coerce.apply(:hello, Foo)
|
171
171
|
# => Myrrha::Error: Unable to coerce `hello` to Foo
|
172
|
-
|
173
|
-
MyRules.apply(:hello, Foo)
|
172
|
+
|
173
|
+
MyRules.apply(:hello, Foo)
|
174
174
|
# => #<Foo:0x8b7d254 @arg=:hello>
|
175
175
|
|
176
176
|
## The <code>to\_ruby\_literal()</code> feature
|
177
177
|
|
178
|
-
Myrrha.to_ruby_literal([:anything])
|
178
|
+
Myrrha.to_ruby_literal([:anything])
|
179
179
|
[:anything].to_ruby_literal # with core extensions
|
180
180
|
|
181
181
|
### What for?
|
182
182
|
|
183
|
-
<code>Object#to\_ruby\_literal</code> has a very simple specification. Given an
|
184
|
-
object o that can be considered as a true _value_, the result of
|
185
|
-
<code>o.to\_ruby\_literal</code> must be such that the following invariant
|
183
|
+
<code>Object#to\_ruby\_literal</code> has a very simple specification. Given an
|
184
|
+
object o that can be considered as a true _value_, the result of
|
185
|
+
<code>o.to\_ruby\_literal</code> must be such that the following invariant
|
186
186
|
holds:
|
187
187
|
|
188
|
-
Kernel.eval(o.to_ruby_literal) == o
|
188
|
+
Kernel.eval(o.to_ruby_literal) == o
|
189
189
|
|
190
|
-
That is, parsing & evaluating the literal yields the same value. When generating
|
191
|
-
(human-readable) ruby code, having a unique entry point that respects the
|
192
|
-
specification is very useful.
|
190
|
+
That is, parsing & evaluating the literal yields the same value. When generating
|
191
|
+
(human-readable) ruby code, having a unique entry point that respects the
|
192
|
+
specification is very useful.
|
193
193
|
|
194
|
-
For almost all ruby classes, but not all, using o.inspect respects the
|
194
|
+
For almost all ruby classes, but not all, using o.inspect respects the
|
195
195
|
invariant. For example, the following is true:
|
196
|
-
|
196
|
+
|
197
197
|
Kernel.eval("hello".inspect) == "hello" # => true
|
198
198
|
Kernel.eval([1, 2, 3].inspect) == [1, 2, 3] # => true
|
199
199
|
Kernel.eval({:key => :value}.inspect) == {:key => :value} # => true
|
@@ -202,19 +202,19 @@ invariant. For example, the following is true:
|
|
202
202
|
Unfortunately, this is not always the case:
|
203
203
|
|
204
204
|
Kernel.eval(Date.today.inspect) == Date.today
|
205
|
-
# => false
|
205
|
+
# => false
|
206
206
|
# => because Date.today.inspect yields "#<Date: 2011-07-20 ...", which is a comment
|
207
207
|
|
208
208
|
### Example
|
209
209
|
|
210
|
-
Myrrha implements a very simple set of rules for implementing
|
210
|
+
Myrrha implements a very simple set of rules for implementing
|
211
211
|
<code>Object#to\_ruby\_literal</code> that works:
|
212
212
|
|
213
213
|
require 'date'
|
214
214
|
require 'myrrha/with_core_ext'
|
215
215
|
require 'myrrha/to_ruby_literal'
|
216
|
-
|
217
|
-
1.to_ruby_literal # => "1"
|
216
|
+
|
217
|
+
1.to_ruby_literal # => "1"
|
218
218
|
Date.today.to_ruby_literal # => "Marshal.load('...')"
|
219
219
|
["hello", Date.today].to_ruby_literal # => "['hello', Marshal.load('...')]"
|
220
220
|
|
@@ -222,7 +222,7 @@ Myrrha implements a best-effort strategy to return a human readable string. It
|
|
222
222
|
simply fallbacks to <code>Marshal.load(...)</code> when the strategy fails:
|
223
223
|
|
224
224
|
(1..10).to_ruby_literal # => "1..10"
|
225
|
-
|
225
|
+
|
226
226
|
today = Date.today
|
227
227
|
(today..today+1).to_ruby_literal # => "Marshal.load('...')"
|
228
228
|
|
@@ -230,7 +230,7 @@ simply fallbacks to <code>Marshal.load(...)</code> when the strategy fails:
|
|
230
230
|
|
231
231
|
require 'date'
|
232
232
|
require 'myrrha/to_ruby_literal'
|
233
|
-
|
233
|
+
|
234
234
|
Myrrha.to_ruby_literal(1) # => 1
|
235
235
|
Myrrha.to_ruby_literal(Date.today) # => Marshal.load("...")
|
236
236
|
# [... and so on ...]
|
@@ -249,9 +249,9 @@ class
|
|
249
249
|
"Foo.new(#{arg.inspect})"
|
250
250
|
end
|
251
251
|
end
|
252
|
-
|
252
|
+
|
253
253
|
Myrrha.to_ruby_literal(Foo.new(:hello))
|
254
|
-
# => "Foo.new(:hello)"
|
254
|
+
# => "Foo.new(:hello)"
|
255
255
|
|
256
256
|
As with coerce, contributing your own rule to Myrrha is possible:
|
257
257
|
|
@@ -262,8 +262,8 @@ As with coerce, contributing your own rule to Myrrha is possible:
|
|
262
262
|
end
|
263
263
|
|
264
264
|
Myrrha.to_ruby_literal(Foo.new(:hello))
|
265
|
-
# => "Foo.new(:hello)"
|
266
|
-
|
265
|
+
# => "Foo.new(:hello)"
|
266
|
+
|
267
267
|
And building your own set of rules is possible as well:
|
268
268
|
|
269
269
|
MyRules = Myrrha::ToRubyLiteral.dup.append do |r|
|
@@ -275,60 +275,60 @@ And building your own set of rules is possible as well:
|
|
275
275
|
# Myrrha.to_ruby_literal is actually a shortcut for:
|
276
276
|
Myrrha::ToRubyLiteral.apply(Foo.new(:hello))
|
277
277
|
# => "Marshal.load('...')"
|
278
|
-
|
278
|
+
|
279
279
|
MyRules.apply(Foo.new(:hello))
|
280
|
-
# => "Foo.new(:hello)"
|
281
|
-
|
280
|
+
# => "Foo.new(:hello)"
|
281
|
+
|
282
282
|
### Limitation
|
283
283
|
|
284
284
|
As the feature fallbacks to marshaling, everything which is marshalable will
|
285
|
-
work. As usual, <code>to\_ruby\_literal(Proc)</code> won't work.
|
285
|
+
work. As usual, <code>to\_ruby\_literal(Proc)</code> won't work.
|
286
286
|
|
287
287
|
## The general coercion framework
|
288
288
|
|
289
289
|
A set of coercion rules can simply be created from scratch as follows:
|
290
290
|
|
291
291
|
Rules = Myrrha.coercions do |r|
|
292
|
-
|
292
|
+
|
293
293
|
# `upon` rules are tried in priority if PRE holds
|
294
294
|
r.upon(SourceDomain) do |value, requested_domain|
|
295
|
-
|
295
|
+
|
296
296
|
# PRE: - user wants to coerce `value` to a requested_domain
|
297
297
|
# - belongs_to?(value, SourceDomain)
|
298
|
-
|
298
|
+
|
299
299
|
# implement the coercion or throw(:newrule)
|
300
300
|
returned_value = something(value)
|
301
|
-
|
301
|
+
|
302
302
|
# POST: belongs_to?(returned_value, requested_domain)
|
303
|
-
|
303
|
+
|
304
304
|
end
|
305
|
-
|
305
|
+
|
306
306
|
# `coercion` rules are then tried in order if PRE holds
|
307
307
|
r.coercion(SourceDomain, TargetDomain) do |value, requested_domain|
|
308
|
-
|
308
|
+
|
309
309
|
# PRE: - user wants to coerce `value` to a requested_domain
|
310
310
|
# - belongs_to?(value, SourceDomain)
|
311
311
|
# - subdomain?(TargetDomain, requested_domain)
|
312
|
-
|
312
|
+
|
313
313
|
# implement the coercion or throw(:newrule)
|
314
|
-
returned_value = something(value)
|
315
|
-
|
314
|
+
returned_value = something(value)
|
315
|
+
|
316
316
|
# POST: returned_value belongs to requested_domain
|
317
|
-
|
317
|
+
|
318
318
|
end
|
319
|
-
|
319
|
+
|
320
320
|
# fallback rules are tried if everything else has failed
|
321
321
|
r.fallback(SourceDomain) do |value, requested_domain|
|
322
|
-
|
322
|
+
|
323
323
|
# exactly the same as upon rules
|
324
|
-
|
324
|
+
|
325
325
|
end
|
326
|
-
|
326
|
+
|
327
327
|
end
|
328
|
-
|
329
|
-
When the user invokes <code>Rules.apply(value, domain)</code> all rules for
|
330
|
-
which PRE holds are executed in order, until one succeed (chain of
|
331
|
-
responsibility design pattern). This means that coercions always execute in
|
328
|
+
|
329
|
+
When the user invokes <code>Rules.apply(value, domain)</code> all rules for
|
330
|
+
which PRE holds are executed in order, until one succeed (chain of
|
331
|
+
responsibility design pattern). This means that coercions always execute in
|
332
332
|
<code>O(number of rules)</code>.
|
333
333
|
|
334
334
|
### Specifying converters
|
@@ -343,7 +343,7 @@ which is passed the source value and requested target domain.
|
|
343
343
|
}
|
344
344
|
end
|
345
345
|
convert("12", Integer)
|
346
|
-
|
346
|
+
|
347
347
|
A converter may also be specified as an array of domains. In this case, it is
|
348
348
|
assumed that they for a path inside the convertion graph. Consider for example
|
349
349
|
the following coercion rules (contrived example)
|
@@ -354,42 +354,42 @@ the following coercion rules (contrived example)
|
|
354
354
|
r.coercion Integer, Float, lambda{|s,t| Float(s) } # 3
|
355
355
|
r.coercion Integer, Symbol, [Float, String] # 4
|
356
356
|
end
|
357
|
-
|
358
|
-
The last rule specifies a convertion path, through intermediate domains. The
|
357
|
+
|
358
|
+
The last rule specifies a convertion path, through intermediate domains. The
|
359
359
|
complete rule specifies that applying the following path will work
|
360
360
|
|
361
361
|
Integer -> Float -> String -> Symbol
|
362
362
|
#3 #2 #1
|
363
|
-
|
363
|
+
|
364
364
|
Indeed,
|
365
365
|
|
366
|
-
rules.coerce(12, Symbol) # => :"12.0"
|
367
|
-
|
368
|
-
### Semantics of <code>belongs\_to?</code> and <code>subdomain?</code>
|
366
|
+
rules.coerce(12, Symbol) # => :"12.0"
|
369
367
|
|
370
|
-
|
368
|
+
### Semantics of <code>belongs\_to?</code> and <code>subdomain?</code>
|
369
|
+
|
370
|
+
The pseudo-code given above relies on two main abstractions. Suppose the user
|
371
371
|
makes a call to <code>coerce(value, requested_domain)</code>:
|
372
372
|
|
373
373
|
* <code>belongs\_to?(value, SourceDomain)</code> is true iif
|
374
|
-
* <code>SourceDomain</code> is a <code>Proc</code> of arity 2, and
|
374
|
+
* <code>SourceDomain</code> is a <code>Proc</code> of arity 2, and
|
375
375
|
<code>SourceDomain.call(value, requested_domain)</code> yields true
|
376
|
-
* <code>SourceDomain</code> is a <code>Proc</code> of arity 1, and
|
376
|
+
* <code>SourceDomain</code> is a <code>Proc</code> of arity 1, and
|
377
377
|
<code>SourceDomain.call(value)</code> yields true
|
378
378
|
* <code>SourceDomain === value</code> yields true
|
379
379
|
|
380
380
|
* <code>subdomain?(SourceDomain,TargetDomain)</code> is true iif
|
381
381
|
* <code>SourceDomain == TargetDomain</code> yields true
|
382
|
-
* TargetDomain respond to <code>:superdomain_of?</code> and answers true on
|
383
|
-
SourceDomain
|
384
|
-
* SourceDomain and TargetDomain are both classes and the latter is a super
|
385
|
-
class of the former
|
386
|
-
|
382
|
+
* TargetDomain respond to <code>:superdomain_of?</code> and answers true on
|
383
|
+
SourceDomain
|
384
|
+
* SourceDomain and TargetDomain are both classes and the latter is a super
|
385
|
+
class of the former
|
386
|
+
|
387
387
|
### Advanced rule examples
|
388
388
|
|
389
389
|
Rules = Myrrha.coercions do |r|
|
390
|
-
|
390
|
+
|
391
391
|
# A 'catch-all' upon rule, always fired
|
392
|
-
catch_all = lambda{|v,rd| true}
|
392
|
+
catch_all = lambda{|v,rd| true}
|
393
393
|
r.upon(catch_all) do |value, requested_domain|
|
394
394
|
if you_can_coerce?(value)
|
395
395
|
# then do it!
|
@@ -397,19 +397,19 @@ makes a call to <code>coerce(value, requested_domain)</code>:
|
|
397
397
|
throw(:next_rule)
|
398
398
|
end
|
399
399
|
end
|
400
|
-
|
400
|
+
|
401
401
|
# Delegate every call to the requested domain if it responds to compile
|
402
|
-
compilable = lambda{|v,rd| rd.respond_to?(:compile)}
|
402
|
+
compilable = lambda{|v,rd| rd.respond_to?(:compile)}
|
403
403
|
r.upon(compilable) do |value, requested_domain|
|
404
404
|
requested_domain.compile(value)
|
405
|
-
end
|
406
|
-
|
405
|
+
end
|
406
|
+
|
407
407
|
# A fallback strategy if everything else fails
|
408
408
|
r.fallback(Object) do |value, requested_domain|
|
409
409
|
# always fired after everything else
|
410
410
|
# this is your last change, an Myrrha::Error will be raised if you fail
|
411
411
|
end
|
412
|
-
|
412
|
+
|
413
413
|
end
|
414
414
|
|
415
415
|
### Factoring domains through specialization by constraint
|
@@ -422,7 +422,7 @@ rules hold:
|
|
422
422
|
* A sub-type can therefore be specified through a predicate on the super domain
|
423
423
|
|
424
424
|
For example, "positive integers" is a sub type of "integers" where the predicate
|
425
|
-
is "value > 0".
|
425
|
+
is "value > 0".
|
426
426
|
|
427
427
|
Myrrha comes with a small feature allowing you to create types 'ala' SByC:
|
428
428
|
|
@@ -435,39 +435,35 @@ Myrrha comes with a small feature allowing you to create types 'ala' SByC:
|
|
435
435
|
PosInt === -1 # => false
|
436
436
|
PosInt.new(10) # => 10
|
437
437
|
PosInt.new(-10) # => ArgumentError, "Invalid value -10 for PosInt"
|
438
|
-
|
438
|
+
|
439
439
|
Note that the feature is very limited, and is not intended to provide a truly
|
440
440
|
coherent typing framework. For example:
|
441
441
|
|
442
442
|
10.is_a?(PosInt) # => false
|
443
|
-
10.kind_of?(PosInt) # => false
|
444
|
-
|
445
|
-
Instead, Myrrha domains are only provided as an helper to build sound coercions
|
446
|
-
rules easily while 1) keeping a Class-based approach to source and target
|
447
|
-
domains and 2) having friendly error messages 3) really supporting true
|
443
|
+
10.kind_of?(PosInt) # => false
|
444
|
+
|
445
|
+
Instead, Myrrha domains are only provided as an helper to build sound coercions
|
446
|
+
rules easily while 1) keeping a Class-based approach to source and target
|
447
|
+
domains and 2) having friendly error messages 3) really supporting true
|
448
448
|
reasoning on types and value:
|
449
449
|
|
450
450
|
# Only a rule that converts String to Integer
|
451
451
|
rules = Myrrha.coercions do |r|
|
452
|
-
r.coercion String, Integer, lambda{|s,t| Integer(s)}
|
453
|
-
end
|
454
|
-
|
452
|
+
r.coercion String, Integer, lambda{|s,t| Integer(s)}
|
453
|
+
end
|
454
|
+
|
455
455
|
# it succeeds on both integers and positive integers
|
456
456
|
rules.coerce("12", Integer) # => 12
|
457
457
|
rules.coerce("12", PosInt) # => 12
|
458
|
-
|
458
|
+
|
459
459
|
# and correctly fails in each case!
|
460
460
|
rules.coerce("-12", Integer) # => -12
|
461
461
|
rules.coerce("-12", PosInt) # => ArgumentError, "Invalid value -12 for PosInt"
|
462
462
|
|
463
|
-
Note that if you want to provide additional tooling to your factored domain,
|
463
|
+
Note that if you want to provide additional tooling to your factored domain,
|
464
464
|
the following way of creating them also works:
|
465
465
|
|
466
466
|
class PosInt < Integer
|
467
|
-
extend Myrrha::Domain
|
468
|
-
|
469
|
-
def self.predicate
|
470
|
-
@predicate ||= lambda{|i| i > 0}
|
471
|
-
end
|
472
|
-
|
467
|
+
extend Myrrha::Domain.new(Integer, [], lambda{|i| i > 0})
|
468
|
+
|
473
469
|
end
|