multi_dispatch 0.1dev

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