V
Victor \Zverok\ Shepelev
Hi all.
I think, pattern-matching for function argument is a rather good idea.
I know, it it someway embodied into Multiple Dispatch Library[1], but it has
some major drawbacks:
1. it is not Ruby:
#normal method:
def reverse(array)
end
#multiple dispatched method:
multi
reverse, []){}
multi
reverse, Array){}
2. it is checking for argument *types*, not their "shape" (read further to
understand it)
3. it does only arguments checking, not their conversion, for ex., lists
can't be divided into "head" and "tail" (therefore my note about "arguments
shape": you can't say something like "argument 1 should be array with first
element being hash).
4. it has no progress since v0.1 (December 21, 2005)
*** And Now for Something Completely Different!!!***
(Oh, is it me citing Monthy *Python*? Shame on me! Chunky bacon! Chunky
bacon!)
The proposed code is rather proof-of-concept than library (it has some ugly
fragments), but it is working.
Some examples:
"sample".pattern_match("sample") #=> []
"sample".pattern_match("other sample") #=>nil
#array of remembered matches returned.
#Empty array means "pattern matched, but nothing to remember"
#Ok, it's very trivial. More complex:
"sample".pattern_match(String) #=> ["sample"]
#The example above checked, if "sample" match String pattern,
#and remembers matched values
"sample".pattern_match(Object) #=> ["sample"]
"sample".pattern_match(Numeric) #=> nil
#life becames more intersting:
["sample", 1].pattern_match(["sample", 1]) #=> []
["sample", 1].pattern_match(["sample", Numeric]) #=> [1]
["sample", 1].pattern_match([String, Numeric]) #=> ["sample", 1]
#patterns can be nested:
["sample", [1,2]].pattern_match([String, [Numeric, Numeric]]) #=> ["sample",
1, 2]
#hashes also work
{"sample" => [1,2]}.pattern_match({String => Array}) #=> ["sample", [1,2]]
{"sample" => [1,2]}.pattern_match({String => [Integer, Integer]}) #=>
["sample", 1, 2]
#splat operator used to say "tail", "the rest of array"
["sample", 1].pattern_match([String, *Object]) #=> ["sample", [1]]
["sample", 1,2,3,4].pattern_match([String, *Object]) #=> ["sample",
[1,2,3,4]]
["sample"].pattern_match([String, *Object]) #=> ["sample", []]
And, finally, the usage of above for multiple dispatch:
-------------------------------------------------------
def reverse(*arg)
arg.pmatch([]){
[]
} ||
arg.pmatch([Object, *Object]){|first, tail|
reverse(tail) + [first]
} ||
arg.nomatch!
end
p reverse([1,2,3]) #=> [3,2,1]
p reverse("") #in `Array#nomatch!': Can't find match for [""] (TypeError)
Isn't it SEXY?
More complex example:
def printTag(*arg)
arg.pmatch(String){|tag|
printTag(tag, [])
} ||
arg.pmatch(String, []){|tag|
"<#{tag}>"
} ||
arg.pmatch(String, Array){|tag, attr|
"<#{tag}#{printAttrs(attr)}>"
} ||
arg.nomatch!
end
def printAttrs(*arg)
arg.pmatch([]){
""
} ||
arg.pmatch([ [String, String], *Object]){|key, value, tail|
" #{key}='#{value}'" + printAttrs(tail)
} ||
arg.nomatch!
end
puts printTag('tag') #=> "<tag>"
puts printTag('tag', [['a','b'], ['c','d']]) #=> "<tag a='b' c='d'>"
No more word.
Here's the code (a bit ugly, sorry):
-------
class Object
def pattern_match(pattern)
pattern.match_by(self)
end
end
class Object
def match_by(obj)
obj == self && []
end
end
class Class
def match_by(obj)
obj.kind_of?(self) && [obj]
end
end
class Array
def match_by(obj)
if !self.empty? && self.last == :splat
head, tail = obj[0...self.size-1], obj[self.size-1..-1]
res = head.pattern_match(self[0..-2])
res ? res + [tail] : nil
else
obj.instance_of?(Array) &&
obj.size == self.size &&
self.zip(obj).inject([]){|res, po|
p,o = po;
r = o.pattern_match(p);
break unless r;
res += r
}
end
end
end
class Hash
def match_by(obj)
return false unless obj.instance_of?(Hash) && obj.size == self.size
res = []
self.each{|k, v|
pair = obj.detect{|ko, vo|
rk = ko.pattern_match(k)
rv = vo.pattern_match(v)
if rk && rv
res += rk + rv
true
else
false
end
}
return false unless pair
}
res
end
end
class Class
def to_splat; [:splat] end
end
class Array
def pmatch(*patterns, &block)
res = self.pattern_match(patterns)
if res
if block
block[*res]
else
res
end
else
nil
end
end
def nomatch!
raise TypeError, "Can't find match for #{self.inspect}"
end
end
-------
That's all.
Thanks.
V.
I think, pattern-matching for function argument is a rather good idea.
I know, it it someway embodied into Multiple Dispatch Library[1], but it has
some major drawbacks:
1. it is not Ruby:
#normal method:
def reverse(array)
end
#multiple dispatched method:
multi
multi
2. it is checking for argument *types*, not their "shape" (read further to
understand it)
3. it does only arguments checking, not their conversion, for ex., lists
can't be divided into "head" and "tail" (therefore my note about "arguments
shape": you can't say something like "argument 1 should be array with first
element being hash).
4. it has no progress since v0.1 (December 21, 2005)
*** And Now for Something Completely Different!!!***
(Oh, is it me citing Monthy *Python*? Shame on me! Chunky bacon! Chunky
bacon!)
The proposed code is rather proof-of-concept than library (it has some ugly
fragments), but it is working.
Some examples:
"sample".pattern_match("sample") #=> []
"sample".pattern_match("other sample") #=>nil
#array of remembered matches returned.
#Empty array means "pattern matched, but nothing to remember"
#Ok, it's very trivial. More complex:
"sample".pattern_match(String) #=> ["sample"]
#The example above checked, if "sample" match String pattern,
#and remembers matched values
"sample".pattern_match(Object) #=> ["sample"]
"sample".pattern_match(Numeric) #=> nil
#life becames more intersting:
["sample", 1].pattern_match(["sample", 1]) #=> []
["sample", 1].pattern_match(["sample", Numeric]) #=> [1]
["sample", 1].pattern_match([String, Numeric]) #=> ["sample", 1]
#patterns can be nested:
["sample", [1,2]].pattern_match([String, [Numeric, Numeric]]) #=> ["sample",
1, 2]
#hashes also work
{"sample" => [1,2]}.pattern_match({String => Array}) #=> ["sample", [1,2]]
{"sample" => [1,2]}.pattern_match({String => [Integer, Integer]}) #=>
["sample", 1, 2]
#splat operator used to say "tail", "the rest of array"
["sample", 1].pattern_match([String, *Object]) #=> ["sample", [1]]
["sample", 1,2,3,4].pattern_match([String, *Object]) #=> ["sample",
[1,2,3,4]]
["sample"].pattern_match([String, *Object]) #=> ["sample", []]
And, finally, the usage of above for multiple dispatch:
-------------------------------------------------------
def reverse(*arg)
arg.pmatch([]){
[]
} ||
arg.pmatch([Object, *Object]){|first, tail|
reverse(tail) + [first]
} ||
arg.nomatch!
end
p reverse([1,2,3]) #=> [3,2,1]
p reverse("") #in `Array#nomatch!': Can't find match for [""] (TypeError)
Isn't it SEXY?
More complex example:
def printTag(*arg)
arg.pmatch(String){|tag|
printTag(tag, [])
} ||
arg.pmatch(String, []){|tag|
"<#{tag}>"
} ||
arg.pmatch(String, Array){|tag, attr|
"<#{tag}#{printAttrs(attr)}>"
} ||
arg.nomatch!
end
def printAttrs(*arg)
arg.pmatch([]){
""
} ||
arg.pmatch([ [String, String], *Object]){|key, value, tail|
" #{key}='#{value}'" + printAttrs(tail)
} ||
arg.nomatch!
end
puts printTag('tag') #=> "<tag>"
puts printTag('tag', [['a','b'], ['c','d']]) #=> "<tag a='b' c='d'>"
No more word.
Here's the code (a bit ugly, sorry):
-------
class Object
def pattern_match(pattern)
pattern.match_by(self)
end
end
class Object
def match_by(obj)
obj == self && []
end
end
class Class
def match_by(obj)
obj.kind_of?(self) && [obj]
end
end
class Array
def match_by(obj)
if !self.empty? && self.last == :splat
head, tail = obj[0...self.size-1], obj[self.size-1..-1]
res = head.pattern_match(self[0..-2])
res ? res + [tail] : nil
else
obj.instance_of?(Array) &&
obj.size == self.size &&
self.zip(obj).inject([]){|res, po|
p,o = po;
r = o.pattern_match(p);
break unless r;
res += r
}
end
end
end
class Hash
def match_by(obj)
return false unless obj.instance_of?(Hash) && obj.size == self.size
res = []
self.each{|k, v|
pair = obj.detect{|ko, vo|
rk = ko.pattern_match(k)
rv = vo.pattern_match(v)
if rk && rv
res += rk + rv
true
else
false
end
}
return false unless pair
}
res
end
end
class Class
def to_splat; [:splat] end
end
class Array
def pmatch(*patterns, &block)
res = self.pattern_match(patterns)
if res
if block
block[*res]
else
res
end
else
nil
end
end
def nomatch!
raise TypeError, "Can't find match for #{self.inspect}"
end
end
-------
That's all.
Thanks.
V.