Forum: Ruby Ruby's duck typing

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
stephan.zimmer (Guest)
on 2008-11-27 19:15
(Received via mailing list)
I would like to represent certain data by a list; to this end I let
class SomeData inherit from Array:

    class SomeData < Array
        def get_field
            self[2]
        end
    end

(I want to keep it simple, therefore, the example might look silly.)
Since the "get_field" method is not universal I don't want to reopen
class "Array". If I write

    SomeData.new([1,2,3]).get_field

everything is fine. If I, however, try to do

    [1,2,3].get_field

I get an exception "NoMethodError", which, of course, is not
surprising.

My question is: is there a way around this, that is, to simply write
[1,2,3] to denote a constant of type "SomeData" instead of always
writing "SomeData.new([1,2,3])" (without reopening Array)?

Thanks a lot,

Stephan
Robert D. (Guest)
on 2008-11-27 19:43
(Received via mailing list)
On Thu, Nov 27, 2008 at 6:10 PM, stephan.zimmer
<removed_email_address@domain.invalid> wrote:
Personally for some time now, and thanks to the learned opinions here
I have started to favor delegation over inheritance *especially* when
it comes to core classes.
Look at this approach

http://pastie.org/325446

hopefully you find it helpful.

Cheers
Robert
--
Ne baisse jamais la tête, tu ne verrais plus les étoiles.

Robert D. ;)
Sean O. (Guest)
on 2008-11-27 20:36
(Received via mailing list)
On Thu, Nov 27, 2008 at 5:10 PM, stephan.zimmer
<removed_email_address@domain.invalid> wrote:
> Since the "get_field" method is not universal I don't want to reopen
>
> My question is: is there a way around this, that is, to simply write
> [1,2,3] to denote a constant of type "SomeData" instead of always
> writing "SomeData.new([1,2,3])" (without reopening Array)?
>
> Thanks a lot,
>
> Stephan
>
>
The short answer is no.

A technique that a (select ;) few of us favour is to define factory
methods like this:

def SomeData(*a, &b)
  SomeData.new(*a, &b)
end

so you can write:

  a = SomeData [1,2,3]

You might also want to have a look at Ara Howard's arrayfields:
http://www.codeforpeople.com/lib/ruby/arrayfields/. Latest version
docs here:
http://www.codeforpeople.com/lib/ruby/arrayfields/...

Having said all that, subclassing core classes tends to throw up a
host of fiddly issues. Robert's suggestion to use delegation instead
is a good one where your class doesn't need to be considered as a kind
of Array (in this case).

Regards,
Sean
Brian A. (Guest)
on 2008-11-28 02:41
(Received via mailing list)
"stephan.zimmer" <removed_email_address@domain.invalid> writes:

> Since the "get_field" method is not universal I don't want to reopen
>
> My question is: is there a way around this, that is, to simply write
> [1,2,3] to denote a constant of type "SomeData" instead of always
> writing "SomeData.new([1,2,3])" (without reopening Array)?

Do I understand you correctly that you want a function that works on
instances of SomeData and Array similarly without re-opening Array?

Rather than:

SomeData.new([1,2,3]).get_field
[1,2,3].get_field  # doesn't work

How about the following?

get_field(SomeData.new([1,2,3]))
get_field([1,2,3])

In other words, maybe you want a function that knows how to
"get_field" given *any* indexable sequence.

     1  class SomeData < Array
     2    def self.get_field list
     3      list[2]
     4    end
     5  end
     6
     7  x = SomeData.new([1,2,3])
     8  y = [4,5,6]
     9
    10  puts SomeData.get_field(x)
    11  puts SomeData.get_field(y)

Ruby doesn't support generic functions, so there's a limit to this
approach, but depending on how simple your real class is, it might be
a possibility.
Robert D. (Guest)
on 2008-11-28 03:17
(Received via mailing list)
> Ruby doesn't support generic functions, so there's a limit to this
> approach,
Which limit Brian? I do not see anything Ruby cannot do where you
*have* to use generics in other languages.
Do I miss the obvious here?
Generics are used to create instantiations of a generic for given type
parameters. As there are no types in ruby
the method just takes the place of the generic function.

Robert

--
Ne baisse jamais la tête, tu ne verrais plus les étoiles.

Robert D. ;)
Brian A. (Guest)
on 2008-11-28 03:40
(Received via mailing list)
Robert D. <removed_email_address@domain.invalid> writes:

>> Ruby doesn't support generic functions, so there's a limit to this
>> approach,
> Which limit Brian? I do not see anything Ruby cannot do where you
> *have* to use generics in other languages.
> Do I miss the obvious here?

No, you didn't "miss the obvious" because I didn't provide much detail
:)

If you consider two models of OOP, message passing and generic
functions, Ruby/Smalltalk would be an example of the former, Common
Lisp would be an example of the latter.

With message passing, you can specialize the first parameter
(e.g. self). With generic functions, you can specialize on multiple
parameters.

As Paul Graham stated, "In the message-passing model, methods are *of*
objects, while in the generic function model, they are specialized
*for* objects.

The approach I mentioned has limitations with respect to inheritance
i.e. if "get_field" need to be specialized for various types. It was
more in the "style" of generic functions (not *belonging* to an
object), but without language support, so it's going against the grain
a bit.

In many cases it's a reasonable mechanism (i.e. a more functional
approach), and can allow for simpler reasoning about the function
since it doesn't have dependencies to the class.

> Generics are used to create instantiations of a generic for given type
> parameters. As there are no types in ruby
> the method just takes the place of the generic function.

"generic function" not "generic type"
Robert D. (Guest)
on 2008-11-28 13:20
(Received via mailing list)
On Fri, Nov 28, 2008 at 2:35 AM, Brian A. <removed_email_address@domain.invalid>
wrote:
> Robert D. <removed_email_address@domain.invalid> writes:

There is an old joke: How do you call a dear without eyes? No idea!
And how do you call a dear without eyes and legs? Still no idea.
Unfortunately the later applies to what you say :(
Can you give an example please.

Cheers
Robert


--
Ne baisse jamais la tête, tu ne verrais plus les étoiles.

Robert D. ;)
stephan.zimmer (Guest)
on 2008-11-28 13:32
(Received via mailing list)
Robert, Sean, Brian,

many thanks for your answers and the very interesting discussion.

> Look at this approach
> http://pastie.org/325446
> hopefully you find it helpful.

Ya, that's really nice, also to me delegation seems more reasonable
than inheritance in many cases. I will utilize it. The "factory
approach" is also nice, I never thought about it. I think it gives
handy code.

> As Paul Graham stated, "In the message-passing model, methods are *of*
> objects, while in the generic function model, they are specialized
> *for* objects.

It might be naive, but wouldn't it be possible to simulate a generic
function by simply accessing the types of the parameters (using
"is_a?") and then provide different function bodies for different
parameters?

Is there any document, paper, etc. which states more details about the
Ruby type system, as design motives, comparisons to other languages,
etc.?

Best,

Stephan
Jesús Gabriel y Galán (Guest)
on 2008-11-28 13:46
(Received via mailing list)
On Fri, Nov 28, 2008 at 12:25 PM, stephan.zimmer
<removed_email_address@domain.invalid> wrote:

>> As Paul Graham stated, "In the message-passing model, methods are *of*
>> objects, while in the generic function model, they are specialized
>> *for* objects.
>
> It might be naive, but wouldn't it be possible to simulate a generic
> function by simply accessing the types of the parameters (using
> "is_a?") and then provide different function bodies for different
> parameters?

For this you can take a look at facets/overload, which allows things
like:

class X
    def x
      "hello"
    end

    overload :x, Integer do |i|
      i
    end

    overload :x, String, String do |s1, s2|
      [s1, s2]
    end
  end

(taken from facet's doc: http://facets.rubyforge.org/doc/index.html)

Jesus.
Brian A. (Guest)
on 2008-11-28 20:30
(Received via mailing list)
Robert D. <removed_email_address@domain.invalid> writes:

> On Fri, Nov 28, 2008 at 2:35 AM, Brian A. <removed_email_address@domain.invalid> wrote:
>> Robert D. <removed_email_address@domain.invalid> writes:
>
> There is an old joke: How do you call a dear without eyes? No idea!
> And how do you call a dear without eyes and legs? Still no idea.
> Unfortunately the later applies to what you say :(
> Can you give an example please.

I have no idear what you're talking about above, but the following
might help:

http://gigamonkeys.com/book/object-reorientation-g...
Brian A. (Guest)
on 2008-11-28 20:35
(Received via mailing list)
"stephan.zimmer" <removed_email_address@domain.invalid> writes:

> approach" is also nice, I never thought about it. I think it gives
> handy code.
>
>> As Paul Graham stated, "In the message-passing model, methods are *of*
>> objects, while in the generic function model, they are specialized
>> *for* objects.
>
> It might be naive, but wouldn't it be possible to simulate a generic
> function by simply accessing the types of the parameters (using
> "is_a?") and then provide different function bodies for different
> parameters?

Don't get me wrong - I love Ruby, but Ruby is slow enough already w/o
introducing an interpreted multiple dispatch mechanism. It certainly
could be done, but I wouldn't recommend it. Double dispatch might be
worth considering, but that would get ugly with more params.

Simple functions that depend on duck typing wrt parameters works fine,
but if inheritance is needed, then using Ruby's inheritance model
would be superior to simulating generic functions.
Robert D. (Guest)
on 2008-11-28 23:02
(Received via mailing list)
On Fri, Nov 28, 2008 at 7:25 PM, Brian A. <removed_email_address@domain.invalid>
wrote:
> I have no idear what you're talking about above, but the following
> might help:
You got it :)
>
> http://gigamonkeys.com/book/object-reorientation-g...
Well Ruby does not have method composition but everything, especially
the generic part, is super-seeded by the dynamic typing model.
Robert

--
Ne baisse jamais la tête, tu ne verrais plus les étoiles.

Robert D. ;)
Brian A. (Guest)
on 2008-11-29 00:00
(Received via mailing list)
Robert D. <removed_email_address@domain.invalid> writes:

>>
>> I have no idear what you're talking about above, but the following
>> might help:
> You got it :)
>>
>> http://gigamonkeys.com/book/object-reorientation-g...
> Well Ruby does not have method composition but everything, especially
> the generic part, is super-seeded by the dynamic typing model.

I think you've reached an erroneous conclusion - Ruby's dynamic typing
model certainly does not supercede generic functions. I'd suggest
taking another look at the article (focusing on the Multimethods
section), or finding other sources of info on generic functions.

This is not a slight against Ruby; both models have pros/cons;
although, generic functions are a generalization of message passing,
so, if anything, generic functions supercede message passing.

Anyway, I think I've caused us to veer off topic - this is
comp.lang.ruby, not comp.lang.lisp, so back to our beloved Ruby :)
Robert D. (Guest)
on 2008-11-29 00:52
(Received via mailing list)
On Fri, Nov 28, 2008 at 10:54 PM, Brian A. 
<removed_email_address@domain.invalid>
wrote:

> comp.lang.ruby, not comp.lang.lisp, so back to our beloved Ruby :)
Sure agree we do not have to prove that Ruby is powerful enough :) and
the link you were presenting is a little confusing to me.
Cheers

Robert
Jörg W Mittag (Guest)
on 2008-11-29 20:06
(Received via mailing list)
Robert D. wrote:
> On Fri, Nov 28, 2008 at 10:54 PM, Brian A. <removed_email_address@domain.invalid> wrote:
>> [Message Passing vs. Multimethods]
> Sure agree we do not have to prove that Ruby is powerful enough :) and
> the link you were presenting is a little confusing to me.


Here's my little attempt at explaining multimethods / multiple
dispatch.

In Ruby and most other single-dispatch OOP languages, methods "belong"
to objects, or, in the case of class-based OO languages like Ruby, to
classes of which the objects are instances.

So, we generally send a message like this:

    receiver.message(param, anotherparam)

However, we could just as well use a procedure instead of a method and
call it like this:

    message(receiver, param, anotherparam)

All the information is still there, the method body has access to all
the same parameters as it had before. The only difference is that in
the method example, the receiver parameter was passed implicitly into
the method body and was magically accessible via the special variable
keyword "self", where in the procedure example it was passed
explicitly.

There is one important difference however, and it is one of the
pillars of OO: polymorphism.

The way that procedures are usually implemented, there can only ever
be one procedure with a specific name. With methods however, there can
be many methods with the same name, and which method to execute gets
determined *at runtime* based on the type (or class) of the special
receiver parameter. This is polymorphism, or, more precisely
single-dispatch runtime subtype polymorphism.

This is where multimethods come in.

A multimethod is a procedure like above. *But* there can be many
procedures with the same name! *And* which one to execute is
determined at runtime, not only by looking at the types of the *first*
parameter (like in single-dispatch OO), but by looking at the types of
*all* parameters! In that sense, multimethods are more powerful than
methods: you can write a multimethod which is only polymorphic in its
first parameter, which is exactly the same as with a method. But you
can also write a multimethod which is polymorphic in more of its
parameters, which you cannot do with methods. You can *fake* it,
though: the Visitor pattern is basically an implementation of
double-dispatch (i.e. polymorphism on *two* parameters instead of one)
for single-dispatch languages. Ruby's coercion protocol is another.

Here is a simple example how that might work in Ruby. Remember the
coercion example in the Pickaxe book with the Roman numerals?
Basically, the problem is this: when I call

    'IV'.to_roman + 3

that works, but

    3 + 'IV'.to_roman

doesn't. Why? Because my Roman class knows about Fixnums but Ruby's
builtin Fixnum class doesn't know about Roman numerals. To deal with
this, Ruby has this coercion protocol. In this case, Fixnum#+ calls
'IV'.to_roman.coerce(3), which in turn returns something like [3, 4]
which can then be used to retry the original operation. With
multimethods, all of this would be unnecessary. The "+" method would
not be defined inside of Fixnum and Roman (and Float and String and
Array and ...) but instead as a multimethod:

    # This would be part of Ruby Core:

    defmulti +(a, b)

    def +(Fixnum f1, Fixnum f2)
      # do stuff
    end

    # This would be part of your library:

    def +(Roman r, Numeric n)
      r.to_int + n
    end

    def +(Numeric n, Roman r)
      n + r.to_int
    end

Basically, what multimethods do, is to free polymorphism from the
tyranny of the receiver.

We can go one step further, however: in CLOS multimethods can not only
dispatch on the *type* of all of their arguments, but also on the
*value* of all of their arguments. This enables something like this
classic example:

    defmulti factorial(n)

    def factorial(Integer n)
      return n * factorial(n - 1)
    end

    def factorial(0)
      return 1
    end

In all of these examples, the dispatch function became more and more
powerful: in procedures, there is no dispatch function. In methods,
the dispatch function is basically a simple switch on the type of the
first parameter (although not *that* simple: it has to follow the
inheritance hierarchy, of course). In simple multimethods, the
dispatch function switches on the types of *all* arguments. In CLOS,
the dispatch function switches on the types *and values* of all
arguments.

However, in all of these cases, the dispatch function is a fixed part
of the language implementation. So, Clojure takes the obvious next
step: when declaring a multimethod, you can provide *your own*
dispatch function and you can do whatever you want with the arguments.
Switch on the first, switch on all, switch on the sum of the 7th and
42nd, switch on the phase of the moon ...

In Ruby, that would look something like this (stolen from the Clojure
website):

    defmulti encounter(animal1, animal2)
      return animal1.class, animal2.class
    end

    def encounter(Lion, Rabbit)(l, r)
      eat(l, r)
    end

    def encounter(Lion, Lion)(l1, l2)
      fight(l1, l2)
    end

    def encounter(Rabbit, Rabbit)(r1, r2)
      mate(r1, r2)
    end

    def encounter(Rabbit, Lion)(r, l)
      run(r)
    end

And here the ubiquitous enterprise joke:

    defmulti enterprise_hello_world()
      return rand <= 0.9, rand <= 0.5
    end

    def enterprise_hello_world(true, true)()
      puts 'Hello, World!'
    end

    def enterprise_hello_world(true, false)()
      sleep 10
      puts 'Hello, World!'
    end

    def enterprise_hello_world(false, *)()
      raise RuntimeError
    end

Note the method body of defmulti: this is *not* the code for the
method, this is the dispatch function. Its return value will be
matched against the pattern definition in the first set of
parantheses. The method definitions have *two* sets of parameters:
first the pattern that is matched against the dispatch function, then
the formal parameters of the method.

Whereas simple multimethods free polymorphism from the tyranny of the
first argument, this frees it from the tyranny of the language
designer.

Anyway, that's my little explanation of multimethods, from someone who
understands them as just as badly as you do (-:

It's gotten a little long (note to self: start a blog, damnit!), I
hope it's not *too* long; and I hope it's helpful.

Cheers,
    jwm
Robert D. (Guest)
on 2008-11-29 20:36
(Received via mailing list)
On Sat, Nov 29, 2008 at 7:00 PM, Jörg W Mittag
<removed_email_address@domain.invalid> wrote:

> It's gotten a little long (note to self: start a blog, damnit!), I
> hope it's not *too* long; and I hope it's helpful.
Very clear, thank you!
Robert
Yaser S. (Guest)
on 2008-11-29 23:50
(Received via mailing list)
> On Sat, Nov 29, 2008 at 7:00 PM, Jörg W Mittag
> <removed_email_address@domain.invalid <removed_email_address@domain.invalid>>
wrote:
>
> (note to self: start a blog, damnit!)
I strongly second that ;)

Thanks for the helpful explanation.

Regards,
Yaser S.
This topic is locked and can not be replied to.