develooper Front page | perl.perl5.porters | Postings from August 2021

Re: Twigils

Thread Previous | Thread Next
Paul "LeoNerd" Evans
August 18, 2021 13:30
Re: Twigils
Message ID:
There are two parts to this argument I haven't seen anyone else make
yet, so here in the hope that this isn't at all a repeat.

Also, both of these points are made by reference to myself having
actually written large amounts of real actual code using Object::Pad.

## Dynamic Method Generation

If twigil syntax exists, then methods that access slots can be
dynamically added to classes by MOP access, by way of a `:unbound`
method attribute. This tells the compiler *not* to resolve slot names
at the time the method body is compiled:

  my $mref = method :unbound {

This "unbound method" is not yet useable, but can now be attached to a
class by a MOP call, and only at that point we'll resolve the slot name
in that class:

  $metaclass->add_slot( '$:aslot' );
  $metaclass->add_method( inc_slot => method :unbound { $:aslot++ } );

If instead, slot variables look identical to regular lexicals, then
code of this form cannot be constructed. There would be no way to write
the anonymous `method {}` reference in the first block of code above,
without it failing to parse at compiletime because of an unrecognised
variable name.

A mechanism does currently exist in Object::Pad to do this kind of
thing, and it is Not Pretty:

  my $metaslot = $metaclass->add_slot( '$aslot' );
  $metaclass->add_method( inc_slot => sub {   # we can't use method
    my $self = shift;                         # because we're not method

It turns what would be a simple efficient OP_SLOTPAD inside the method
body into a closure capture of the $metaslot instance which invokes
an lvalue accessor method on it at runtime, just to fetch the value of
that slot. It works, but it's incredibly inefficient and removes most
of the entire point of having these slot variables feel variable-like
in the first place.

## Surprising Lazy Initialization

One of the commonly-requested features from Moo(se?) users is "does it
support lazy builders?". I have been hesitant to add these to
Object::Pad itself for the reasons I explain below, but because people
kept asking about it, I created a mechanism to allow 3rd-party
attributes, and then created this:

This kind of thing is subtle to use in practice, because "slots look
like lexicals". With Moo(se?), all instance data is accessed via
accessor methods, and people are culturally used to the fact that
methods can run code. They might print spurious warnings, or they might
fail outright:

  use Moo;
  package OrangePeeler;
  has orange => ( is => 'lazy' );
  sub _build_orange {
    die "Not on a Tuesday, no" if (localtime)[6] == 2;
    warn "Maybe, maybe not" if rand > 0.5;
    return Orange->new;

  sub peel {
    my $self = shift;

    # might die or warn but that's OK; we expect that
    my $orange = $self->orange;
    say "Peeling $orange...";

Whereas, if all your slot variables look just like variables, then this
might be considered surprising:

  use Object::Pad;
  class OrangePeeler;

  has $orange :LazyInit(_make_orange);
  method _make_orange {
    die "Not on a Tuesday, no" if (localtime)[6] == 2;
    warn "Maybe, maybe not" if rand > 0.5;
    return Orange->new;

  method peel {
    # This variable interpolation itself might die or warn
    say "Peeling $orange...";

By using a twigil and making slots "look different to variables", we
can disarm this false feeling of safety, by reminding the programmer
that these aren't just boring lexical variables, and that fun things
may still happen:

  method peel {
    # This interpolation might die, but that's OK because it looks weird
    say "Peeling $:orange...";

Paul "LeoNerd" Evans      |  |

Thread Previous | Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About