Automated ducks solution (was: Re: Duck Typing as Pattern Matching)

Lähettäjä: "David A. Black" <dblack@wobblini.net>
Aihe: Re: Duck Typing as Pattern Matching

Hi --

> 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... :slight_smile:
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?' :slight_smile:
  # ...
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

···

On Mon, 10 Jan 2005, itsme213 wrote: