Пользовательский синтаксис Perl для передачи аргументов функции
Я использую Perl в течение некоторого времени. Я хочу знать, как я могу запустить следующую операцию в Perl:
subtract(40)(20)
Чтобы получить результат:
20
Я думаю, что мне придется взглянуть на пользовательские методы синтаксического анализа для Perl. Вот на что я сейчас смотрю:
и http://www.perl.com/pub/2012/10/an-overview-of-lexing-and-parsing.html
Теперь я не уверен, что искать или что делать. Любая помощь, КАК идти об этом, ЧТО читать, будет принята с благодарностью. Пожалуйста, будьте ясны. Спасибо.
5 ответов
Я рекомендую попробовать Parse:: Keyword. Parse:: Keyword действительно отлично подходит для синтаксического анализа пользовательского синтаксиса, так как позволяет вам вызывать различные части парсера Perl, такие как parse_listexpr
, parse_block
, parse_fullstmt
и т. д. (см. Перлапи).
Недостатком является то, что если вы используете те, которые разбирают выражения, закрывающие переменные, они плохо обрабатываются, но это можно обойти с помощью PadWalker.
Parse:: Keyword (включая обман PadWalker) - это то, что использует Kavorka; и это делает довольно сложные вещи! Ранние версии 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
Если вы можете жить с добавлением сигилы и стрелы, вы могли бы карри subtract
как в
my $subtract = sub {
my($x) = @_;
sub { my($y) = @_; $x - $y };
};
Назовите это как в
my $result = $subtract->(40)(20);
Если стрелка приемлема, но не символ, переделайте subtract
как
sub subtract {
my($x) = @_;
sub { my($y) = @_; $x - $y };
};
Вызов в этом случае выглядит так
my $result = subtract(40)->(20);
Пожалуйста, не используйте в своей программе нарушенные синтаксические расширения для решения решенной проблемы. То, что вы хотите, это замыкания и метод, иногда называемый каррированием.
Карринг - это работа по преобразованию функции, которая принимает несколько аргументов, в функцию, которая вызывается несколько раз с одним аргументом каждый. Например, рассмотрим
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, вы можете прочитать Perl высшего порядка.
@Heartache: Пожалуйста, забудьте эту проблему, так как она не имеет смысла для парсера и пользователя.
Вы можете подумать об использовании fn[x][y]
или же fn{x}{y}
которые являются допустимыми вариантами синтаксиса - то есть вы можете сложить []
а также {}
но не списки, или fn(x,y)
или же fn(x)->(y)
которые выглядят хорошо, также являются допустимыми и значимыми вариантами синтаксиса. Но fn(x)(y)
не будет знать, в каком контексте следует использовать второй список.
За fn(x)(y)
общая интерпретация будет fn(x); (y) => (y)
, Возвращает 2-й список после оценки первого звонка.
Вы можете создать фильтр исходного кода:
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];
}