adt 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/adt.rb +41 -54
- data/lib/data/maybe.rb +4 -0
- data/lib/data/validation.rb +21 -0
- metadata +26 -40
data/lib/adt.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'adt/case_recorder'
|
2
2
|
|
3
|
-
module
|
3
|
+
module AdtUtils
|
4
4
|
def self.underscore(camel_cased_word)
|
5
5
|
word = camel_cased_word.to_s.dup
|
6
6
|
word.gsub!(/::/, '/')
|
@@ -12,6 +12,16 @@ module StringHelp
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
def ADT(&block)
|
16
|
+
m = Module.new
|
17
|
+
m.class.send(:public, :define_method)
|
18
|
+
(class <<m; self end).define_method(:extended) do |base|
|
19
|
+
base.extend(ADT)
|
20
|
+
base.send(:cases, &block)
|
21
|
+
end
|
22
|
+
m
|
23
|
+
end
|
24
|
+
|
15
25
|
module ADT
|
16
26
|
module_function
|
17
27
|
|
@@ -81,68 +91,64 @@ module ADT
|
|
81
91
|
cases = dsl._church_cases
|
82
92
|
num_cases = cases.length
|
83
93
|
case_names = cases.map { |x| x[0] }
|
84
|
-
is_enumeration = cases.all?{ |(_, args)| args.count == 0 }
|
85
|
-
|
86
|
-
# creates procs with a certain arg count. body should use #{prefix}N to access arguments. The result should be
|
87
|
-
# eval'ed at the call site
|
88
|
-
proc_create = proc { |argc, prefix, body|
|
89
|
-
args = argc > 0 ? "|#{(1..argc).to_a.map { |a| "#{prefix}#{a}" }.join(',')}|" : ""
|
90
|
-
"proc { #{args} #{body} }"
|
91
|
-
}
|
94
|
+
is_enumeration = cases.all? { |(_, args)| args.count == 0 }
|
92
95
|
|
93
96
|
# Initializer. Should not be used directly.
|
94
|
-
define_method(:initialize) do
|
95
|
-
@
|
97
|
+
define_method(:initialize) do |tag, tag_index, values|
|
98
|
+
@tag = tag
|
99
|
+
@tag_index = tag_index
|
100
|
+
@values = values
|
96
101
|
end
|
97
102
|
|
98
103
|
# The Fold.
|
99
104
|
define_method(:fold) do |*args|
|
100
105
|
if args.first && args.first.is_a?(Hash) then
|
101
|
-
|
106
|
+
args.first[@tag].call(*@values)
|
102
107
|
else
|
103
|
-
@
|
108
|
+
args[@tag_index].call(*@values)
|
104
109
|
end
|
105
110
|
end
|
106
111
|
|
107
112
|
# If we're inside a named class, then set up an alias to fold
|
108
|
-
fold_synonym =
|
109
|
-
if fold_synonym && fold_synonym.
|
110
|
-
|
113
|
+
fold_synonym = name && AdtUtils.underscore(name.split('::').last)
|
114
|
+
if fold_synonym && !fold_synonym.empty? then
|
115
|
+
alias_method(fold_synonym, :fold)
|
111
116
|
end
|
112
117
|
|
113
118
|
# The Constructors
|
114
119
|
cases.each_with_index do |(name, case_args), index|
|
115
|
-
constructor =
|
120
|
+
constructor = lambda { |*args| self.new(name, index, args) }
|
116
121
|
if case_args.size > 0 then
|
117
122
|
singleton_class.send(:define_method, name, &constructor)
|
118
123
|
else
|
119
124
|
# Cache the constructed value if it is unary
|
120
125
|
singleton_class.send(:define_method, name) do
|
121
126
|
instance_variable_get("@#{name}") || begin
|
122
|
-
instance_variable_set("@#{name}",
|
127
|
+
instance_variable_set("@#{name}", self.new(name, index, []))
|
123
128
|
end
|
124
129
|
end
|
125
130
|
end
|
126
131
|
end
|
127
132
|
|
133
|
+
# Case info
|
134
|
+
singleton_class.send(:define_method, :case_info) { cases }
|
135
|
+
|
128
136
|
# Getter methods for common accessors
|
129
137
|
all_arg_names = cases.map { |(_, args)| args }.flatten
|
130
138
|
all_arg_names.each do |arg|
|
131
|
-
case_positions = cases.map { |(
|
139
|
+
case_positions = cases.map { |(n, args)| args.index(arg) }
|
132
140
|
if case_positions.all?
|
133
|
-
define_method(arg)
|
134
|
-
|
135
|
-
|
136
|
-
})
|
137
|
-
end
|
141
|
+
define_method(arg) {
|
142
|
+
@values[case_positions[@tag_index]]
|
143
|
+
}
|
138
144
|
end
|
139
145
|
end
|
140
146
|
|
141
147
|
# Case info methods
|
142
148
|
# Indexing is 1-based
|
143
|
-
define_method(:case_index)
|
144
|
-
define_method(:case_name)
|
145
|
-
define_method(:case_arity)
|
149
|
+
define_method(:case_index) { @tag_index + 1 }
|
150
|
+
define_method(:case_name) { @tag.to_s }
|
151
|
+
define_method(:case_arity) { self.class.case_info[@tag_index][1].count }
|
146
152
|
|
147
153
|
# Enumerations are defined as classes with cases that don't take arguments. A number of useful
|
148
154
|
# functions can be defined for these.
|
@@ -151,31 +157,22 @@ module ADT
|
|
151
157
|
@all_values ||= case_names.map { |x| send(x) }
|
152
158
|
end
|
153
159
|
|
154
|
-
|
160
|
+
alias_method(:to_i, :case_index)
|
155
161
|
singleton_class.send(:define_method, :from_i) do |idx| send(case_names[idx - 1]) end
|
162
|
+
#TODO succ, pred
|
156
163
|
end
|
157
164
|
|
158
165
|
# The usual object helpers
|
159
166
|
define_method(:inspect) do
|
160
|
-
|
161
|
-
|
162
|
-
bit = case_args.map { |ca|
|
163
|
-
index += 1
|
164
|
-
" #{ca}:#\{a#{index}\.inspect}"
|
165
|
-
}.join('')
|
166
|
-
eval(proc_create[case_args.count, "a", " \" #{cn}#{bit}\""])
|
167
|
-
}) + ">"
|
167
|
+
args = self.class.case_info[@tag_index][1]
|
168
|
+
"#<#{self.class.name} #{@tag}#{args.zip(@values).map { |(x,y)| " #{x}:#{y.inspect}" }.join("")}>"
|
168
169
|
end
|
169
170
|
|
170
171
|
define_method(:==) do |other|
|
171
172
|
!other.nil? && case_index == other.case_index && to_a == other.to_a
|
172
173
|
end
|
173
174
|
|
174
|
-
define_method(:to_a)
|
175
|
-
fold(*cases.map { |(cn, args)|
|
176
|
-
eval(proc_create[args.count, "a", "[" + (1..args.count).to_a.map { |idx| "a#{idx}" }.join(',') + "]"])
|
177
|
-
})
|
178
|
-
end
|
175
|
+
define_method(:to_a) { @values }
|
179
176
|
|
180
177
|
# Comparisons are done by index, then by the values within the case (if any) via #to_a
|
181
178
|
define_method(:<=>) do |other|
|
@@ -190,22 +187,12 @@ module ADT
|
|
190
187
|
cases.each_with_index do |(name, args), idx|
|
191
188
|
# Thing.foo(5).foo? # <= true
|
192
189
|
# Thing.foo(5).bar? # <= false
|
193
|
-
define_method("#{name}?")
|
194
|
-
fold(*case_names.map { |cn|
|
195
|
-
eval(proc_create[0, "a", cn == name ? "true" : "false"])
|
196
|
-
})
|
197
|
-
end
|
190
|
+
define_method("#{name}?") { @tag == name }
|
198
191
|
|
199
192
|
# Thing.foo(5).when_foo(proc {|v| v }, proc { 0 }) # <= 5
|
200
193
|
# Thing.bar(5).when_foo(proc {|v| v }, proc { 0 }) # <= 0
|
201
194
|
define_method("when_#{name}") do |handle, default|
|
202
|
-
|
203
|
-
if (cn == name)
|
204
|
-
proc { |*args| handle.call(*args) }
|
205
|
-
else
|
206
|
-
default
|
207
|
-
end
|
208
|
-
})
|
195
|
+
@tag == name ? handle.call(*@values) : default.call
|
209
196
|
end
|
210
197
|
end
|
211
198
|
end
|
@@ -249,7 +236,7 @@ module ADT
|
|
249
236
|
# instance_exec it back on the instance.
|
250
237
|
# TODO: use the proc builder like in the `cases` method, which will let us tie
|
251
238
|
# down the arity
|
252
|
-
some_impl =
|
239
|
+
some_impl = lambda { |*args| the_instance.instance_exec(*args, &impl) }
|
253
240
|
memo[c] = some_impl
|
254
241
|
memo
|
255
242
|
})
|
data/lib/data/maybe.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'adt'
|
2
|
+
|
3
|
+
class Validation
|
4
|
+
extend ADT
|
5
|
+
cases do
|
6
|
+
failure(:errors)
|
7
|
+
success(:value)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.fail_with(error)
|
11
|
+
failure([error])
|
12
|
+
end
|
13
|
+
|
14
|
+
def map
|
15
|
+
fold(proc { |_| self }, proc { |v| Validation.success(yield v) })
|
16
|
+
end
|
17
|
+
|
18
|
+
def bind(&b)
|
19
|
+
fold(proc { |_| self }, b)
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,66 +1,52 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: adt
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 6
|
9
|
-
version: 0.0.6
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.7
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Nick Partridge
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
date: 2011-09-06 00:00:00 +10:00
|
18
|
-
default_executable:
|
12
|
+
date: 2012-07-10 00:00:00.000000000 Z
|
19
13
|
dependencies: []
|
20
|
-
|
21
|
-
|
14
|
+
description: Define multiple constructors for a type, then match on them! Get all
|
15
|
+
catamorphic.
|
22
16
|
email: nkpart@gmail.com
|
23
17
|
executables: []
|
24
|
-
|
25
18
|
extensions: []
|
26
|
-
|
27
19
|
extra_rdoc_files: []
|
28
|
-
|
29
|
-
files:
|
20
|
+
files:
|
30
21
|
- lib/adt/case_recorder.rb
|
31
22
|
- lib/adt.rb
|
32
23
|
- lib/data/maybe.rb
|
24
|
+
- lib/data/validation.rb
|
33
25
|
- CHANGELOG.md
|
34
26
|
- README.md
|
35
|
-
|
36
|
-
homepage: ""
|
27
|
+
homepage: ''
|
37
28
|
licenses: []
|
38
|
-
|
39
29
|
post_install_message:
|
40
30
|
rdoc_options: []
|
41
|
-
|
42
|
-
require_paths:
|
31
|
+
require_paths:
|
43
32
|
- lib
|
44
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
requirements:
|
53
|
-
- -
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
|
56
|
-
- 0
|
57
|
-
version: "0"
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
58
45
|
requirements: []
|
59
|
-
|
60
46
|
rubyforge_project:
|
61
|
-
rubygems_version: 1.
|
47
|
+
rubygems_version: 1.8.11
|
62
48
|
signing_key:
|
63
49
|
specification_version: 3
|
64
50
|
summary: Algebraic data type DSL
|
65
51
|
test_files: []
|
66
|
-
|
52
|
+
has_rdoc:
|