Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

FunkOpera: Abstracting Perl Operators

by Zaxo (Archbishop)
on Feb 04, 2005 at 08:56 UTC ( [id://427990]=perlmeditation: print w/replies, xml ) Need Help??

Work on Tie::Constrained has had me slinging around a lot of small anonymous subroutines. Many of those are composed of similar parts, joined by && or ||. I began wanting an easy way to crank out new subs from a small set of old.

In mathematics, operators like addition are defined on functions by their action on the values the functions return. For example, (sin + exp)(x) is sin(x) + exp(x). I got the notion of overloading the && and || ops for a class of blessed subroutines, so that $foo && $bar would return a blessed sub { &$foo && &$bar }.

You may have already noticed what's wrong with that idea. The && and || operators are not overloadable! I think I can understand why, too. The logical operators are used to test for success of a constructor. How could that work if $baz = FunkOpera->new(...) or die $! returned sub { $baz or die $!}?

Well, every idea has its ups and downs. The bitwise operators, & and | are conceptually similar to the logical ones, and they are overloadable. They can stand in for their logical counterparts. That is not ideal. The overloaded operators will be parsed by perl as the bitwise ops. There is no ultra-low precedence form of the bitwise ops. Those shortcomings may force heavier use of grouping parentheses than we're accustomed to. It's not perfect, but it will do.

So here it is, called for the moment FunkOpera.

#!/usr/bin/perl package FunkOpera; use overload '&' => sub { my ($l, $r, $f) = @_; bless $f ? sub {&{$r} && &{$l}} : sub {&{$l} && &{$r}} , __PACKAGE__ }, '|' => sub { my ($l, $r, $f) = @_; bless $f ? sub {&{$r} || &{$l}} : sub {&{$l} || &{$r}} , __PACKAGE__ }, '!' => sub { my ($fn) = @_; bless sub { ! &{$fn}}, __PACKAGE__ }; 1;
package main; my $foo = bless sub { $_[0] =~ /foo/ }, FunkOpera; my $bar = sub {"bar"}; my %funk; @funk{qw/foonbar foorbar barnfoo barrfoo/} = ($foo & $bar, $foo | $bar, $bar & $foo, $bar | $foo); for (keys %funk) { print qq($_("food") returns ), $funk{$_}->('food'}, $/; print qq($_("ford") returns ), $funk{$_}->('ford'}, $/; } __END__ foorbar("food") returns 1 foorbar("ford") returns bar barnfoo("food") returns 1 barnfoo("ford") returns foonbar("food") returns bar foonbar("ford") returns barrfoo("food") returns bar barrfoo("ford") returns bar
The old-timey calling convention with "&" sigil and no parens is intended. Both subs are to be called with the same arguments, so it is convenient to not alter @_ in the base functions and have the arguments brought in automatically.

It is only necessary that one sub in a simple expression is a FunkOpera. The rest is taken care of by overload.pm magic. The odd trinary construction in the overload subs for binary operators helps with that, as well as keeping argument order straight.

This works very well for arithmetic operators, too.

So, it's not perfect, but it does what I wanted.

After Compline,
Zaxo

Replies are listed 'Best First'.
Re: FunkOpera: Abstracting Perl Operators
by chb (Deacon) on Feb 04, 2005 at 09:12 UTC
    I think the reason for && and || not being overloadable is a different one: they do something no normal operator/function-invocation can do: they implement short-circuit logic. Normal invocation will evaluate all arguments before running the actual sub body, the && and || stop as early as possible. You wouldn't want this special calling semantics for your own overloaded code.
Re: FunkOpera: Abstracting Perl Operators
by dragonchild (Archbishop) on Feb 04, 2005 at 14:11 UTC
    First off, this is very cool. I'd figure out a name for this and throw it onto CPAN.

    A few thoughts:

    • The old-timey calling convention with "&" sigil and no parens is intended. Both subs are to be called with the same arguments, so it is convenient to not alter @_ in the base functions and have the arguments brought in automatically.

      This doesn't work as you'd expect for functions that consume parts of @_, particularly with shift. I would make a two shallow copies of @_ and pass one into each of the subroutines. I think that would DWIM better.

    • You have conjunction of return values, disjunction of return values, and negation of return value. I would propose that you add composition - f( g( x ) ) - as well. While the first three are good, I can definitely see a need for the fourth. Unfortunately, I'm not quite sure what operation to overload for this. Maybe the bitshift operators? (f << g)(x) means f ( g(x) )? I dunno ...

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: FunkOpera: Abstracting Perl Operators
by Anonymous Monk on Feb 04, 2005 at 10:50 UTC
    I think I can understand why, too. The logical operators are used to test for success of a constructor. How could that work if $baz = FunkOpera->new(...) or die $! returned sub { $baz or die $!}?
    Well, the same way as $baz = FunkOpera->new(...) + die $! returning sub {$baz + die $!} I guess.

    But the reason || and && aren't overloadable is very different. The reason is that operators aren't the things that carry the overload magic - the operands do. So to know whether an operator acts like it's overloaded, you first need to know the value of all its operands. This is not a problem for most operators, as it needs to know the value of both operands before know what its value will be. But || and && act differently - their left operand is evaluated to find out whether or not to evaluate its right operand.

Re: FunkOpera: Abstracting Perl Operators
by fergal (Chaplain) on Feb 04, 2005 at 18:08 UTC
    Check out Tangram. It uses exactly the same techniques to allow you to write you're SQL where clauses in Perl
    my @simpsons = $storage->select( $person, $person->{name} eq 'Simpson' & $person->{age} > 35 );

    So in this case $person represents a table with a fields "name" and "age". $person->{name} gives you an overloaded object similar to your example.

    It would be cool if you could get together with Sam Villain and produce an independent module to handle this as I've wanted to use it myself in the past.

    It's not immediately obvious on search.cpan.org but the guided tour for Tangram is in a file named tour. Look down to "Filtering" for more examples of this.

Re: FunkOpera: Abstracting Perl Operators
by jdporter (Paladin) on Feb 04, 2005 at 14:20 UTC
    This looks to me a lot like currying and higher-order functions (which will be natively supported in perl6, from what I understand).
Re: FunkOpera: Abstracting Perl Operators
by gaal (Parson) on Feb 05, 2005 at 11:13 UTC
    This is a cool technique. FWIW, Aspect uses it too.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (6)
As of 2024-04-19 22:34 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found