Beefy Boxes and Bandwidth Generously Provided by pair Networks vroom
Perl-Sensitive Sunglasses
 
PerlMonks  

RFC: Simulating Python's @decorators in Perl with Attributes

by LanX (Abbot)
on May 28, 2013 at 16:52 UTC ( #1035650=perlmeditation: print w/ replies, xml ) Need Help??

A new episode in the series: "Yes, Perl can do!"

I recently had a discussion with a fellow perlmonger about a 3h video¹ about meta-programming in Python thanks to so called decorators. (something like advice in LISP)

So my reply was

Great, but what exactly are the benefits over attributes and type-glob manipulation in Perl?

His long reply can be summarized with something like

  • Attributes are very difficult to handle
  • "Nobody" understands them
  • no good tutorials
  • In Python decorators are first class objects with very short elegant code
  • glob manipulations in Perl are cryptic
  • Attributes are rarely used in Perl
  • While typical use cases in Python are
    • memoization,
    • debugging,
    • logging
    • CLI wrapping,
    • route-wrapper for web-applications
    • ...

So I accepted the challenge to prove him wrong:

Taking this (rather stupid) example from SO:

def makebold(fn): def wrapped(): return "<b>" + fn() + "</b>" return wrapped def makeitalic(fn): def wrapped(): return "<i>" + fn() + "</i>" return wrapped @makebold @makeitalic def hello(): return "hello world" print hello() ## returns <b><i>hello world</i></b>

I hacked this code

use strict; use warnings; use Data::Dumper qw'Dumper'; use Attribute::Handlers; use feature 'say'; sub wrap { my ($glob,$c_wrapper) = @_; no warnings 'redefine'; *$glob = $c_wrapper; } sub BOLD :ATTR { my ($pkg,$glob,$ref) = @_; wrap $glob => sub { "<b>" . $ref->() . "</b>" }; } sub ITALIC :ATTR { my ($pkg,$glob,$ref) = @_; wrap $glob => sub { "<i>" . $ref->() . "</i>"}; } sub hello :ITALIC :BOLD { return "hello world"; } say hello(); __END__ <b><i>hello World</i></b>

IMHO the Perl code is already better readable and can even be further improved with more syntactic sugar.

Not every decorator really needs to wrap code, abstracting it into a function wrap from an imported module seems reasonable (Python-folks always return the identical function-ref to handle this)

My friend was impressed, but maybe he wasn't critical enough.

Here my questions :

  • Did I miss something?
  • Does Attribute::Handlers have traps to be avoided?
  • Why do we ignore attributes?
  • Why don't we adapt (i.e. steal) good use cases from Python or LISP

Actually I wanted to meditate more about the issue and refine the code before posting, but I'm quite busy at the moment and the risk to forget this task in a drawer is quite high.

So following the release often paradigm I just posted my raw results...

At least I hope you have now good arguments, if someone claims Python was superior because of decorators!

Cheers Rolf

( addicted to the Perl Programming Language)

¹) OMG 8-|

Comment on RFC: Simulating Python's @decorators in Perl with Attributes
Select or Download Code
Re: RFC: Simulating Python's @decorators in Perl with Attributes
by tobyink (Abbot) on May 28, 2013 at 17:28 UTC

    MooseX::ModifyTaggedMethods (disclaimer: I wrote it) provides a little sugar if you happen to be using Moose...

    #!/usr/bin/env perl use v5.14; package MyClass { use Moose; use MooseX::ModifyTaggedMethods; use Sub::Talisman qw( Bold Italic ); around methods_tagged("Bold") => sub { my ($orig, $self, @params) = @_; my $string = $self->$orig(@params); return "<b>$string</b>"; }; around methods_tagged("Italic") => sub { my ($orig, $self, @params) = @_; my $string = $self->$orig(@params); return "<i>$string</i>"; }; sub greeting :Bold :Italic { return "Hello World"; } } say MyClass->greeting;
    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: RFC: Simulating Python's @decorators in Perl with Attributes
by pokki (Scribe) on May 28, 2013 at 19:14 UTC

    Without introducing sub attributes into the mix (I have nothing against attributes myself, but they're uncommon enough that non-Monks may trip on them), I find that Class::Method::Modifiers does a good enough job of providing the same functionality as decorators without extra syntax. Moo exposes C::M::M directly while Moose reimplements it (unless I'm mistaken?). (Edit: tobyink points out that I am in fact mistaken, Moose got there first.)

    use strict; use warnings; use 5.010; use Test::More; use Class::Method::Modifiers; sub makebold { my ($orig, @args) = @_; return '<b>'.$orig->(@args).'</b>'; } sub hello { return "ohai"; } around 'hello' => \&makebold; is(hello(), '<b>ohai</b>'); done_testing;
      I think the essential point about attributes is to DRY when marking subs and variables.

      so this code might be explicit but repeats the subname hello as a string.

      around 'hello' => \&makebold;

      Please note that attributes are not necessarily wrapping code.

      One use-case for instance could be to set a breakpoint for the debugger

      sub tst :break { ... }

      Another to restrict the type to variables

      my $y :Int;

      which might be very helpful when trying to translate Perl code to a typed language like C.

      All use cases could be achieved by adding tst or $y to a hash somewhere, but the possibility to do it right at the point of interest w/o name repetition can be very handy and avoids headaches when refactoring.

      Just imagine the overhead for a refactoring tool to find all hellos after sub hello was renamed.

      Cheers Rolf

      ( addicted to the Perl Programming Language)

      update

      I'm no expert of Moose's compile phases, but a DRY notation of around using attributes would look like

      sub hello :around(makebold) { return "ohai"; }

        All those other uses for attributes are useful and interesting, but I thought we were talking about method decoration?

        Decorators as code rather than syntax also allow you to decorate someone else's methods, or to compose some advice into some class through roles, even at runtime. This is not possible with attributes.

      "Moo exposes C::M::M directly while Moose reimplements it (unless I'm mistaken?)."

      You've got it the wrong way around: Class::Method::Modifiers is the re-implementation; Moose came before it.

      Class::Trigger and Aspect are a fairly similar idea, though different interfaces.

      package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: RFC: Simulating Python's @decorators in Perl with Attributes
by Anonymous Monk on May 28, 2013 at 19:16 UTC

    Why do we ignore attributes?

    Can you explain why you do it?

    Why don't we adapt (i.e. steal) good use cases from Python or LISP

    Can you explain why you do it? How many of those good use cases from Python or LISP have you identified? And made sure they don't exist in Moo/se?

    bah me no grok rhetoricalism

      > > Why do we ignore attributes?

      > Can you explain why you do it?

      Not many modules which provide them and I'm surely influenced by the monastery mainstream.

      And yes the API of attributes is very "rich".

      > bah me no grok rhetoricalism

      Ja nje panimaju!

      Cheers Rolf

      ( addicted to the Perl Programming Language)

Re: RFC: Simulating Python's @decorators in Perl with Attributes
by Arunbear (Parson) on Jun 01, 2013 at 11:25 UTC
    I wrote a module that used attributes to make sorted hashes. But to get syntax like
    my %capital : Sorted;
    the 'Sorted' sub (that provides the sorting behaviour) had to be pushed into the UNIVERSAL package, so if someone else wanted to provide a 'Sorted' attribute there would be a name collision.

    The name collision could be avoided by using a non UNIVERSAL namespace, but then syntax like

    my %capital : Hash::Sorted;
    would be too cumbersome.
      > so if someone else wanted to provide a Sorted attribute there would be a name collision.

      In O'Reilly's Perl Hacks you'll find a similar issue with an attribute Doc for docstrings living in UNIVERSAL.

      Maybe we just need a module to safely add features to UNIVERSAL and warn about conflicts.

      > my %capital : Hash::Sorted;

      > would be too cumbersome.

      really?

      Maybe just leave it to the user. An import-option could specify the namespace.

      I wouldn't mind having different modules collecting their attributes into X:: like "eXtension".

      Why not X::Sorted or X::Doc ... or _::Sorted or _::Doc? :)

      BTW: Happy to find other's trying to use syntactic sugar to extend the language! =)

      Cheers Rolf

      ( addicted to the Perl Programming Language)

      > ...then syntax like
      my %capital : Hash::Sorted;

      > would be too cumbersome.

      did you ever get this to work? I can't.

      I even tried Perl4's quote as separator...

      code
      use strict; use warnings; use Attribute::Handlers; package H; sub Sorted :ATTR { print "sorted" } package main; my $tst :H::Sorted; sub tst :H::Sorted { print "hui" } tst();
      stderr
      Invalid separator character ':' in attribute list at /home/lanx/B/PL/P +M/uni_attr.pl line 16, near "$tst :H" syntax error at /home/lanx/B/PL/PM/uni_attr.pl line 16, near "$tst :H" Invalid separator character ':' in attribute list at /home/lanx/B/PL/P +M/uni_attr.pl line 18, near "sub tst :H" syntax error at /home/lanx/B/PL/PM/uni_attr.pl line 18, near "sub tst +:H" Execution of /home/lanx/B/PL/PM/uni_attr.pl aborted due to compilation + errors.

      Cheers Rolf

      ( addicted to the Perl Programming Language)

        Sorry, the verbose syntax example I gave was wrong. It would actually be like this:
        #!/usr/bin/perl use strict; use warnings; package Foo::Sorted; use Attribute::Handlers; sub Sorted :ATTR { print "all sorted" } package main; my Foo::Sorted $tst :Sorted;

      Yes, Attribute::Handlers is really badly designed. It uses inheritance in stupid ways and requires things to be put into UNIVERSAL and other nonsense.

      I've implemented attributes by not using it and avoided these problems. I actually have a module that is an alternative to Attribute::Handlers that avoids these problems. It isn't on CPAN yet mostly because dealing with the possibility of somebody else having used Attribute::Handlers is a complex mess (and because this was a means to an end so my motivation was more about the module that used this than about this module).

      - tye        

        Your alternative to Attribute::Handlers sounds very useful. Perhaps the first release could just come with a warning that it may not play well with existing code that uses Attribute::Handlers (If I understood that part correctly)? Or maybe just 'release' it on Github?

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://1035650]
Approved by Corion
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (4)
As of 2014-04-21 01:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (489 votes), past polls