ruby picture

RCR 318: easy way(s) to make a duck

Submitted by eric_mahurin (Sun Sep 11 21:24:24 UTC 2005)

Abstract

Provide one or more ways to make an object for an argument to a method that is duck-typed.

Problem

In a typical duck-typed method, any argument only needs to respond to just a few methods (usually zero or one). If you don't have an object available that maps to those method names, you may need to make a "duck" that gives this behaviour.

A simple example would be if you have a method that takes an object that responds to #=== (Enumerable#grep for example). You may have an object that you want to use, but you want to use its #include? method (or #== or #[] or #equal?, etc) instead. Here would be one solution:

 # call enum.grep using obj1.include? mapped to obj2.===.
 obj2 = Object.new
 meta_class = (class<<obj2;self;end)
 meta_class.send(:define_method,:===,&obj1.method(:include?))
 enum.grep(obj2,&block)

Proposal

Provide an Object instance method to map methods of an object to a new object with new method names. Also provide another easily accessible method to map Proc's to method names in a new object.

I've also seen a "Behavior" class that did the second method mentioned above. But, I don't have the link.

Assuming the implementation below, the above example would become:

 # grep needs something responding to #=== and we want to use
 # obj1.include? instead.
 enum.grep(obj1.duck(:===,:include?),&block)

Analysis

This will promote additional polymorphism associated with duck-typing.

Implementation

 class Object
     # Return a new object that maps methods from self
     # to different names.  The arguments are a list
     # alternating new and old method names (strings or
     # symbols).  If an odd number (usually one) of
     # arguments is given, the last old method is assumed
     # to be the original object itself - hopefully a Proc
     # or Method and it is used to map to the new name in
     # the new object.
     def duck(*new_old)
         obj = Object.new
         klass = (class << obj;self;end)
         until new_old.empty?
             new,old = new_old.slice!(0,2)
             klass.__send__(:define_method,new,&
                 (old ? self.method(old) : self))
         end
         obj
     end
     # Return a new object that maps a hash of name=>proc
     # to the methods of the object.
     def self.duck(methods)
         obj = self.new
         klass = (class << obj;self;end)
         methods.each { |name,proc|
             klass.__send__(:define_method,name,&proc)
         }
         obj
     end
 end
ruby picture
Comments Current voting
There was some discussion on using proc to do this, which seems nice and versitle.

  obj2 = proc(:==){ |x| obj1.include?(x) }

It might even be extensed to handle mutiple methods.

  proc(:==,:not_there?) { |op,x| 
    case op
    when :==
      obj1.include?(x)
    when :not_there?
      !obj1.include?(x)
    end
  }

T.


This RCR demonstrates a lack of understanding of what duck typing really is. David Black has nicely deconstructed most (if not all) of the flaws behind this, but the quick summary is that duck typing is not a typing system (which this seems to want to introduce) but is rather an approach to programming. Formalising it as something the compiler can deal with is, IMO, a mistake made by novices or theoretical purists who aren't happy with Ruby but can't find anything better. -Austin


Trans, the above implementation does the first example you are talking about very easily:

  1. map :== to self (a proc)
obj2 = lambda { |x| obj1.include?(x) }.duck(:==)

In your second example, I'd still propose just to use multiple Proc's:

Object.duck(

  :== => lambda { |x| obj1.include?(x) },
  :not_there => lambda { |x| !obj1.include?(x) }
)

Austin, you are going off on a tangent. You are talking about the method definition. I've never proposed formalizing anything in the code. I have talked about better describing the "duck-type" in the documentation in the method definition, but that is it. What am I formalizing? I'm only talking about adding another method (or two) to better take advantage of the duck-typing that already exists. I'll add a concrete usage example from the core library - Enumerable.grep. And you can describe how you would solve that example.


There is no duck-typing system that already exists. There exists a *typing* system, which you can deploy, if you choose, in a manner which Dave Thomas has described as "duck typing". It's also plausible to say, as Jim Weirich has, that Ruby is a "duck-typed language" -- that is, that Ruby's typing system always has something of the duck-typing ethos about it, whether one considers oneself to have elected to do duck-typing or not.

Having a method called "duck" adds a fixity and rigidity to all of this which does not play well with either the existing typing system or the concept of duck typing. Asking an object to "duck" also does not make sense semantically, in my view.

Furthermore, as I understand it, there is a lot of freedom but also a certain discipline to duck typing. When you ask that an object respond to << , for example, it's because there's at least *something* about << that resonates with what you're asking the object to do. If you can masquerade any old method as << , it seems to me you're actually creating a way to circumvent duck typing, or at least encouraging it to be done in a less disciplined, less semantically integral way.

David Black


I don't really care the the method name is. It doesn't have to be "duck". I called it that because you are making an object that walks like, talks like the duck the the method needs.

You are correct in that this proposal is allowing one to easily circumvent the original intent the method writer had for the arguments. I think there is a lot of power in that compared to the polymorphism of other languages. I see no reason not to take advantage of it. It also gives ruby some polymorphic bragging rights over other languages.

For the opponents to this RCR, what would be your solution to the problem I posed - using #include? instead of #=== for a pattern object you want to give to Enumerable#grep? Manually create an object an new object that mapped #=== to #include?, add another method to Enumerable, use other methods and more code to accomplish the same functionality, or what?


I think the grep example shows exactly how much of a stretch this is. You'd end up with things like:

 %w{ a b c }.grep(%w{ a d e }.duck(:===, :include?)  # => ["a"]

which I find quite obscure. I would much rather use select, which is designed for this purpose:

 %w{ a b c }.select {|x| %w{ a d e }.include?(x) }  # => ["a"]

With the fill-in-the-blank functionality provided by iterators like select, there shouldn't really be a need to (in effect) reprogram methods like grep.


Yes, that probably is the better solution for the functionality of the non-block form of Enumerable#grep. But what about the block form?

You don't always have the luxury of having multiple methods doing the same thing with changes in what methods are used. Most classes/modules aren't like Enumerable in this respect.

In general, having the method be written using arguments (or a block) treated as a Proc (#call) or even a lookup (#[]) is the easiest to get flexibility when the argument needs to respond to only one method. But, methods are usually written to directly use the common case objects (i.e. Enumerable#grep was likely written for Regexp objects). And in those cases where it needs multiple methods of an argument, you couldn't easily use Proc (or block).

Eric


Strongly opposed 4
Opposed 1
Neutral 1
In favor 0
Strongly advocate 1
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .