multi_dispatch 0.1dev

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDM5YjUyM2QxODYxMTY1YWJiM2VlMTQ1M2UwYTVjNjIxYWQ3ODY3MQ==
5
+ data.tar.gz: !binary |-
6
+ OTU1OTY5YmQ5MmZiMzNmNmQ5M2M5MjMyOGUwOGY3ZTQ3ZGRjODhlMA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ Zjk5NTlhMTRlYzRlYTI0YzIxYTQxZjIxY2FlNjU0MTJiY2MwNmEzMmJiMTlh
10
+ Y2RmMTE5YjAzMGI3ODNiM2NkOTVkYjNkYWQ3MmYwOWJkZWYwNDlhYWM0Y2Vl
11
+ MGQzMDhiN2YzZmUxZjU5MTdlNDAyZTAxZjhiNDg3ZGU0YTZmOWY=
12
+ data.tar.gz: !binary |-
13
+ MzUzNWM2NjBhOGE0ZGM5YzgzNDQ2ZTJhZDE3NDE1MmQ1MjY2ZDEyZjEwYTZj
14
+ ZTFhNzY2MjIzMDE4ZTczNGMzOGI3NWJhYzFkNGVjZjFmZmMyNGI0OWU3MmQ4
15
+ MjY2MmQwNTc4YTJhNDk3MjMyZGQxYWVmNDk5M2EyNTAxM2ZhMjI=
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.kpf
3
+ *.rbc
4
+ *.sw?
5
+ *.un~
6
+ .DS_Store
7
+ .buildpath
8
+ .bundle
9
+ .config
10
+ .project
11
+ .yardoc
12
+ Gemfile.lock
13
+ InstalledFiles
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
data/Gemfile ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Robert Pozoga
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ MultiDispatch
2
+ ====================
3
+
4
+ multiple-dispatch is a library extending Ruby objects with multiple dispatch generic functions.
5
+
6
+ Look for examples in the examples/ directory and in test cases test/. Here are simple examples:
7
+
8
+ Functional style
9
+ ----------------
10
+
11
+ ```ruby
12
+ require_relative '../lib/multi_dispatch.rb'
13
+
14
+ # reverse for Array
15
+ MultiDispatch::def_multi :reverse, [] { [] }
16
+ MultiDispatch::def_multi :reverse, Array do |list|
17
+ [list.pop] + reverse(list)
18
+ end
19
+
20
+ # map for Array
21
+ MultiDispatch::def_multi :map, [], Proc do ; [] end
22
+ MultiDispatch::def_multi :map, Array, Proc do |list, func|
23
+ [func.call(list.first)] + map(list[1..-1], func)
24
+ end
25
+
26
+ # foldl for Array (equivalent to haskell implementation in Data.List module)
27
+ MultiDispatch::def_multi :foldl, Proc, Object, [] do |f, init, l|
28
+ init
29
+ end
30
+ MultiDispatch::def_multi :foldl, Proc, Object, Array do |f, init, l|
31
+ foldl(f, f.call(init, l.first), l.drop(1))
32
+ end
33
+ ```
34
+
35
+
36
+ Pattern Matching
37
+ ----------------
38
+
39
+ It allows to do hacky pattern matching using annonymous functions for defining parameters. The example below shows equivalent Ruby code for Racket examples of list pattern matching:
40
+
41
+
42
+ ```ruby
43
+ # (match '(1 2 3)
44
+ # [(list a b c) (list c b a)]) => '(3 2 1)
45
+ #
46
+ def_multi :ex1, lambda { |l| l.size == 3 } do |list|
47
+ a,b,c = list
48
+ [c, b, a]
49
+ end
50
+
51
+ # (match '(1 2 3)
52
+ # [(list 1 a ...) a]) => '(2 3)
53
+ #
54
+ def_multi :ex2, lambda { |l| l.first == 1 } do |list|
55
+ list[1..-1]
56
+ end
57
+
58
+ # (match '(1 (2) (2) (2) 5)
59
+ # [(list 1 (list a) ..3 5) a]
60
+ # [_ 'else]) => '(2 2 2)
61
+ #
62
+ ptr = lambda do |list|
63
+ list.first == 1 && list.size == 5 && list.last == 5 &&
64
+ list[1..-2].all? { |e| e.size == 1}
65
+ end
66
+ def_multi :ex5, ptr do |list|
67
+ list[1..3].map(&:first)
68
+ end
69
+
70
+ # (match '(1 2 3)
71
+ # [(list-no-order 3 2 x) x]) => 1
72
+ #
73
+ ptr = lambda do |list|
74
+ list.size == 3 && list.include?(2) && list.include?(3)
75
+ end
76
+ def_multi :ex6, ptr do |list|
77
+ list.delete_if { |x| [2,3].include? x }.first
78
+ end
79
+
80
+ # (match '(1 (2 3) 4)
81
+ # [(list _ (and a (list _ ...)) _) a]) => '(2 3)
82
+ #
83
+ ptr = lambda do |list|
84
+ list.size == 3 && list[1].size >= 1
85
+ end
86
+ def_multi :ex7, ptr do |list|
87
+ list[1]
88
+ end
89
+ ```
90
+
91
+ Method Ambiguities
92
+ ------------------
93
+
94
+ It is possible to define a set of methods such that there is no unique most specific method applicable to some combinations of arguments:
95
+
96
+ ```ruby
97
+ def_multi :arg, 1, Numeric do |*a|
98
+ a.first
99
+ end
100
+ def_multi :arg, Numeric, 2 do |*a|
101
+ a.last
102
+ end
103
+
104
+ # is_one?(1) matches to the method with argument defined by value
105
+ def_multi :is_one?, 1 do
106
+ true
107
+ end
108
+ def_multi :is_one?, lambda { |x| x == 1 } do
109
+ true
110
+ end
111
+
112
+ 1.9.3-p374 :001 > arg(1,2)
113
+ 2
114
+
115
+ 1.9.3-p374 :001 > arg(1, 1337)
116
+ 1
117
+
118
+ 1.9.3-p374 :001 > arg(1337, 2)
119
+ 2
120
+
121
+ ```
122
+
123
+
124
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList['tests/*.rb']
6
+ t.verbose = true
7
+ end
data/TODO.md ADDED
@@ -0,0 +1,7 @@
1
+ * more documentation
2
+
3
+ * comments
4
+
5
+ * more examples
6
+
7
+ * add inspection for arguments defined by lambda conditions (ruby2ruby)
@@ -0,0 +1,30 @@
1
+ require_relative '../lib/multi_dispatch.rb'
2
+
3
+
4
+ # reverse for Array
5
+ MultiDispatch::def_multi :reverse, [] { [] }
6
+ MultiDispatch::def_multi :reverse, Array do |list|
7
+ [list.pop] + reverse(list)
8
+ end
9
+
10
+ p reverse [1,2,3,4,5]
11
+
12
+ # map for Array
13
+ MultiDispatch::def_multi :map, [], Proc do ; [] end
14
+ MultiDispatch::def_multi :map, Array, Proc do |list, func|
15
+ [func.call(list.first)] + map(list[1..-1], func)
16
+ end
17
+
18
+ p map [1,2,3], lambda { |x| x**2 }
19
+
20
+ # foldl for Array (equivalent to haskell Data.List implementation)
21
+ MultiDispatch::def_multi :foldl, Proc, Object, [] do |f, init, l|
22
+ init
23
+ end
24
+ MultiDispatch::def_multi :foldl, Proc, Object, Array do |f, init, l|
25
+ foldl(f, f.call(init, l.first), l.drop(1))
26
+ end
27
+
28
+ p foldl(lambda { |x, y| x+y }, 0, [1,2,3,4,5])
29
+
30
+
@@ -0,0 +1,162 @@
1
+ require_relative '../lib/multi_dispatch.rb'
2
+
3
+ class Interpreter
4
+ include MultiDispatch
5
+
6
+ def initialize
7
+ @env = Env.new
8
+ end
9
+
10
+ class Env < Hash
11
+ end
12
+
13
+ class Expression
14
+
15
+ end
16
+
17
+ class Var < Expression
18
+ attr_accessor :name
19
+ def initialize(name)
20
+ @name = name
21
+ end
22
+ end
23
+
24
+ class Num < Expression
25
+ attr_accessor :value
26
+ def initialize(value) ; @value = value end
27
+ end
28
+
29
+ class Bool < Expression
30
+ attr_accessor :value
31
+ def initialize(value) ; @value = value end
32
+ end
33
+
34
+ class Plus < Expression
35
+ attr_accessor :left, :right
36
+ def initialize(left, right)
37
+ @left, @right = left, right
38
+ end
39
+ end
40
+
41
+ class Minus < Expression
42
+ attr_accessor :left, :right
43
+ def initialize(left, right)
44
+ @left, @right = left, right
45
+ end
46
+ end
47
+
48
+ class Func < Expression
49
+ attr_accessor :var_name, :body
50
+ def initialize(var_name, body)
51
+ @var_name, @body = var_name, body
52
+ end
53
+ end
54
+
55
+ class Closure
56
+ attr_accessor :var_name, :body
57
+ def initialize(var_name, body)
58
+ @var_name, @body = var_name, body
59
+ end
60
+ end
61
+
62
+ class Call < Expression
63
+ attr_accessor :func, :param
64
+ def initialize(func, param)
65
+ @func, @param = func, param
66
+ end
67
+ end
68
+
69
+ class LetDirect
70
+ attr_accessor :var, :expr, :body
71
+ def initialize(var, expr, body)
72
+ @var, @expr, @body = var, expr, body
73
+ end
74
+ end
75
+
76
+ class LetByFunc
77
+ attr_accessor :var, :expr, :body
78
+ def initialize(var, expr, body)
79
+ @var, @expr, @body = var, expr, body
80
+ end
81
+ end
82
+
83
+ class If
84
+ attr_accessor :cond, :if_true, :if_false
85
+ def initialize(cond, if_true, if_false)
86
+ @cond, @if_true, @if_false = cond, if_true, if_false
87
+ end
88
+ end
89
+
90
+ def_multi :evaluate, Num do |num|
91
+ num.value
92
+ end
93
+
94
+ def_multi :evaluate, Plus do |plus|
95
+ evaluate(plus.left) + evaluate(plus.right)
96
+ end
97
+
98
+ def_multi :evaluate, Minus do |minus|
99
+ evaluate(minus.left) - evaluate(minus.right)
100
+ end
101
+
102
+ def_multi :evaluate, Bool do |bool|
103
+ bool.value
104
+ end
105
+
106
+ def_multi :evaluate, Var do |var|
107
+ @env[var.name]
108
+ end
109
+
110
+ def_multi :evaluate, Func do |func|
111
+ Closure.new(func.var_name, func.body)
112
+ end
113
+
114
+ def_multi :evaluate, Call do |call|
115
+ closure = evaluate(call.func)
116
+ @env[closure.var_name] = evaluate(call.param)
117
+ evaluate(closure.body)
118
+ end
119
+
120
+ def_multi :evaluate, If do |stat|
121
+ evaluate(stat.cond) ? evaluate(stat.if_true) : evaluate(stat.if_false)
122
+ end
123
+
124
+ def_multi :evaluate, LetDirect do |let_dir|
125
+ @env[let_dir.var] = evaluate(let_dir.expr)
126
+ ret = evaluate(let_dir.body) ; @env.delete(let_dir.var) ; ret
127
+ end
128
+
129
+ def_multi :evaluate, LetByFunc do |let_func|
130
+ evaluate(Call.new(Func.new(let_func.var, let_func.body), let_func.expr))
131
+ end
132
+
133
+
134
+ def test
135
+ p evaluate(Num.new(7))
136
+ p evaluate(Plus.new(Num.new(3), Num.new(5)))
137
+ p evaluate(Minus.new(Num.new(4), Num.new(4)))
138
+ p evaluate(Plus.new(Num.new(5), Minus.new(Num.new(2), Num.new(1))))
139
+ @env[:a] = 5
140
+ p evaluate(Var.new(:a))
141
+ p evaluate(Plus.new(Var.new(:a), Minus.new(Num.new(2), Num.new(1))))
142
+ p evaluate(Call.new(Func.new(:x, Plus.new(Var.new(:x), Num.new(1))), Num.new(2)))
143
+ p evaluate(Call.new(
144
+ Func.new(:f, Plus.new(Num.new(2), Call.new(Var.new(:f), Num.new(8)))),
145
+ Func.new(:x, Plus.new(Num.new(5), Var.new(:x)))))
146
+ p evaluate(Call.new(
147
+ Func.new(:f, Plus.new(Num.new(2), Call.new(Var.new(:f), Num.new(8)))),
148
+ Func.new(:f, Plus.new(Num.new(5), Var.new(:f)))))
149
+
150
+ p evaluate(LetDirect.new(:x, Num.new(28), Var.new(:x))) # 28
151
+ p evaluate(LetByFunc.new(:x, Num.new(18), Var.new(:x))) # 18
152
+ p evaluate(LetByFunc.new(:b, Bool.new(true), Var.new(:b))) # 6
153
+ p evaluate(LetByFunc.new(:b, Bool.new(true), If.new(Var.new(:b), Num.new(6), Num.new(7)))) # 6
154
+ p evaluate(LetByFunc.new(:b, Bool.new(false), If.new(Var.new("b"), Num.new(6), Num.new(7)))) # 7
155
+
156
+
157
+ end
158
+
159
+ end
160
+
161
+ inter = Interpreter.new
162
+ inter.test
@@ -0,0 +1,154 @@
1
+ # Simple lambda calculus interpreter to evaluate arithmetic expressions
2
+ # in the form of abstract syntax trees. Has support for anonymous
3
+ # functions, closures, and let expressions.
4
+ #
5
+ # Implemented in functional way, using multi_dispatch gem.
6
+
7
+ require_relative '../lib/multi_dispatch.rb'
8
+
9
+ # helpers
10
+ module Camelizer
11
+ def camelize
12
+ return self.to_s if self !~ /_/ && self =~ /[A-Z]+.*/
13
+ to_s.split('_').map { |e| e.capitalize }.join
14
+ end
15
+ end
16
+
17
+ Symbol.send(:include, Camelizer)
18
+
19
+ # Lambda Calculus Interpreter
20
+ class LCI
21
+ include MultiDispatch
22
+
23
+ attr_accessor :env
24
+
25
+ def initialize
26
+ @env = {}
27
+ end
28
+
29
+ def self.def_expr(name, *args)
30
+ klass = Object.const_set(name.camelize, Class.new)
31
+ klass.class_eval do
32
+ attr_accessor *args
33
+ define_method :initialize do |*values|
34
+ args.zip(values).each do |var, value|
35
+ instance_variable_set("@#{var}", value)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def_expr(:var, :name)
42
+ def_expr(:plus, :left, :right)
43
+ def_expr(:minus, :left, :right)
44
+ def_expr(:func, :var_name, :body)
45
+ def_expr(:closure, :var_name, :body)
46
+ def_expr(:call, :func, :param)
47
+ def_expr(:let_direct, :var, :expr, :body)
48
+ def_expr(:let_by_func, :var, :expr, :body)
49
+ def_expr(:if, :cond, :if_true, :if_false)
50
+
51
+ def_multi :evaluate, Numeric do |num| ; num end
52
+ def_multi :evaluate, true do ; true end
53
+ def_multi :evaluate, false do ; false end
54
+
55
+ def_multi :evaluate, Plus do |plus|
56
+ evaluate(plus.left) + evaluate(plus.right)
57
+ end
58
+
59
+ def_multi :evaluate, Minus do |minus|
60
+ evaluate(minus.left) - evaluate(minus.right)
61
+ end
62
+
63
+ def_multi :evaluate, Var do |var|
64
+ @env[var.name]
65
+ end
66
+
67
+ def_multi :evaluate, Func do |func|
68
+ Closure.new(func.var_name, func.body)
69
+ end
70
+
71
+ def_multi :evaluate, Call do |call|
72
+ closure = evaluate(call.func)
73
+ @env[closure.var_name] = evaluate(call.param)
74
+ evaluate(closure.body)
75
+ end
76
+
77
+ def_multi :evaluate, If do |stat|
78
+ evaluate(stat.cond) ? evaluate(stat.if_true) : evaluate(stat.if_false)
79
+ end
80
+
81
+ def_multi :evaluate, LetDirect do |let_dir|
82
+ @env[let_dir.var] = evaluate(let_dir.expr)
83
+ ret = evaluate(let_dir.body) ; @env.delete(let_dir.var) ; ret
84
+ end
85
+
86
+ def_multi :evaluate, LetByFunc do |let_func|
87
+ evaluate(Call.new(Func.new(let_func.var, let_func.body), let_func.expr))
88
+ end
89
+
90
+ class << self ; undef :def_expr end
91
+
92
+ end
93
+
94
+ # *** TESTS ***
95
+ require 'minitest/autorun'
96
+
97
+ class TestStuff < MiniTest::Unit::TestCase
98
+
99
+ # helper for tests
100
+ def eval(expr)
101
+ LCI.new.evaluate(expr)
102
+ end
103
+
104
+ def test_evaluate_to_7
105
+ assert_equal( 7,
106
+ eval(7))
107
+ end
108
+ def test_simple_addition
109
+ assert_equal( 8,
110
+ eval( Plus.new(3, 5)))
111
+ end
112
+ def test_nested_addition
113
+ assert_equal( 0,
114
+ eval( Minus.new(4, 4)))
115
+ end
116
+ def test_addition_and_subtraction
117
+ assert_equal( 6,
118
+ eval( Plus.new(5, Minus.new(2, 1))))
119
+ end
120
+ def test_eval_with_env_variable
121
+ i = LCI.new
122
+ i.env[:a] = 5
123
+ assert_equal( 5,
124
+ i.evaluate( Var.new(:a) ))
125
+ assert_equal( 6,
126
+ i.evaluate(Plus.new(Var.new(:a), Minus.new(2, 1))))
127
+ end
128
+ def test_functions
129
+ assert_equal( 3,
130
+ eval(Call.new(Func.new(:x, Plus.new(Var.new(:x), 1)), 2)))
131
+ end
132
+ def test_passing_functions_to_functions
133
+ assert_equal( 15,
134
+ eval(Call.new(
135
+ Func.new(:f, Plus.new( 2, Call.new( Var.new(:f), 8))),
136
+ Func.new( :x, Plus.new(5, Var.new(:x))))))
137
+ assert_equal( 15,
138
+ eval(Call.new(
139
+ Func.new(:f, Plus.new(2, Call.new(Var.new(:f), 8))),
140
+ Func.new(:f, Plus.new(5, Var.new(:f))))))
141
+ end
142
+ def test_let_direct
143
+ assert_equal( 28, eval(LetDirect.new(:x, 28, Var.new(:x))))
144
+ assert_equal( 18, eval(LetByFunc.new(:x, 18, Var.new(:x))))
145
+ end
146
+ def test_let_by_func
147
+ assert_equal( true, eval(LetByFunc.new(:b, true, Var.new(:b))))
148
+ assert_equal( 6, eval(LetByFunc.new(:b, true, If.new(Var.new(:b), 6, 7))))
149
+ assert_equal( 7, eval(LetByFunc.new(:b, false, If.new(Var.new(:b), 6, 7))))
150
+ end
151
+
152
+ end
153
+
154
+
@@ -0,0 +1,122 @@
1
+ require_relative '../lib/multi_dispatch.rb'
2
+
3
+ class ListPatternMatching
4
+ include MultiDispatch
5
+
6
+ # (match '(1 2 3)
7
+ # [(list a b c) (list c b a)]) => '(3 2 1)
8
+ #
9
+ def_multi :ex1, lambda { |l| l.size == 3 } do |list|
10
+ a,b,c = list
11
+ [c, b, a]
12
+ end
13
+
14
+ # (match '(1 2 3)
15
+ # [(list 1 a ...) a]) => '(2 3)
16
+ #
17
+ def_multi :ex2, lambda { |l| l.first == 1 } do |list|
18
+ list[1..-1]
19
+ end
20
+
21
+ # (match '(1 2 3 4)
22
+ # [(list 1 a ..3) a]
23
+ # [_ 'else]) => '(2 3 4)
24
+ #
25
+ def_multi :ex3,
26
+ lambda { |l| l.first == 1 && l[1..-1].size >= 3 } do |list|
27
+ list[1..3]
28
+ end
29
+
30
+ # (match '(1 2 3 4 5)
31
+ # [(list 1 a ..3 5) a]
32
+ # [_ 'else]) => '(2 3 4)
33
+ #
34
+ def_multi :ex4,
35
+ lambda { |l| l.first == 1 && l.size == 5 && l.last == 5 } do |list|
36
+ list[1..3]
37
+ end
38
+
39
+ # (match '(1 (2) (2) (2) 5)
40
+ # [(list 1 (list a) ..3 5) a]
41
+ # [_ 'else]) => '(2 2 2)
42
+ #
43
+ ptr = lambda do |list|
44
+ list.first == 1 && list.size == 5 && list.last == 5 &&
45
+ list[1..-2].all? { |e| e.size == 1}
46
+ end
47
+ def_multi :ex5, ptr do |list|
48
+ list[1..3].map(&:first)
49
+ end
50
+
51
+ # (match '(1 2 3)
52
+ # [(list-no-order 3 2 x) x]) => 1
53
+ #
54
+ ptr = lambda do |list|
55
+ list.size == 3 && list.include?(2) && list.include?(3)
56
+ end
57
+ def_multi :ex6, ptr do |list|
58
+ list.delete_if { |x| [2,3].include? x }.first
59
+ end
60
+
61
+ # (match '(1 (2 3) 4)
62
+ # [(list _ (and a (list _ ...)) _) a]) => '(2 3)
63
+ #
64
+ ptr = lambda do |list|
65
+ list.size == 3 && list[1].size >= 1
66
+ end
67
+ def_multi :ex7, ptr do |list|
68
+ list[1]
69
+ end
70
+
71
+ end
72
+
73
+ class RegexPatternMatching
74
+ include MultiDispatch
75
+
76
+ animals_ptr = /dog|cat|cow|ox/
77
+ def_multi :classify, lambda { |s| s =~ animals_ptr } do |s|
78
+ s =~ animals_ptr ; [$&, :animal]
79
+ end
80
+
81
+ def_multi :classify, lambda { |s| s =~ /-?\d+/ } do |s|
82
+ s =~ /-?\d*/ ; [$&, :integer]
83
+ end
84
+
85
+ food_ptr = /burger|pizza|falafel|burrito|pasta/
86
+ def_multi :classify, lambda { |s| s =~ food_ptr } do |s|
87
+ s =~ food_ptr ; [$&, :food]
88
+ end
89
+
90
+ os_ptr = /windows|linux|freebsd|solaris|plan b/
91
+ def_multi :classify, lambda { |s| s =~ os_ptr } do |s|
92
+ s =~ os_ptr ; [$&, :os]
93
+ end
94
+
95
+ end
96
+
97
+ # *** TESTS ***
98
+ require 'minitest/autorun'
99
+
100
+ class TestStuff < MiniTest::Unit::TestCase
101
+
102
+ def test_list_pattern_matching
103
+ pm = ListPatternMatching.new
104
+ assert_equal([3,2,1], pm.ex1([1, 2, 3]))
105
+ assert_equal([2,3], pm.ex2([1, 2, 3]))
106
+ assert_equal([2,3,4], pm.ex3([1, 2, 3, 4]))
107
+ assert_equal([2,3,4], pm.ex4([1, 2, 3, 4, 5]))
108
+ assert_equal([2,2,2], pm.ex5([1, [2], [2], [2], 5]))
109
+ assert_equal(1, pm.ex6([1, 2, 3]))
110
+ assert_equal([2, 3], pm.ex7([1, [2, 3], 4]))
111
+
112
+ end
113
+
114
+ def test_regexp_pattern_matching
115
+ pm = RegexPatternMatching.new
116
+ assert_equal(['dog', :animal], pm.classify('some dog'))
117
+ assert_equal(['-1337', :integer], pm.classify('-1337 or 31337'))
118
+ assert_equal(['freebsd', :os], pm.classify('freebsd is great!'))
119
+ assert_equal(['pasta', :food], pm.classify('I love italian pasta'))
120
+ end
121
+
122
+ end
data/examples/pe_31.rb ADDED
@@ -0,0 +1,23 @@
1
+ require_relative '../lib/multi_dispatch.rb'
2
+
3
+ # PROJECT EULER PROBLEM 31
4
+ #
5
+ # The example shows that it's possible to simulate pattern matching
6
+ # behavior using anonymous functions to define condition for the pattern.
7
+ # It is equivalent to the haskell cade below:
8
+ #
9
+ # count _ 0 = 1
10
+ # count [c] _ = 1
11
+ # count (c:cs) s = sum $ map (count cs . (s-)) [0,c..s]
12
+
13
+
14
+ MultiDispatch::def_multi :count, Object, 0 do ; 1 end
15
+ MultiDispatch::def_multi :count,
16
+ lambda { |list| list.size == 1 }, Object do |a, b| 1 end
17
+ MultiDispatch::def_multi :count, Array, Numeric do |list, sum|
18
+ (0..sum).step(list.first).to_a.map { |e|
19
+ count(list[1..-1], sum-e)
20
+ }.reduce(:+)
21
+ end
22
+
23
+ p count( [200,100,50,20,10,5,2,1], 200)
@@ -0,0 +1,58 @@
1
+ require_relative 'unbound_multi_method'
2
+ require_relative 'multi_method'
3
+
4
+ module MultiDispatch
5
+
6
+ module ClassMethods
7
+
8
+ def def_multi(_name, *args, &body)
9
+ # _name = args.shift
10
+ # closure (to prevent namespace pollution)
11
+ multi_methods = {}
12
+
13
+ # only at first invocation
14
+ define_singleton_method :instance_multi_methods do
15
+ multi_methods.values.flatten
16
+ end
17
+
18
+ define_singleton_method :instance_multi_method do |name, *args|
19
+ mthd = self.instance_multi_methods.select do |m|
20
+ m.match?(name, args)
21
+ end.sort_by { |m| m.match_distance(args) }.first
22
+ unless mthd
23
+ raise NoMethodError,
24
+ "undefined method `#{name}' for class `#{singleton_class}'"
25
+ end
26
+ mthd
27
+ end
28
+
29
+ define_singleton_method :def_multi do |name, *args, &body|
30
+ multi_methods[name] ||= []
31
+ multi_methods[name].unshift(UnboundMultiMethod.new(name, *args, body ))
32
+ if !method_defined?(name)
33
+ define_method name do |*params|
34
+ singleton_class.instance_multi_method(name, *params).
35
+ bind(self).call(*params)
36
+ end
37
+ end
38
+ end
39
+ self.send(:def_multi, *([_name]+args), &body)
40
+ end
41
+
42
+ end
43
+
44
+ def self.def_multi(name, *args, &body)
45
+ Object.instance_eval do
46
+ include MultiDispatch
47
+ def_multi(name, *args, &body)
48
+ end
49
+ end
50
+
51
+ def self.instance_multi_methods ; [] end
52
+ def self.instance_multi_method(name, obj)
53
+ raise NoMethodError,
54
+ "undefined method `#{name}' for class `#{obj.class}'"
55
+ end
56
+
57
+ end
58
+
@@ -0,0 +1,34 @@
1
+ module MultiDispatch
2
+
3
+ class MultiMethod
4
+ extend Forwardable
5
+
6
+ def_delegators :@unbound_method, :arity, :eql?, :to_s, :owner, :name
7
+
8
+ def initialize(obj, unbnd_mthd)
9
+ @obj, @unbound_method = obj, unbnd_mthd
10
+ end
11
+
12
+ def call(*args)
13
+ if args.size != arity
14
+ raise ArgumentError,
15
+ "wrong number of arguments(#{args.size} for #{arity})"
16
+ end
17
+ @obj.instance_exec(*args, &@unbound_method.body)
18
+ end
19
+
20
+ def unbind
21
+ @unbound_method
22
+ end
23
+
24
+ def receiver
25
+ @obj
26
+ end
27
+
28
+ def to_proc
29
+ Proc.new { |*args| call(*args) }
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,62 @@
1
+ module MultiDispatch
2
+
3
+ class UnboundMultiMethod
4
+ attr_accessor :patterns, :body, :name
5
+
6
+ def initialize(name, *args, body)
7
+ @name, @patterns, @body = name, args, body
8
+ end
9
+
10
+ def match?(name, params)
11
+ name == @name &&
12
+ params.size == arity &&
13
+ params.zip(@patterns).all? do |param, pattern|
14
+ # match by type (Class)
15
+ (pattern.is_a?(Class) && param.kind_of?(pattern)) ||
16
+ # match by condition passed in Proc object
17
+ (pattern.is_a?(Proc) && pattern.call(param) rescue false) ||
18
+ # match by value
19
+ param == pattern
20
+ end
21
+ end
22
+
23
+ def match_distance(params)
24
+ params.zip(@patterns).reduce(0) do |dist, pair|
25
+ prm, ptr = pair
26
+ if prm == ptr ; dist -= 1
27
+ else
28
+ if (ptr.is_a?(Class) && prm.kind_of?(ptr))
29
+ # calculates the distance from given type to parent type
30
+ prm = prm.class
31
+ while (dist+=1 ; prm != ptr) do
32
+ prm = prm.superclass
33
+ end
34
+ end
35
+ end
36
+ dist
37
+ end
38
+ end
39
+
40
+ def bind(obj)
41
+ MultiMethod.new(obj, self)
42
+ end
43
+
44
+ def arity
45
+ @patterns.size
46
+ end
47
+
48
+ def eql?(mthd)
49
+ self.body == mthd.body
50
+ end
51
+
52
+ def to_s
53
+ "#{self.class}: #{self.class}##{name} (#{@patterns.map { |p| p.is_a?(Proc) ? Proc : p }})"
54
+ end
55
+
56
+ def parameters
57
+ @patterns
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,3 @@
1
+ module MultiDispatch
2
+ VERSION = "0.1dev"
3
+ end
@@ -0,0 +1,13 @@
1
+ require 'forwardable'
2
+
3
+ require_relative 'multi_dispatch/version'
4
+ require_relative 'multi_dispatch/dispatch'
5
+
6
+
7
+ module MultiDispatch
8
+
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+ end
12
+
13
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'multi_dispatch/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "multi_dispatch"
9
+ spec.version = MultiDispatch::VERSION
10
+ spec.authors = ["Robert Pozoga"]
11
+ spec.email = ["robert.pozoga@gmail.com"]
12
+ spec.description = %q{This gem provides light-weight and easy-to-use multiple dispatch generic methods.}
13
+ spec.summary = %q{Multiple dispatch for Ruby.}
14
+ spec.homepage = "http://github.com/robpe/multi-dispatch"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../lib/multi_dispatch.rb'
2
+ require 'minitest/autorun'
3
+
4
+ class TestAmbiguity < MiniTest::Unit::TestCase
5
+
6
+ class Foo
7
+ include MultiDispatch
8
+
9
+ def_multi :arg, 1, Numeric do |*a|
10
+ a.first
11
+ end
12
+ def_multi :arg, Numeric, 2 do |*a|
13
+ a.last
14
+ end
15
+
16
+ def_multi :is_one?, 1 do
17
+ true
18
+ end
19
+ def_multi :is_one?, lambda { |x| x == 1 } do
20
+ true
21
+ end
22
+
23
+ end
24
+
25
+ def test_ambigous_cases
26
+ f = Foo.new
27
+
28
+ assert_equal(2, f.arg(1,2))
29
+ assert_equal(1, f.arg(1, 1337))
30
+ assert_equal(2, f.arg(1337, 2))
31
+ end
32
+
33
+ def test_values_over_lambdas
34
+ f = Foo.new
35
+ # values have higher priority than lambda functions
36
+ assert_equal( 1,
37
+ f.singleton_class.instance_multi_method(:is_one?, 1).parameters.first)
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,87 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/multi_dispatch'
3
+
4
+
5
+ class TestSimpleStuff < MiniTest::Unit::TestCase
6
+
7
+ class JuliaExample
8
+ include MultiDispatch
9
+
10
+ def initialize(mul)
11
+ @mltplr = mul
12
+ end
13
+
14
+ def_multi :g, Float, Float do |x, y|
15
+ @mltplr*x + @mltplr*y
16
+ end
17
+
18
+ def_multi :g, Float, Object do |x, y|
19
+ @mltplr*x + y
20
+ end
21
+
22
+ def_multi :g, Object, Float do |x, y|
23
+ x + @mltplr*y
24
+ end
25
+ end
26
+
27
+ def test_looking_up_instance_multi_methods_works
28
+ multi_methods = JuliaExample.instance_multi_methods
29
+
30
+ assert_equal(3, multi_methods.size)
31
+ assert_equal(3, multi_methods.select { |m| m.name == :g }.size)
32
+
33
+ example = JuliaExample.new(2)
34
+ assert_equal(
35
+ JuliaExample.instance_multi_methods.select { |m|
36
+ m.patterns.all? { |p| p.eql? Float }
37
+ }.first,
38
+ JuliaExample.instance_multi_method(:g, 1.0, 1.0))
39
+ end
40
+
41
+ def test_bind_call_to_proc_methods
42
+ unbound = JuliaExample.instance_multi_method(:g, Float, Float)
43
+ bound = unbound.bind(JuliaExample.new(7))
44
+
45
+ assert_equal(unbound, bound.unbind)
46
+ expected = JuliaExample.new(7).g(133.0, 37.0)
47
+ assert_equal(expected, bound.call(133.0, 37.0))
48
+ assert_equal(expected, bound.to_proc.call(133.0, 37.0))
49
+ end
50
+
51
+ def test_dispatch_fails_on_nonexistent_methods
52
+ example = JuliaExample.new(7)
53
+ assert_raises(NoMethodError) { example.f(1) }
54
+ assert_raises(NoMethodError) {
55
+ JuliaExample.instance_multi_method(:f, 1)
56
+ }
57
+ end
58
+
59
+ end
60
+
61
+ class TestDispatchFunctionality < MiniTest::Unit::TestCase
62
+
63
+ class Container
64
+ include MultiDispatch
65
+
66
+ def_multi :reverse, [] { [] }
67
+ def_multi :reverse, Array do |list|
68
+ [list.pop] + reverse(list)
69
+ end
70
+
71
+ def_multi :map, [], Proc do ; [] end
72
+ def_multi :map, Array, Proc do |list, func|
73
+ [func.call(list.first)] + map(list[1..-1], func)
74
+ end
75
+
76
+ end
77
+
78
+ def test_multi_methods
79
+ container = Container.new
80
+ container.reverse([1,2,3,4,5])
81
+
82
+ assert_equal( [5,4,3,2,1], container.reverse([1,2,3,4,5]))
83
+ assert_equal( [1,4,9,16],
84
+ container.map([1,2,3,4], Proc.new { |x| x**2 }))
85
+ end
86
+
87
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multi_dispatch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1dev
5
+ platform: ruby
6
+ authors:
7
+ - Robert Pozoga
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: This gem provides light-weight and easy-to-use multiple dispatch generic
42
+ methods.
43
+ email:
44
+ - robert.pozoga@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - Gemfile
51
+ - LICENSE
52
+ - README.md
53
+ - Rakefile
54
+ - TODO.md
55
+ - examples/functional_style.rb
56
+ - examples/lambda_calc.rb
57
+ - examples/lc_interpreter.rb
58
+ - examples/pattern_matching.rb
59
+ - examples/pe_31.rb
60
+ - lib/multi_dispatch.rb
61
+ - lib/multi_dispatch/dispatch.rb
62
+ - lib/multi_dispatch/multi_method.rb
63
+ - lib/multi_dispatch/unbound_multi_method.rb
64
+ - lib/multi_dispatch/version.rb
65
+ - multi_dispatch.gemspec
66
+ - tests/ambiguity_tests.rb
67
+ - tests/multi_dispatch_tests.rb
68
+ homepage: http://github.com/robpe/multi-dispatch
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ! '>'
84
+ - !ruby/object:Gem::Version
85
+ version: 1.3.1
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.0.6
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Multiple dispatch for Ruby.
92
+ test_files: []
93
+ has_rdoc: