From: "David A. Black" Date: 2005-01-11T10:58:58+09:00 Subject: Re: Duck Typing as Pattern Matching Hi -- On Tue, 11 Jan 2005, itsme213 wrote: > > "David A. Black" wrote > >> 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 > > Not always true. Below I've dropped many of the ':' as I think they are not > syntactically essential. > > [x,y] = obj > is the same as > x = obj[0] > y = obj[1] > So I ask obj to 'do' its [0], [1] right where I want it. Variables are bound > to results. Hmmm... irb(main):004:0> obj = [1,2] => [1, 2] irb(main):005:0> [x,y] = obj SyntaxError: compile error Or did you mean: x,y = obj ? I don't think I'd characterize it as obj "doing its [0], [1]". You're not sending messages to obj -- not even the message #[]. What happens in this scenario depends on assignment semantics, not message semantics. Also I think you're playing on the similarity of the literal array constructor [] and the method #[], which I think has to be discounted as something of a coincidence :-) (And, as above, that syntax doesn't parse.) > (m1(obj1) x, m2(obj2) y) = obj > is the same as > x = obj.m1(obj1) > y = obj.m2(obj2) > Again, I ask obj to do it's m1(), m2() right where I want it. Variables are > bound to results. You can devise some very obscure notation in which things happen and the results are saved in variables :-) I don't think that, in itself, makes a case one way or the other. I'm feeling very reactionary here, because I vastly prefer your 'unrolled' ("same as...") version. I don't really get that other syntax; it seems confusing to put the messages being sent to obj on the other side of the assignment from obj itself. What would you gain by that? > x = obj > Object x = obj > are identical. > > Now > T x = obj > would be > x = obj > assert x.is_of_type(T) > # Object#is_of_type would handle named types, type patterns, classes, > etc. It could be an interesting experiment (and it's been attempted), but I don't know that such a thick description of an object at a given moment in its life-cycle would have much practical value. There are mainly two things at stake: whether to send a message to an object, and what will happen when you do. To make a reasonable judgement about the former, the most you need to know is whether the object responds at all to the message. As for the second (what the object will do), you literally cannot know without sending the message, so no amount of thick description will help you there. I wonder whether any full run-time type-capturing facility in Ruby might therefore be doomed to being overengineered, even if quite interesting. >> ...[can] prevent you from getting to that point at all >> under certain conditions. > > Yes, this pattern matching can fail, and depending on context might > sometimes raise errors e.g. In places like a case-when or if- statement a > match can fail without raising any error; its just a boolean check, with the > added bonus of variable bindings from a successful match. In a regular > assignment it can fail to match and will raise the same errors as the > equivalent regular ruby code. > - [x,y]=obj : can raise same errors as x=obj[0], y=obj[1] > - (m1(), m2()) x = obj can raise errors from 'assert x.respond_to...' > >> That doesn't make it good or bad in itself -- it >> would just be clearer, I think, not to label it duck typing. > > I see. Some usages of it are intentionally closer to explicit type > declarations. Maybe 'duck type declarations'? I believe that duck typing and type declaration are two different things -- i.e., that the concept of duck typing is at heart an alternative to the concept of type declaration. In any case, if you declare/describe/document what a Ruby object can do at a given moment, you are declaring/etc. its *type*, not its "duck type". In other words, that's what type *is* in Ruby. If you call this an object's "duck type", it suggests that there's some other "type" that an object can have -- which, frequently if not inexorably, leads back to the type == class thing. >> Maybe there's some way to develop the pattern-matching idea but not >> necessarily put it all in the method signature. > > Yes. For example we could put this info right after the def f(x,y) so the > def line is uncluttered. Something like this would be ok. > def f ( x, y ) > (m1(), m2()) x > # or T x, if T was named type defined as m1(), m2() > In this case Ruby should associate the type-declared x with the parameter x, > rather than a new local variable. Would you agree? In context, yes, but I'm not sold on this syntax either. m1() looks like a method call. If you're querying whether a method *can* be called, but not calling it, I'd rather use something that doesn't look like a method call. #respond_to?(sym) comes to mind.... :-) >> Could incremental >> things, like allowing #respond_to? to take an array or multiple >> arguments, work in that direction? > > I see where you are going. Do you mean: > def f(x) > x.respond_to? :m1, :m2, :m3 > > I did want to somehow distinguish patterns used as type declarations from a > regular (and overridable!) method call like respond_to. There may be other > alternatives to "punctuation", but how would using 'normal' respond_to would > for this purpose? I just wonder whether letting respond_to? take multiple arguments might actually solve, in practice, 99% of the perceived problems in this realm. Or even wrapped, like so: module Kernel def can?(*methods) methods.all? {|m| respond_to?(m) } end end class C def blah(x) raise ArgumentError unless x.can?(:a,:b,:c) end end which looks strangely familiar.... I think that I or someone else wrote something very like that at some other point. David -- David A. Black dblack@wobblini.net