LWN.net Logo

Re: indirect object syntax (was Re: Revising the OO docs - take 2)

From:  Tom Christiansen <tchrist-AT-perl.com>
To:  Jesse Luehrs <doy-AT-tozt.net>
Subject:  Re: indirect object syntax (was Re: Revising the OO docs - take 2)
Date:  Sun, 10 Jul 2011 21:25:56 -0600
Message-ID:  <22081.1310354756@chthon>
Cc:  perl5-porters-AT-perl.org
Archive-link:  Article, Thread

Please do not try to excise dative syntax for the documentation.

We certainly shall not be doing so in the 4th edition of the Camel.

In 3E, the section on method invocation had these subsections,
which I've numbered here for your convenience:

    Method Invocation

      1. Method Invocation Using the Arrow Operator
	  INVOCANT->METHOD(LIST)
	  INVOCANT->METHOD

      2. Method Invocation Using Indirect Objects
	  METHOD INVOCANT (LIST)
	  METHOD INVOCANT LIST
	  METHOD INVOCANT

      3. Syntactic Snafus with Indirect Objects
	  move $party->{LEADER};               # probably wrong!
	  move $riders[$i];                    # probably wrong!

      4. Package-Quoted Classes
	  $obj = method CLASS::;   # forced to be "CLASS"->method

I had initially wanted to get rid of the whole kit&kaboodle, but
Larry talked me out of it, pointing out that if you follow normal
practices, you won't be getting yourself into trouble.  The only one
that bothers me is that you can't easily chain them.  Apart from
that, I kinda like putting my commands before their objects.

Plus I don't have to figure out whether to use vocative or ablative. :)

    [vocative] O Object: do this thing!

    [ablative] With this Object, do this thing.

I'm used to putting the command first in any imperative programming
language, or in English imperative statements.  I'm a verb?first
commander who finds that

    Take charge of your own affairs.
    Get thee to a nunnery.
    Go to hell.

    say STDERR "You are expected to enjoy this."
    @nums = sort { $b <=> $a } @nums;
    consider $him "méfiant";

all read better in English than do these reformulations:

    Of your own affairs take charge.
    To a nunnery get thee.
    To hell go.

    STDERR->say("You are expected to enjoy this.");
    @nums = { $b <=> $a }->sort(@nums);
    $him->consider("méfiant");

See what I mean?  It screws things up because it demotes the most
important element from its natural position right at the front,
essentially leaving us nonsense the likes of

   "In its interior the part of a sentence
    that's important hide should not one!"

That's how this stuff all reads to me. In other words, pretending
that our (S)VO language is an OV one makes it gratuitously hard to read.

Must we really do that?  This shouldn't be Latin, dangnabbit!

Programming is tough enough; don't go putting pretending the object is
more important than the verb, for goodness' sake!  Verbs verbs verbs.

  [
    Just *what* kind of object that is, I leave to your own meditation.

    Grammatically, it is seldom a direct object in the accusative case. It is
    more apt to be something like an indirect object in the dative case, but
    many times it is actually a "dative of interest", which is something of a
    vestige of Latin's ablative case or the old PIE intrumental case, neither of
    which is familiar to most modern readers of English who couldn't tell a
    dative from an accusative if it threw them out the window a new curve ball.

    A Spaniard for example doesn't drop a plate on the floor; rather,
    the plate lets itself fall off the table to the floor on him, where
    the him part there is something of a dative of interest.  "Se me
    cayó el plato" is literally "The plate fell itself on/to/at/from
    me."  It's just like how we in English are wont to say "It broke
    on me" instead of saying "I broke it". Guilt avoidance, eh. :)
  ]

It's bad enough that we go swapping around VO into OV, but it's still worse
that we turn VID into IVD (where I=indirect object and D=direct object)
Nobody talks that way in English, which makes our programs read *really*
poorly: it makes me feel like I'm Latin or German reading.  It's just
plain unnatural.

I feel the perils of using a Perl object or class to be overstated.
Yes, they *can* happen if you do things in an abnormal fashion,
but using the infix arrow does not solve those problems!

*That's* why you always need to use package-quoting notation for your class
names, even *with* an infix arrow.  Nobody but me does this, alas.  But
because I do, the following has absolutely no ambiguities in it:

    my $collator = new Unicode::Collate::
                   level           => 1,
                   normalization   => undef,
                ## variable        => "non-ignorable",
                               ### pick from these 4 possible values:
                               ###    "non-ignorable",
                               ###    "blanked",
                               ###    "shifted",
                               ###    "shift-trimmed",
                 ;

I rather like the way it looks declarative with the double?colon postfix
string?quoter there.  I'm big on vertical alignment, in case you hadn't
noticed this in my various code snipulets yet.  I find it guides the eye
and thence the mind to a quicker and more accurate understanding.  There
are a lot such examples in my Unicode scripts examples programs I include
with my OSCON talks, although admittedly some of those are of venerable
vintage and are not so homogeneous in style as I'd've preferred.

Using that collator, when I wrote this:

    next unless my @hits = gmatch $collator $name => $search;

there is still zero ambiguity.  And it puts the "globally match"
verb in sentence-initial position where it belongs in English
imperatives.  It's also less punctuation/symbol intensive.

So what's the gripe, anyway?  SHEESH!

The most infamous corner case is stuff like:

    new   Something;
    clone $whatever;

where clone or new is already an in?scope sub by the same name as some
clone or new method belonging to $whatever.  The problem disappears if
you provide arguments:

    clone $whatever accuracy => "98%";

Also, anybody who isn't package-quoting is playing with fire, 
and the arrow-thingie won't help you!

Plus what you're all calling "indirect object syntax" is wrong anyway.

Here in all cases we have an identifier that represents an object directly:

    print STDERR "my stuff here\n";      # not an indirect object
    STDERR->print("my stuff here\n");    # not an indirect object
    STDERR->$meth("my stuff here\n");    # direct object w/indirect method

Those are both direct uses of an object, and the arrow is immaterial to
that.  It's the dollar sign that matters.  That's why in the final case,
it's the method that's indirected, not the object, which is still not an
indirect one.

Here again in all cases we have an identifier that represents an
object indirectly:

    print $fh "my stuff here\n";        # this is an indirect object
    $fh->print("my stuff here\n");      # this is an indirect object
    $fh->$meth("my stuff here\n");      # indirect object w/indirect method

Again, those are indirect uses of the object, again irrespective of the bloody arrow.

Which is why I'm changing the documentation to stop lying about 
what is and what is not an "indirect" object.  Whether you get 
"dative syntax" instead is uncertain.  If I get irritated it might
just be instrumentive or something.

Another pet peeve is dying the death this release.  Yes, that's right:
"lexical filehandles" are also getting the axe, because they're a lie.
*THIS* is a lexical filehandle:

    my $fh = \*STDERR;

And this is *not* a lexical filehandle:

    open($main::FAIL = undef, "> /dev/null");

I'll let you see what they really get called when the book sees 
print.  But at least it won't be lying the way so much of the 
documentation does.  Say what you mean and mean what you say. 

Bedtime.

--tom

=============================
Camel3 Excerpt on This Matter
=============================

Here follows the pod2texted section that talks about putting the
command-method before the object.  This is expected to be updated,
but not deleted.

Method Invocation

    If you were to boil down all of object-oriented programming into one
    quintessential notion, it would be *abstraction*. It's the single
    underlying theme you'll find running through all those 10-dollar words
    that OO enthusiasts like to bandy about, like polymorphism and
    inheritance and encapsulation. We believe in those fancy words, but
    we'll address them from the practical viewpoint of what it means to
    invoke methods. Methods lie at the heart of the object system because
    they provide the abstraction layer needed to implement all these fancy
    terms. Instead of directly accessing a piece of data sitting in an
    object, you invoke an instance method. Instead of directly calling a
    subroutine in some package, you invoke a class method. By interposing
    this level of indirection between class use and class implementation,
    the program designer remains free to tinker with the internal workings
    of the class, with little risk of breaking programs that use it.

    Perl supports two different syntactic forms for invoking methods. One
    uses a familiar style you've already seen elsewhere in Perl, and the
    second is a form you may recognize from other programming languages. No
    matter which form of method invocation is used, the subroutine
    constituting the method is always passed an extra initial argument. If a
    class is used to invoke the method, that argument will be the name of
    the class. If an object is used to invoke the method, that argument will
    be the reference to the object. Whichever it is, we'll call it the
    method's *invocant*. For a class method, the invocant is the name of a
    package. For an instance method, the invocant is a reference that
    specifies an object.

    In other words, the invocant is whatever the method was invoked *with*.
    Some OO literature calls this the method's *agent* or its *actor*.
    Grammatically, the invocant is neither the subject of the action nor the
    receiver of that action. It's more like an indirect object, the
    beneficiary on whose behalf the action is performed--just like the word
    "me" in the command, "Forge me a sword!" Semantically, you can think of
    the invocant as either an invoker or an invokee, whichever fits better
    into your mental apparatus. We're not going to tell you how to think.
    (Well, not about that.)

    Most methods are invoked explicitly, but methods may also be invoked
    implicitly when triggered by object destructors, overloaded operators,
    or tied variables. Properly speaking, these are not regular subroutine
    calls, but rather method invocations automatically triggered by Perl on
    behalf of an object. Destructors are described later in this chapter,
    overloading is described in CHP-13, and ties are described in CHP-14.

    One difference between methods and regular subroutines is when their
    packages are resolved--that is, how early (or late) Perl decides which
    code should be executed for the method or subroutine. A subroutine's
    package is resolved during compilation, before your program begins to
    run.More precisely, the subroutine call is resolved down to a particular
    typeglob, a reference to which is stuffed into the compiled opcode tree.
    The meaning of that typeglob is negotiable even at run time--this is how
    "AUTOLOAD" can autoload a subroutine for you. Normally, however, the
    meaning of the typeglob is also resolved at compile time by the
    definition of an appropriately named subroutine. In contrast, a method's
    package isn't resolved until it is actually invoked. (Prototypes are
    checked at compile time, which is why regular subroutines can use them
    but methods can't.)

    The reason a method's package can't be resolved earlier is relatively
    straightforward: the package is determined by the class of the invocant,
    and the invocant isn't known until the method is actually invoked. At
    the heart of OO is this simple chain of logic: once the invocant is
    known, the invocant's class is known, and once the class is known, the
    class's inheritance is known, and once the class's inheritance is known,
    the actual subroutine to call is known.

    The logic of abstraction comes at a price. Because of the late
    resolution of methods, an object-oriented solution in Perl is likely to
    run slower than the corresponding non-OO solution. For some of the
    fancier techniques described later, it could be a *lot* slower. However,
    many common problems are solved not by working faster, but by working
    smarter. That's where OO shines.

  Method Invocation Using the Arrow Operator

    We mentioned that there are two styles of method invocation. The first
    style for invoking a method looks like this:

      INVOCANT->METHOD(LIST)
      INVOCANT->METHOD

    For obvious reasons, this style is usually called the arrow form of
    invocation. (Do not confuse "->" with "=>", the "double-barreled" arrow
    used as a fancy comma.) Parentheses are required if there are any
    arguments. When executed, the invocation first locates the subroutine
    determined jointly by the class of the INVOCANT and the METHOD name, and
    then calls that subroutine, passing INVOCANT as its first argument.

    When INVOCANT is a reference, we say that METHOD is invoked as an
    instance method, and when INVOCANT is a package name, we say that METHOD
    is invoked as a class method. There really is no difference between the
    two, other than that the package name is more obviously associated with
    the class itself than with the objects of the class. You'll have to take
    our word for it that the objects also know their class. We'll tell you
    in a bit how

    to associate an object with a class name, but you can use objects
    without knowing that.

    For example, to construct an object using the class method "summon" and
    then invoke the instance method "speak" on the resulting object, you
    might say this:

      $mage = Wizard->summon("Gandalf");  # class method
      $mage->speak("friend");             # instance method

    The "summon" and "speak" methods are defined by the "Wizard" class--or
    one of the classes from which it inherits. But you shouldn't worry about
    that. Do not meddle in the affairs of Wizards.

    Since the arrow operator is left associative (see CHP-3), you can even
    combine the two statements into one:

      Wizard->summon("Gandalf")->speak("friend");

    Sometimes you want to invoke a method without knowing its name ahead of
    time. You can use the arrow form of method invocation and replace the
    method name with a simple scalar variable:

      $method = "summon";
      $mage = Wizard->$method("Gandalf");  # Invoke Wizard->summon

      $travel = $companion eq "Shadowfax" ? "ride" : "walk";
      $mage->$travel("seven leagues");     # Invoke $mage->ride or $mage->walk

    Although you're using the name of the method to invoke it indirectly,
    this usage is not forbidden by "use strict 'refs'", since *all* method
    calls are in fact looked up symbolically at the time they're resolved.

    In our example, we stored the name of a subroutine in $travel, but you
    could also store a subroutine reference. This bypasses the method lookup
    algorithm, but sometimes that's exactly what you want to do. See both
    the section "Private Methods" and the discussion of the "can" method in
    the section "UNIVERSAL: The Ultimate Ancestor Class". To create a
    reference to a particular method being called on a particular instance,
    see the section "Closures" in CHP-8.

  Method Invocation Using Indirect Objects

    The second style of method invocation looks like this:

      METHOD INVOCANT (LIST)
      METHOD INVOCANT LIST
      METHOD INVOCANT

    The parentheses around LIST are optional; if omitted, the method acts as
    a list operator. So you can have statements like the following, all of
    which use this style of method call:

      $mage = summon Wizard "Gandalf";
      $nemesis = summon Balrog home => "Moria", weapon => "whip";
      move $nemesis "bridge";
      speak $mage "You cannot pass";
      break $staff;               # safer to use: break $staff ();

    The list operator syntax should be familiar to you; it's the same style
    used for passing filehandles to "print" or "printf":

      print STDERR "help!!!\n";

    It's also similar to English sentences like "Give Gollum the
    Preciousss", so we call it the *indirect object* form. The invocant is
    expected in the *indirect object slot*. When you read about passing a
    built-in function like "system" or "exec" something in its "indirect
    object slot", this means that you're supplying this extra, comma-less
    argument in the same place you would when you invoke a method using the
    indirect object syntax.

    The indirect object form even permits you to specify the INVOCANT as a
    BLOCK that evaluates to an object (reference) or class (package). This
    lets you combine those two invocations into one statement this way:

      speak { summon Wizard "Gandalf" } "friend";

  Syntactic Snafus with Indirect Objects

    One syntax will often be more readable than the other. The indirect
    object syntax is less cluttered, but suffers from several forms of
    syntactic ambiguity. The first is that the LIST part of an indirect
    object invocation is parsed the same as any other list operator. Thus,
    the parentheses of:

      enchant $sword ($pips + 2) * $cost;

    are assumed to surround all the arguments, regardless of what comes
    afterward. It would therefore be equivalent to this:

      ($sword->enchant($pips + 2)) * $cost;

    That's unlikely to do what you want: "enchant" is only being called with
    "$pips + 2", and the method's return value is then multiplied by $cost.
    As with other list operators, you must also be careful of the precedence
    of "&&" and "||" versus "and" and "or".

    For example, this:

      name $sword $oldname || "Glamdring";   # can't use "or" here!

    becomes the intended:

      $sword->name($oldname || "Glamdring");

    but this:

      speak $mage "friend" && enter();        # should've been "and" here!

    becomes the dubious:

      $mage->speak("friend" && enter());

    which could be fixed by rewriting into one of these equivalent forms:

      enter() if $mage->speak("friend");
      $mage->speak("friend") && enter();
      speak $mage "friend" and enter();

    The second syntactic infelicity of the indirect object form is that its
    INVOCANT is limited to a name, an unsubscripted scalar variable, or a
    block.Attentive readers will recall that this is precisely the same list
    of syntactic items that are allowed after a funny character to indicate
    a variable dereference--for example, @ary, @$aryref, or "@{$aryref}". As
    soon as the parser sees one of these, it has its INVOCANT, so it starts
    looking for its LIST. So these invocations:

      move $party->{LEADER};               # probably wrong!
      move $riders[$i];                    # probably wrong!

    actually parse as these:

      $party->move->{LEADER};
      $riders->move([$i]);

    rather than what you probably wanted:

      $party->{LEADER}->move;
      $riders[$i]->move;

    The parser only looks a little ways ahead to find the invocant for an
    indirect object, not even as far as it would look for a unary operator.
    This oddity does not arise with the first notation, so you might wish to
    stick with the arrow as your weapon of choice.

    Even English has a similar issue here. Think about the command, "Throw
    your cat out the window a toy mouse to play with." If you parse that
    sentence too quickly, you'll end up throwing the cat, not the mouse
    (unless you notice that the cat is already out the window). Like Perl,
    English has two different syntaxes for expressing the agent: "Throw your
    cat the mouse" and "Throw the mouse to your cat." Sometimes the longer
    form is clearer and more natural, and sometimes the shorter one is. At
    least in Perl, you're required to use braces around any complicated
    indirect object.

  Package-Quoted Classes

    The final syntactic ambiguity with the indirect object style of method
    invocation is that it may not be parsed as a method call at all, because
    the current package may have a subroutine of the same name as the
    method. When using a class method with a literal package name as the
    invocant, there is a way to resolve this ambiguity while still keeping
    the indirect object syntax: package-quote the classname by appending a
    double colon to it.

      $obj = method CLASS::;   # forced to be "CLASS"->method

    This is important because the commonly seen notation:

      $obj = new CLASS;        # might not parse as method

    will not always behave properly if the current package has a subroutine
    named "new" or CLASS. Even if you studiously use the arrow form instead
    of the indirect object form to invoke methods, this can, on rare
    occasion, still be a problem. At the cost of extra punctuation noise,
    the CLASS"::" notation guarantees how Perl will parse your method
    invocation. The first two examples below do not always parse the same
    way, but the second two do:

      $obj = new ElvenRing;               # could be new("ElvenRing")
                                          # or even new(ElvenRing())
      $obj = ElvenRing->new;              # could be ElvenRing()->new()

      $obj = new ElvenRing::;             # always "ElvenRing"->new()
      $obj = ElvenRing::->new;            # always "ElvenRing"->new()

    This package-quoting notation can be made prettier with some creative
    alignment:

      $obj = new ElvenRing::
                  name    => "Narya",
                  owner   => "Gandalf",
                  domain  => "fire",
                  stone   => "ruby";

    Still, you may say, "Oh, ugh!" at that double colon, so we'll tell you
    that you can almost always get away with a bare class name, provided two
    things are true. First, there is no subroutine of the same name as the
    class. (If you follow the convention that subroutine names like "new"
    start lowercase and class names like "ElvenRing" start uppercase, this
    is never a problem.) Second, the class has been loaded with one of:

      use ElvenRing;
      require ElvenRing;

    Either of these declarations ensures that Perl knows "ElvenRing" is a
    module name, which forces any bare name like "new" before the class name
    "ElvenRing" to be interpreted as a method call, even if you happen to
    have declared a "new" subroutine of your own in the current package.
    People don't generally get into trouble with indirect objects unless
    they start cramming multiple classes into the same file, in which case
    Perl might not know that a particular package name was supposed to be a
    class name. People who name subroutines with names that look like
    "ModuleNames" also come to grief eventually.




(Log in to post comments)

Re: indirect object syntax (was Re: Revising the OO docs - take 2)

Posted Jul 14, 2011 21:19 UTC (Thu) by elanthis (guest, #6227) [Link]

Linguists shouldn't pretend be competent designers of computer languages. Dear god.

Sure, "verb noun" reads better in English. Who gives a shit? We're not writing novels and prose, we're writing math and algorithms.

noun->verb allows for significantly more powerful programming tools, e.g. object introspection and code completion inside of an editor. The problems I run into while writing code are not "does this sound good in English?" but more "I'm working on some code I haven't seen before, what the hell is this parameter's object for and what does it do?"

When your code puts the verb as the primary element like an imperative language does you can easily look at a verb but that's it. Without overloading, a single verb works on only a single specific form of object, which makes it even more worthless. Looking at all verbs that affect a particular object is much harder (especially when the verbs are used in a dynamic language where the verbs' parameters aren't tagged with object types).

Even if the verb-first is just syntactical "sugar" for noun->verb style of syntaxes -- that is, the verbs are still attached directly to the objects in the code structure -- putting the verb first just kills code completion. I type out a verb and then my editor can't do anything more to help me, other than maybe list out parameters. With the noun first, the editor can helpfully then show me all the possible verbs on that noun. Can't remember the exact spelling of the verb, or are you not entirely sure if the verb you want already exists? You better damn well hope your language puts the noun first. Otherwise, you better not mind wasting oodles of time and effort grepping source code and reading through (likely completely deficient) code documentation.

Re: indirect object syntax (was Re: Revising the OO docs - take 2)

Posted Jul 14, 2011 23:10 UTC (Thu) by nix (subscriber, #2304) [Link]

Linguists shouldn't pretend be competent designers of computer languages. Dear god.
We could call this the Perl Lesson.

Re: indirect object syntax (was Re: Revising the OO docs - take 2)

Posted Jul 15, 2011 8:17 UTC (Fri) by janneke (subscriber, #15012) [Link]

Linguists shouldn't pretend be competent designers of computer languages. Dear god.
We could call this the Perl Lesson.
With someone creating Perl [or php ftm] not anything to do I don't think, the lesson has. Neither at what someone does claiming to be an authority, it has. At persuading many people to choose one of their languages, the real computer language designers apparently failed.

Re: indirect object syntax (was Re: Revising the OO docs - take 2)

Posted Jul 16, 2011 11:54 UTC (Sat) by jengelh (subscriber, #33263) [Link]

>Linguists shouldn't pretend be competent designers of computer languages.

Neither should computer language designers pretend to know all linguistics, or having a narrow view that just encompasses indoeuropean languages. For one, Japanese is basically /V$/ and would match the OV style that is being criticized in the mail. (Of course my interjection does not consider the argument list yet.. that does go against /V$/)

Copyright © 2011, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds