adt 0.0.6 → 0.0.7

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.
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: