用于传递函数参数的Perl自定义语法

时间:2013-12-18 20:52:35

标签: perl parsing lexical

我一直在使用perl一段时间了。 我想知道如何在perl中运行以下操作:

subtract(40)(20)

要获得结果:

20

我想我必须查看Perl的自定义解析技术。 这就是我现在正在看的内容:

Devel::Declare

Devel::CallParser

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

现在,我不确定该寻找什么或做什么。 关于如何解决这个问题的任何帮助,阅读什么将不胜感激。请清楚。 谢谢。

5 个答案:

答案 0 :(得分:3)

我建议您尝试Parse::Keyword。 Parse :: Keyword非常适合解析自定义语法,因为它允许您回调Perl解析器的各个部分,例如parse_listexprparse_blockparse_fullstmt等(请参阅{{ 3}})。

它有一个缺点,如果你使用它们来解析关闭变量的表达式,这些处理得很糟糕,但这可以用perlapi解决。

Parse :: Keyword(包括PadWalker技巧)是PadWalker使用的;这确实是一些非常复杂的东西!早期版本的p5-mop-redux也使用它。

无论如何,这里有一个演示如何解析你奇怪的函数......

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

答案 1 :(得分:2)

如果您可以使用添加的符号和箭头,则可以{/ 3}} subtract使用

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

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

将其称为

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

如果箭头可以接受而不是sigil,则重新subtract

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

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

此案例中的调用类似于

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

答案 2 :(得分:1)

请不要在程序上破坏语法扩展,以解决已解决的问题。 您想要的是闭包,以及有时称为 currying 的技术。

Currying是将一个函数转换为一个函数的工作,该函数将多个参数转换为多次调用的函数,每个函数使用一个参数。例如,考虑

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

现在我们可以创建一个已经提供第一个参数的子例程:

sub subtract1 { subtract(40, @_) }

调用subtract1(20)现在评估为20

我们可以使用匿名子程序,这样可以更灵活:

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

我们不需要那个变量:

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

我们可以用直接执行此操作的方式编写subtract

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;
  };
}

现在:subtract_curried(40)->(20) - 注意两者之间的箭头,因为我们正在处理代码引用(匿名子程序或闭包的另一个名称)。

这种写作函数在 Haskell OCaml 等函数式语言中更为常见,其语法更为漂亮。它允许非常灵活的功能组合。如果您对Perl中的这种编程感兴趣,可能需要阅读Higher-Order Perl

答案 3 :(得分:0)

@Heartache:请忘记这个挑战,因为它对解析器和用户毫无意义。

您可以考虑使用fn[x][y]fn{x}{y}作为有效的语法变体 - 即您可以堆叠[]{}但不能列出, 或者看起来不错的fn(x,y)fn(x)->(y)也是有效且有意义的语法变体。 但是fn(x)(y)不知道应该在哪个上下文中使用第二个列表。

对于fn(x)(y),常见的解释是fn(x); (y) => (y)。它在评估第一次调用后返回第二个列表。

答案 4 :(得分:-1)

您可以创建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;

并使用它:

#!/usr/bin/perl

use BracketFilter;

subtract(40)(20);

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