Perl custom syntax for passing function arguments

413 views Asked by At

I have been using perl for some time now. I want to know how I can run the following operation in perl:

subtract(40)(20)

To get the result:

20

I think I would have to look at custom parsing techniques for Perl. This is what I am looking at right now:

Devel::Declare

Devel::CallParser

and http://www.perl.com/pub/2012/10/an-overview-of-lexing-and-parsing.html

Now, I am not sure what to look for or what to do. Any help on HOW to go about this, WHAT to read would be appreciated. Please be clear. Thank you.

5

There are 5 answers

0
tobyink On

I recommend trying Parse::Keyword. Parse::Keyword is really great for parsing custom syntax, as it lets you call back various parts of the Perl parser, such as parse_listexpr, parse_block, parse_fullstmt, etc (see perlapi).

It has a drawback in that if you use those to parse expressions that close over variables, these are handled badly, but this can be worked around with PadWalker.

Parse::Keyword (including PadWalker trickery) is what Kavorka uses; and that does some pretty complex stuff! Early versions of p5-mop-redux used it too.

Anyway, here's a demonstration of how your weird function could be parsed...

use v5.14;
use strict;
use warnings;

# This is the package where we define the functions...
BEGIN {
  package Math::Weird;

  # Set up parsing for the functions
  use Parse::Keyword {
    add      => \&_parser,
    subtract => \&_parser,
    multiply => \&_parser,
    divide   => \&_parser,
  };

  # This package is an exporter of course
  use parent 'Exporter::Tiny';
  our @EXPORT = qw( add subtract multiply divide );

  # We'll need these things from PadWalker
  use PadWalker qw( closed_over set_closed_over peek_my );

  sub add {
    my @numbers = _grab_args(@_);
    my $sum = 0;
    $sum += $_ for @numbers;
    return $sum;
  }

  sub subtract {
    my @numbers = _grab_args(@_);
    my $diff = shift @numbers;
    $diff -= $_ for @numbers;
    return $diff;
  }

  sub multiply {
    my @numbers = _grab_args(@_);
    my $product = 1;
    $product *= $_ for @numbers;
    return $product;
  }

  sub divide {
    my @numbers = _grab_args(@_);
    my $quotient = shift @numbers;
    $quotient /= $_ for @numbers;
    return $quotient;
  }

  sub _parser {
    lex_read_space;

    my @args;
    while (lex_peek eq '(')
    {
      # read "("
      lex_read(1);
      lex_read_space;

      # read a term within the parentheses
      push @args, parse_termexpr;
      lex_read_space;

      # read ")"
      lex_peek eq ')' or die;
      lex_read(1);
      lex_read_space;
    }

    return sub { @args };
  }

  # In an ideal world _grab_args would be implemented like
  # this:
  #
  #    sub _grab_args { map scalar(&$_), @_ }
  #
  # But because of issues with Parse::Keyword, we need
  # something slightly more complex...
  #
  sub _grab_args {
    my $caller_vars = peek_my(2);
    map {
      my $code = $_;
      my $closed_over = closed_over($code);
      $closed_over->{$_} = $caller_vars->{$_} for keys %$closed_over;
      set_closed_over($code, $closed_over);
      scalar $code->();
    } @_;
  }

  # We've defined a package inline. Mark it as loaded, so
  # that we can `use` it below.
  $INC{'Math/Weird.pm'}  = __FILE__;
};

use Math::Weird qw( add subtract multiply );

say add(2)(3);          # says 5
say subtract(40)(20);   # says 20

say multiply( add(2)(3) )( subtract(40)(20) );   # says 100
7
Denis Ibaev On

You may create the source code filter:

package BracketFilter;

use Filter::Util::Call;

sub import {
    filter_add(sub {
        my $status;
        s/\)\(/, /g if ($status = filter_read()) > 0;
        return $status ;
    });
}

1;

And use it:

#!/usr/bin/perl

use BracketFilter;

subtract(40)(20);

sub subtract {
    return $_[0] - $_[1];
}
0
rurban On

@Heartache: Please forget this challenge as it makes no sense for the parser and the user.

You can think of using fn[x][y] or fn{x}{y} which are valid syntax variants - i.e. you can stack [] and {} but not lists, or fn(x,y) or fn(x)->(y) which do look nice, are also valid and meaningful syntax variants. But fn(x)(y) will not know in which context the second list should be used.

For fn(x)(y) the common interpretation would be fn(x); (y) => (y). It returns the 2nd list after evaluating the first call.

4
Greg Bacon On

If you can live with the additions of a sigil and an arrow, you could curry subtract as in

my $subtract = sub {
  my($x) = @_;

  sub { my($y) = @_; $x - $y };
};

Call it as in

my $result = $subtract->(40)(20);

If the arrow is acceptable but not the sigil, recast subtract as

sub subtract {
  my($x) = @_;

  sub { my($y) = @_; $x - $y };
};

Invocation in this case looks like

my $result = subtract(40)->(20);
5
amon On

Please don't tack on broken syntax extensions on your program to solve a solved problem. What you want are closures, and a technique sometimes called currying.

Currying is the job of transforming a function that takes multiple arguments into a function that is invoked multiple times with one argument each. For example, consider

sub subtract {
  my ($x, $y) = @_;
  return $x - $y;
}

Now we can create a subroutine that already provides the first argument:

sub subtract1 { subtract(40, @_) }

Invoking subtract1(20) now evaluates to 20.

We can use anonymous subroutines instead, which makes this more flexible:

my $subtract = sub { subtract(40, @_) };
$subtract->(20);

We don't need that variable:

sub { subtract(40, @_) }->(20); # equivalent to subtract(40, 20)

We can write subtract in a way that does this directly:

sub subtract_curried {
  my $x = shift;
  # don't return the result, but a subroutine that calculates the result
  return sub {
    my $y = shift;
    return $x - $y;
  };
}

Now: subtract_curried(40)->(20) – notice the arrow in between, as we are dealing with a code reference (another name for anonymous subroutine, or closures).

This style of writing functions is much more common in functional languages like Haskell or OCaml where the syntax for this is prettier. It allows very flexible combinations of functions. If you are interested in this kind of programming in Perl, you might want to read Higher-Order Perl.