adt 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/lib/adt.rb +41 -54
  2. data/lib/data/maybe.rb +4 -0
  3. data/lib/data/validation.rb +21 -0
  4. metadata +26 -40
data/lib/adt.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'adt/case_recorder'
2
2
 
3
- module StringHelp
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 |&fold|
95
- @fold = fold
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
- @fold.call(*case_names.map { |cn| args.first.fetch(cn) })
106
+ args.first[@tag].call(*@values)
102
107
  else
103
- @fold.call(*args)
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 = StringHelp.underscore(name.split('::').last)
109
- if fold_synonym && fold_synonym.length > 0 then
110
- define_method(fold_synonym) do |*args| fold(*args) end
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 = proc { |*args| self.new(&eval(proc_create[num_cases, "a", "a#{index+1}.call(*args)"])) }
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}", constructor.call)
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 { |(_, args)| args.index(arg) && [args.index(arg), args.count] }
139
+ case_positions = cases.map { |(n, args)| args.index(arg) }
132
140
  if case_positions.all?
133
- define_method(arg) do
134
- fold(*case_positions.map { |(position, count)|
135
- eval(proc_create[count, "a", "a#{position+1}" ])
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) do fold(*(1..case_names.length).to_a.map { |i| proc { i } }) end
144
- define_method(:case_name) do fold(*case_names.map { |i| proc { i.to_s } }) end
145
- define_method(:case_arity) do fold(*cases.map { |(_, args)| proc { args.count } }) end
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
- define_method(:to_i) { case_index }
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
- "#<" + self.class.name + fold(*cases.map { |(cn, case_args)|
161
- index = 0
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) do
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}?") do
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
- fold(*case_names.map { |cn|
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 = proc { |*args| the_instance.instance_exec(*args, &impl) }
239
+ some_impl = lambda { |*args| the_instance.instance_exec(*args, &impl) }
253
240
  memo[c] = some_impl
254
241
  memo
255
242
  })
@@ -8,6 +8,10 @@ class Maybe
8
8
  end
9
9
 
10
10
  def map
11
+ fold(proc { self }, proc { |v| self.class.just(yield v) })
12
+ end
13
+
14
+ def bind
11
15
  fold(proc { self }, proc { |v| yield v })
12
16
  end
13
17
 
@@ -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
- prerelease: false
5
- segments:
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
- description: Define multiple constructors for a type, then match on them! Get all catamorphic.
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
- has_rdoc: true
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
- requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- segments:
49
- - 0
50
- version: "0"
51
- required_rubygems_version: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- segments:
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.3.6
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: