Improved autobox-ing. I'm loving it :o)

print (0..9)->grep { $_ > 5 }
            ->map  { $_ * 2 }
            ->join(' - ');

 # prints: 12 - 14 - 16 - 18

Isn’t that nice ? It is now possible with

use autobox::Core;    
use PerlX::MethodCallWithBlock;

autobox::Core enables to call methods on Perl native types (scalars, lists, hashes, etc). It has the advantage that you can call functions like grep() and map() in the order in which they are executed. But it has the annoying requirement of passing coderefs to map() and grep(), which makes them look ugly:

use autobox::Core;

print (0..9)->grep( sub { $_ > 5 } )
            ->map(  sub { $_ * 2 } )
            ->joint(' - ');

PerlX::MethodCallWithBlock enables to pass blocks instead of coderefs, as arguments to functions. It’s magic! And it solves autobox’s biggest syntax hindrance (in my opinion).

I love these two modules. I will propose both authors to document the fact that they work well together.

~ ~ ~

Also, autobox::Core enables you to very easily create your own methods for Perl’s native types. For example in this article about “checking that a list contains”, i use it to create a contains() list-method :

do_something if @ingredients->contains('flour');

Now, i’m imagine that people will come up with ideas of methods for List, Hashes, Scalars, etc, that would take a block as an argument.

There’s probably a lot to copy from Ruby, who uses block-passing a lot. I’ve read an example in a book, i’ll share it here if i manage to make it work.

22 Comments

I'm a big fan of PerlX::MethodCallWithBlock. I'd love to see it become part of standard Perl syntax. Another module it works very nicely with is XML::LibXML::NodeList.

Ick. I've never felt the appeal of autobox. If you want to write things in that order, Ruby already exists, and it even lets you use "." instead of "->". It's cool that Perl can be contorted to do this, but I'm glad I never see it used in real code.

I'm with educated_foo. There may be times when autobox is useful, but this sure doesn't look like one of them :-(.

You'd probably like the Perl 6 solution:

say (^10).grep(* > 5).map(* * 2).join(' - ');

However, I'd put all of these options into the category of fun and elegant code that I wouldn't release to production.

Fortunately, TIMTOWTDI!

say (0..9).grep({ $_ > 5 }).map({ $_ * 2 }).join(' - ');

I'm sort of on the educated_foo side here. Terseness is a good attribute for code, but not at the expense of readability or exposure of business rules IMHO. This (exposure of business rules) is why I am not allergic to 'unless'. Sometimes that's the best way to expose the business rule.

I'm a believer in clarity first. So if terseness and succinctness support that goal, then great, however I think some folks like to push Perl syntax to the edge of readability for the sole purpose of terseness (and perhaps to show off a bit).

The beard test. "If the guy in the next cubicle scratches his beard when he looks at it, recode it." ;-)

Plenty of people would call the “clear and obvious” Perl 5 solution cryptic. You call the Perl 6 solution cryptic. They are wrong and so are you.

I hired into my job a smart friend who had mostly done Java up to that point, and let him loose on a very complicated piece of Perl code I didn’t have the time to finish nor explain. He had to figure it out almost entirely by himself. He said at first he thought my copious use of map and grep cryptic and obfuscatory, so he rewrote the code with loops in order to understand it. But little by little he got comfortable with the more functional approach, still trying to understand what I had been doing, and started using them himself. By the end he had written much of the code back to how it was.

He started out finding map and grep cryptic and ended up finding them clarifying the code.

Intuition is a matter of recognition.

There are some ineffable measures of obviousness and it’s not all just subjective – but it’s far less straightforward than judging something by whether it looks obvious to you. The real question is how learnable it is, and after being learned, how effortlessly it can be read without slowing down to think.

I’m not closely familiar with Perl 6, but on that count it looks pretty good to me from afar.

although i agree that we can learn new syntax quite fast, i still think that it is harder for a programmer to have to use a "different Perl language" for every new project they are involved in ("because now there's a better syntax").

I’m confused… you are making this argument in the comments of an entry in which you advocate the use of autobox + PerlX::MethodCallWithBlock.

I hadn't criticized the syntax, but I do have an example of something I'm not fond of and a reason why. I find the use of chained method calls without parens to be jarring.

(0..9)->grep { $_ > 5 } ->map { $_ * 2 } ->join(' - ')

This is, of course, all a matter of personal taste and I could get used to it, but it's not the way that Perl or most languages work. I can think of an exception: LiveScript; although I'm sure there are more.

$ 'h1' .on 'click'
  alert 'boom!'

Which compiles to JavaScript:

$('h1').on('click', function(){
  alert('boom!');
});

Note that LiveScript only uses functional versions of map and filter (think grep), so it doesn't have a corresponding OO example.

map (* 2), filter (> 5), [0 to 9]

I would strongly prefer your example if it used parens like so:

(0..9)->grep({ $_ > 5 })->map({ $_ * 2 })->join(' - ')

Even though you can't natively do that in Perl 5 (or Perl 6 unless you swap the method call operator -> with .), it still seems Perlish to me, unlike the original example. This is all subjective though!

You said that “(^10) is not as self-explanatory”.

It’s not without understanding the fundamentals of Perl 6, like the operators, which are needed in order to program in the language or understand the code. Just like Perl 5 or Ruby has their own prerequisite fundamentals.

$a  ..  $b  # $a (inclusive) through $b (inclusive)
$a  ..^ $b  # $a (inclusive) through $b (exclusive)
$a ^..  $b  # $a (exclusive) through $b (inclusive)
$a ^..^ $b  # $a (exclusive) through $b (exclusive)
       ^$b  # 0  (inclusive) through $b (exclusive)

Which makes these examples all equivalent:

0..9
0..^10
^10

This ultimately makes for cleaner code when you’re not constantly doing things like $a..$b-1 but requires knowledge of operators unavailable to most languages, just like Perl 5 has operators like =~ and x which aren’t generally known to programmers of other languages.

As a point of information, you mentioned Method::Signatures for code that doesn't need speed optimizing implying that the signatures would slow things down.

Method::Signatures has little overhead itself. With the exception of the type checks, all code is inlined. The required argument checks are little more than things like "@_ > 2". Code like "method foo($this, $that)" is essentially free.

Type checks have the cost of type checks. Mouse type checks for the basic types are exceptionally fast and basically free. Moose type checks are another matter. We're working on making Method::Signatures smarter about whether it uses Moose or Mouse types. We're also working on a "make go fast" switch to disable checks in production.

What is slow is MooseX::Method::Signatures. At last benchmark even a method which takes no arguments is something like 5000 times slower than a normal method call.

Leave a comment

About mascip

user-pic Perl, Javascript (AngularJS), Coffeescript, Firebase