adt 0.0.1
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.
- data/README.md +59 -0
- data/lib/adt.rb +155 -0
- data/lib/adt/case_recorder.rb +21 -0
- metadata +58 -0
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
ADT
|
2
|
+
===
|
3
|
+
|
4
|
+
A library for declaring algebraic data types in Ruby.
|
5
|
+
|
6
|
+
Usage
|
7
|
+
-----
|
8
|
+
|
9
|
+
gem install adt
|
10
|
+
|
11
|
+
Now:
|
12
|
+
|
13
|
+
require 'adt'
|
14
|
+
class ValidatedValue
|
15
|
+
extend ADT
|
16
|
+
cases do
|
17
|
+
ok(:value)
|
18
|
+
missing
|
19
|
+
invalid(:reason)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Later...
|
24
|
+
|
25
|
+
# Create values
|
26
|
+
mine = ValidatedValue.ok(5)
|
27
|
+
missing = ValidatedValue.missing
|
28
|
+
invalid = ValidatedValue.invalid("Wrong number!")
|
29
|
+
|
30
|
+
# Define operations on a value, only the proc matching the value's case will be
|
31
|
+
# executed
|
32
|
+
mine.fold(
|
33
|
+
proc { |value| value },
|
34
|
+
proc { "missing default" }
|
35
|
+
proc { |reason| raise "gah. Invalid is terrible" }
|
36
|
+
)
|
37
|
+
|
38
|
+
mine.ok? # <= true
|
39
|
+
mine.when_missing(proc { "I'm missing!" }, proc { "It's okay I'm around" })
|
40
|
+
|
41
|
+
mine.fold(
|
42
|
+
:ok => proc { |value| value },
|
43
|
+
:missing => proc { "missing default " },
|
44
|
+
:invalid => proc { |reason| raise "gah. Invalid is terrible!" }
|
45
|
+
)
|
46
|
+
|
47
|
+
# == does what you expect.
|
48
|
+
mine == ValidatedValue.missing # <= false
|
49
|
+
mine == ValidatedValue.ok(5) # <= true
|
50
|
+
|
51
|
+
# Inspect looks good.
|
52
|
+
mine.inspect # <= "#<ValidatedValue ok value:5>"
|
53
|
+
|
54
|
+
More Information on ADTs
|
55
|
+
------------------------
|
56
|
+
|
57
|
+
* http://blog.tmorris.net/algebraic-data-types-again/
|
58
|
+
* http://en.wikibooks.org/wiki/Haskell/Type_declarations#data_and_constructor_functions
|
59
|
+
* http://en.wikipedia.org/wiki/Algebraic_data_type
|
data/lib/adt.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'adt/case_recorder'
|
2
|
+
|
3
|
+
module ADT
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Configures the class to be an ADT. Cases are defined by calling
|
7
|
+
# methods named for the case, and providing symbol arguments for
|
8
|
+
# the parameters to the case.
|
9
|
+
#
|
10
|
+
# eg.
|
11
|
+
#
|
12
|
+
# class Validation
|
13
|
+
# extend ADT
|
14
|
+
# cases do
|
15
|
+
# success(:values)
|
16
|
+
# failure(:errors, :position)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# This will provde 2 core pieces of functionality.
|
21
|
+
#
|
22
|
+
# 1. Constructors, as class methods, named the same as the case, and expecting
|
23
|
+
# parameters as per the symbol arguments provided in the `cases` block.
|
24
|
+
# @failure = Validation.failure(["error1"], 5)
|
25
|
+
# @success = Validation.success([1,2])
|
26
|
+
# 2. #fold. This method takes a proc for every case. If the case has parameters, those
|
27
|
+
# will be passed to the proc. The proc matching the particular value of the case will
|
28
|
+
# be called. Using this method, every instance method for the ADT can be defined.
|
29
|
+
# @failure.fold(
|
30
|
+
# proc { |values| "We are a success! #{values} "},
|
31
|
+
# proc { |errors, position| "Failed :(, at position #{position}" }
|
32
|
+
# )
|
33
|
+
# It can also be passed a hash of procs, keyed by case name:
|
34
|
+
# @failure.fold(
|
35
|
+
# :success => proc { |values| values },
|
36
|
+
# :failures => proc { |errors, position| [] }
|
37
|
+
# )
|
38
|
+
#
|
39
|
+
# In addition, a number of helper methods are defined:
|
40
|
+
#
|
41
|
+
# * Standard object methods: #==, #inspect
|
42
|
+
# * Case checking predicates:
|
43
|
+
# some_validation.success?
|
44
|
+
# some_validation.failure?
|
45
|
+
# * Functions for handling specific cases:
|
46
|
+
# some_validation.when_success(proc { |values| values }, proc { [] })
|
47
|
+
#
|
48
|
+
# @param [Proc] &definitions block which defines the constructors. This will be evaluated using
|
49
|
+
# #instance_eval to record the cases.
|
50
|
+
#
|
51
|
+
def cases(&definitions)
|
52
|
+
dsl = CaseRecorder.new
|
53
|
+
dsl.__instance_eval(&definitions)
|
54
|
+
|
55
|
+
cases = dsl._church_cases
|
56
|
+
num_cases = dsl._church_cases.length
|
57
|
+
case_names = dsl._church_cases.map { |x| x[0] }
|
58
|
+
|
59
|
+
# creates procs with a certain arg count. body should use aN to access arguments. The result should be
|
60
|
+
# evalled at the call site
|
61
|
+
proc_create = proc { |argc, prefix, body|
|
62
|
+
args = argc > 0 ? "|#{(1..argc).to_a.map { |a| "#{prefix}#{a}" }.join(',')}|" : ""
|
63
|
+
"proc { #{args} #{body} }"
|
64
|
+
}
|
65
|
+
|
66
|
+
# Initializer. Should not be used directly.
|
67
|
+
define_method(:initialize) do |&fold|
|
68
|
+
@fold = fold
|
69
|
+
end
|
70
|
+
|
71
|
+
# The Fold.
|
72
|
+
define_method(:fold) do |*args|
|
73
|
+
if args.first && args.first.is_a?(Hash) then
|
74
|
+
@fold.call(*case_names.map { |cn| args.first.fetch(cn) })
|
75
|
+
else
|
76
|
+
@fold.call(*args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# The Constructors
|
81
|
+
dsl._church_cases.each_with_index do |(name, case_args), index|
|
82
|
+
self.class.send(:define_method, name) do |*args|
|
83
|
+
the_proc = eval(proc_create[num_cases, "a", "a#{index+1}.call(*args)"])
|
84
|
+
self.new(&the_proc)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# The usual object helpers
|
89
|
+
define_method(:inspect) do
|
90
|
+
"#<" + self.class.name + fold(*dsl._church_cases.map { |(cn, case_args)|
|
91
|
+
index = 0
|
92
|
+
bit = case_args.map { |ca|
|
93
|
+
index += 1
|
94
|
+
" #{ca}:#\{a#{index}\}"
|
95
|
+
}.join('')
|
96
|
+
eval(proc_create[case_args.count, "a", " \" #{cn}#{bit}\""])
|
97
|
+
}) + ">"
|
98
|
+
end
|
99
|
+
|
100
|
+
define_method(:==) do |other|
|
101
|
+
!other.nil? && begin
|
102
|
+
fold(*cases.map { |(cn, args)|
|
103
|
+
inner_check = proc_create[args.count, "o", (1..(args.count)).to_a.map { |idx| "s#{idx} == o#{idx}" }.<<("true").join(' && ')]
|
104
|
+
eval(proc_create[args.count, "s", "other.when_#{cn}(#{inner_check}, proc { false })"])
|
105
|
+
})
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Case specific methods
|
110
|
+
# eg.
|
111
|
+
# cases do foo(:a); bar(:b); end
|
112
|
+
cases.each_with_index do |(name, args), idx|
|
113
|
+
# Thing.foo(5).foo? # <= true
|
114
|
+
# Thing.foo(5).bar? # <= false
|
115
|
+
define_method("#{name}?") do
|
116
|
+
fold(*case_names.map { |cn|
|
117
|
+
eval(proc_create[0, "a", cn == name ? "true" : "false"])
|
118
|
+
})
|
119
|
+
end
|
120
|
+
|
121
|
+
# Thing.foo(5).when_foo(proc {|v| v }, proc { 0 }) # <= 5
|
122
|
+
# Thing.bar(5).when_foo(proc {|v| v }, proc { 0 }) # <= 0
|
123
|
+
define_method("when_#{name}") do |handle, default|
|
124
|
+
fold(*case_names.map { |cn|
|
125
|
+
if (cn == name)
|
126
|
+
proc { |*args| handle.call(*args) }
|
127
|
+
else
|
128
|
+
default
|
129
|
+
end
|
130
|
+
})
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
module Kernel
|
137
|
+
# Returns a class configured with cases as specified in the block. See `ADT::cases` for details.
|
138
|
+
#
|
139
|
+
# Maybe = ADT do
|
140
|
+
# just(:value)
|
141
|
+
# nothing
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# v = Maybe.just(5)
|
145
|
+
#
|
146
|
+
def ADT(&blk)
|
147
|
+
c = Class.new
|
148
|
+
c.instance_eval do
|
149
|
+
extend ADT
|
150
|
+
cases(&blk)
|
151
|
+
end
|
152
|
+
c
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ADT
|
2
|
+
class CaseRecorder
|
3
|
+
alias :__instance_eval :instance_eval
|
4
|
+
|
5
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|object_id)/ }
|
6
|
+
|
7
|
+
attr_reader :_church_cases
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@_church_cases = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def define_case(sym, *args)
|
14
|
+
@_church_cases << [sym, args]
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(sym, *args)
|
18
|
+
define_case(sym, *args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nick Partridge
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-10 00:00:00 +10:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Define multiple constructors for a type, then match on them! Get all catamorphic.
|
18
|
+
email: nkpart@gmail.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- lib/adt/case_recorder.rb
|
27
|
+
- lib/adt.rb
|
28
|
+
- README.md
|
29
|
+
has_rdoc: true
|
30
|
+
homepage: ""
|
31
|
+
licenses: []
|
32
|
+
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
requirements: []
|
51
|
+
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.6.1
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Algebraic data type DSL
|
57
|
+
test_files: []
|
58
|
+
|