Skip to content

Instantly share code, notes, and snippets.

@draegtun
Created August 20, 2010 14:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save draegtun/540493 to your computer and use it in GitHub Desktop.
Save draegtun/540493 to your computer and use it in GitHub Desktop.
Custom infix operators in perl5
#!/usr/bin/env perl
# custom infix operators in perl
# see "Infix operators in Python" - http://code.activestate.com/recipes/384122/
# - http://news.ycombinator.com/item?id=1606155
use 5.012;
use warnings;
{
package Infix;
use overload '|' => 'infix';
sub new {
my $class = shift;
bless {
left => undef,
code => $_[0],
}, $class;
}
sub left {
my $self = shift;
bless {
%{ $self },
left => $_[0],
}, ref $self;
}
sub infix {
my ($self, $arg) = @_;
# if right side then evaulate
return $self->{code}->( $self->{left}, $arg ) if $self->{left};
# else store left side of infix op
$self->left( $arg );
}
}
my $x = Infix->new( sub { $_[0] * $_[1] });
say 10 |$x| 3; # => 30
say 2 |$x| 1.5; # => 3;
@kurahaupo
Copy link

kurahaupo commented Apr 13, 2021

Cool idea, but may I suggest replacing $x with a bareword to improve readability:

use constant {
    'Add' => Infix->new( sub{ $_[0] + $_[1] } ),
    'Mul' => Infix->new( sub{ $_[0] * $_[1] } ),
    'Cat' => Infix->new( sub{ $_[0] . $_[1] } ),
};

say 1 |Add| 1;
say 6 |Mul| 9;
say 'x' |Cat| 'y';

I've also investigated using bracket-like operators, but so far the only one that comes close to working as desired is

    say 10 <<MUL>> 3;

This has the advantage that the << and >> are overloaded separately, so no test is needed to know which is which.

(I would have preferred <MUL> but the comparison operators < and > are non-associative.)

Inside the infix sub, $_[2] can be used to determine whether this is a left-side or right-side overloaded operator, which somewhat slightly simplifies the logic.

Using a function rather than a method as the constructor allows the use of a (&) prototype so that sub can be omitted when calling it.

I've golfed it down a bit, and used goto&func to reduce some of the overhead.

{
    package Infix;
    use overload '<<' => 'left';
    use overload '>>' => 'right';
    use overload '|'  => 'infix';
    # func is called as a function, not a method
    sub func(&) { return bless \@_ }
    sub left { return bless [$_[0]->[0], $_[1]] }
    sub right { @_ = (@{$_[0]}, $_[1]); goto &{+shift} }
    sub infix { goto &left if $_[2]; goto &right }
}

use constant Cat => Infix::func { @_ ? '<'.join('|',@_).'>' : '' };

printf "[%s]\n", "abc" <<Cat>> "xyz";
printf "[%s]\n", "ABC" |Cat| "XYZ";

1;

which outputs

[<abc|xyz>]
[<ABC|XYZ>]

Another approach would be to have two classes, one for the operator token, which only has a "left"method, and another for the pending left operand, which only has a "right" method.

Whilst this has been fun, it's still just putting lipstick on a pig; we really need proper operator definitions, like in Raku.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment