> LçÉettçËäº "David A. Black" <dblack / wobblini.net> > Aihe: Re: Duck Typing as Pattern Matching > > Hi -- > > On Mon, 10 Jan 2005, itsme213 wrote: > > > I am not a type-system expert, but I started thinking about Ruby-based duck > > typing some time ago and came up with something that seems promising to me. > > I wondered what other Ruby-ists think of the general idea. > > > > Duck typing should specify the minimal behavior required from an object that > > gets bound to a variable. Pattern matching (functional languages, Lisp loop > > macros, etc.) binds a set of variables (all at once) by stating the minimum > > necessary to extract values for the pattern variable from the target > > objects, ignoring other irrelevant bits of that object. Both are about > > matching criteria and binding of variables. > > > > Pattern matching is more concise than the corresponding code to bind each > > variable separately (think regexes, or multiple value assignment), and it > > happens to also convey type information (defines a criteria to match > > objects). > > > > Ruby already uses some special-case patterns: multi-value assignment, > > *splat, &block. > > > > So the basic idea is: duck typing = pattern matching > > Types are patterns. > > An object is a member of a type if the type pattern matches that object. > > > > Preliminary examples below, please don't get hung up on the (very tentative) > > syntax: > > > > ::[x, y] = obj > > # x = obj[0], y = obj[1]; # illustration only, defer *splat for now > > ::[x, {k1: y}] = obj > > # x = obj[0], y = obj[1][:k1] > > > > def f ( [x, y] ) : [] > > # def f ( xy ); x = xy[0]; y = xy[1].. > > # assert returns.respond_to? :[] > > # Notice how revealing the signature has become > > > > def f ( m(): x ) > > # you may prefer var : type style instead... later .. > > # def f (x) > > # assert x.respond_to?(:m) > > > > def f ( [ *m() ] : x ) > > # def f (x) > > # assert x.all?{|y| y.respond_to?(:m)}, or #each equivalent > > > > M1 = ::(m1()) # things that have #m1() > > M1M2 = M1 && ::(m2()) # things with m1(), m2() > > def f ( M1M2: x ) > > # def f (x) > > # assert: x responds to :m1 and :m2 > > > > def f ( (m1(), m2()): x, (m3(): y, m4(): z) ) > > # you may prefer var : type style instead... later .. > > # def f ( x, yz ) > > # assert x.respond_to?(:m1) && x.respond_to(:m2) > > # y = yz.m3() > > # z = yz.m4() > > # Notice how revealing the signature has become. > > > > I am interested in any initial reactions. > > My main first reaction was that I find the reference to duck typing > misleading. I think what you're sketching out here is, in some > respects, the opposite of duck typing; that is, rather than simply > asking an object to do something at the moment you want it to do that > thing, you're introducing a wrapper/notation mechanism which, as I > understand it, will prevent you from getting to that point at all > under certain conditions. Thus your mechanism actually causes you to > *avoid* duck typing. That doesn't make it good or bad in itself -- it > would just be clearer, I think, not to label it duck typing. > > I know we're supposed to ignore all that punctuation for now... :-) > but one way or another, *something* would have to encapsulate all that > information, so it would be likely to look at least somewhat like what > you've got, which for me would be a fairly major drawback. I love the > clean, not very punctuation-heavy look of Ruby so much that I'm > willing to give it lots of weigh against possible language features > and innovations. I'd rather have some explicit calls to #respond_to? > than a system for abbreviating those calls into punctuation -- even > though it's more words, even though it takes longer to type. Ruby is > already very concise; even doing a couple of explicit assignments (as > in the expansion of your last example) is a lot shorter than it would > be if, say, memory had to be allocated, or variable type declared. > > Maybe there's some way to develop the pattern-matching idea but not > necessarily put it all in the method signature. Could incremental > things, like allowing #respond_to? to take an array or multiple > arguments, work in that direction? Patience, the solution is at the bottom. I suppose the issue is what they're to be used for. I agree, pattern matching and duck typing are *not* the same thing. To recap, pattern matching means (in Haskell): n = [] m = [1, 2, 3, 4] -- List 1..4 f [] = 0 -- Return 1 on an empty list f (x:xs) = x -- Return the head element of the list f n -- => 0 f m -- => 1 Essentially f is able to determine what the type of its argument is and then split it into its constituent parts (due to the way the functional type system works). Somewhat equivalent in Ruby would be >> class Foo >> def initialize >> @m = 5 >> @n = 6 >> end >> >> def bar >> # >> end >> end >> >> def f (:m,:n,:bar) >> puts "success" >> end >> >> def f (_) # _ = anything >> puts "failure" >> end >> >> x = 5 >> y = Foo.new >> >> f x => "failure" >> f y => "success" The merits of this approach are obviously not that great in Ruby. What OP seems to want to do is to just have shorthand for the respond_to? method call: >> def foo x >> if !(x.responds_to? :length) >> raise ArgumentError "Doesn't respond to." >> end >> # ... >> end Obviously, a great replacement syntax would be: >> def foo x.length? # Or 'x#length?' :) >> # ... >> end But that has its own problems, so something like 'x::length?' (since :: normally requires a Constant) might work. Multiple methods could be checked with 'x::length?::index?' etc. >> foo 5 => ArgumentError ... >> foo "Hello" => [Whatever] Clarity is of greatest importance here. This would of course be much simpler if def were a method call (and will probably be easier in 2.0) but I suppose you could do something like this (untested): class Module # Automate duck typing def auto_duck(m_sym, parms) # Generate constraints parms = checks = "" parms.each do |parm, ck_syms| parms << "#{parm.id2name}," if ck_syms ck_syms.each do |ck| checks << "raise ArgumentError \"Doesn't respond to #{ck.id2name}\"" + "unless (#{parm.id2name}.respond_to? #{ck.id2name});" end end end # Ensure access to the method module_eval("alias :__#{m_sym.to_i}__, #{m_sym.inspect}) # Define a proxy to do the checking module_eval <<-END_EVAL def #{m_sym.id2name}(#{parms.chop}) #{checks} return __#{m_sym.to_i}__(#{parms.chop}) end END_EVAL end end class Foo # Define a method we want to check def foo_method(bar, baz, bax) # ... end # Define the constraints # The hash must contain all parameters. # Constraints given as an array. # If there's no constraint, give a nil. auto_duck :foo_method, {:bar => [:test1, :test2], :baz => [:test3], :bag => nil} # Trivial to add processing for this type # of method definition instead. include Duck ddef %q{ foo_method(bar#test1 bar#test2, baz#test3, bax) # Method body } end There you go, for what it's worth. This could of course be extended to use some sort of an alternative 'def' to eliminate the extra syntax but it'd be kind of clunky. E