Пользовательский синтаксис Perl для передачи аргументов функции

Я использую 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

Теперь я не знаю, что искать и что делать. Любая помощь в том, КАК это сделать, ЧТО читать, будет оценена по достоинству. Пожалуйста, будьте ясны. Спасибо.


person Heartache    schedule 18.12.2013    source источник
comment
Что ты пытаешься сделать? Это выглядит так, как будто вы пытаетесь использовать указатель функции.   -  person Elliott Frisch    schedule 19.12.2013
comment
Я хочу иметь возможность запускать подпрограмму, называемую вычитанием, как описано выше.   -  person Heartache    schedule 19.12.2013
comment
subtract(40)->(20) будет допустимым синтаксисом. Если subtract возвращает замыкание, это даже сработает. (40)(20) не будет действительным, если вы не совершили серьезное насилие над синтаксическим анализатором. И ни один из вариантов не является более читаемым оператором вычитания, чем старый добрый инфикс -, который мы все знаем примерно с детского сада. Есть смысл?   -  person    schedule 19.12.2013


Ответы (5)


Я рекомендую попробовать Parse::Keyword. Parse::Keyword отлично подходит для разбора пользовательского синтаксиса, так как позволяет вызывать различные части парсера Perl, такие как parse_listexpr, parse_block, parse_fullstmt и т. д. (см. perlapi).

У него есть недостаток в том, что если вы используете их для анализа выражений, которые закрываются над переменными, они обрабатываются плохо, но это можно обойти с помощью 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
person tobyink    schedule 18.12.2013

Если вы можете жить с добавлением символа и стрелки, вы можете карри 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);
person Greg Bacon    schedule 18.12.2013
comment
Интересно, что вам не нужен -> между (40) и (20), если он у вас есть слева от (40). Страшно интересно... - person ; 19.12.2013
comment
@WumpusQ.Wumbley В разделе «Использование ссылок» документа perlref мы читаем , «Стрелка необязательна между нижними индексами скобок…» - person Greg Bacon; 19.12.2013
comment
Хм... в моей части мира (а также в той части мира, откуда взялся Perl и его документация) скобки не означают скобки. Но я вижу, что пример также включает скобки, которые я тоже не считаю скобками. Но $array[$x]{foo}[0] действителен без стрелок, в то время как subtract(40)(20) требует по крайней мере одну стрелку где-то. Мне все еще кажется, что необязательное правило стрелки применяется к скобкам вызова функции иначе, чем к скобкам поиска в массиве или фигурным скобкам поиска по хешу. - person ; 19.12.2013
comment
Спасибо за ответ. Но я требую, чтобы вызов был строго таким: вычесть (40) (20) Префикс или суффикс не имеют значения. Я могу нарушать парсер сколько угодно. - person Heartache; 19.12.2013

Пожалуйста, не добавляйте неработающие расширения синтаксиса в свою программу, чтобы решить решенную проблему. Вам нужны замыкания и метод, иногда называемый каррингом.

Каррирование — это работа по преобразованию функции, которая принимает несколько аргументов, в функцию, которая вызывается несколько раз с одним аргументом каждая. Например, рассмотрим

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 высшего порядка.

person amon    schedule 18.12.2013
comment
Я требую, чтобы вызов был строго таким: вычесть (40) (20) Моя благодарность за ваш ответ. - person Heartache; 19.12.2013
comment
@Heartache Какую проблему вы на самом деле пытаетесь решить, когда у вас такие строгие требования? Если вам действительно нужно написать расширение синтаксиса, по крайней мере, используйте Parse::Keywords, а не один из более старых (более неработающих) экспериментов. - person amon; 19.12.2013
comment
Это сложная проблема. - person Heartache; 19.12.2013
comment
На самом деле это просто так, или если я смогу найти альтернативу: D Проблема прямо в моем вопросе. - person Heartache; 19.12.2013
comment
@Heartache my $source_code = "subtract(40)(20)"; die if $source_code ne "subtract(40)(20)"; $result = 20 — это было бы решением, с которым мне было бы удобно. - person amon; 19.12.2013

@Heartache: Пожалуйста, забудьте об этом вызове, так как он не имеет смысла ни для парсера, ни для пользователя.

Вы можете подумать об использовании fn[x][y] или fn{x}{y}, которые являются допустимыми вариантами синтаксиса, то есть вы можете складывать [] и {}, но не списки, или fn(x,y) или fn(x)->(y), которые выглядят красиво, также являются допустимыми и значимыми вариантами синтаксиса. Но fn(x)(y) не будет знать, в каком контексте следует использовать второй список.

Для fn(x)(y) общей интерпретацией будет fn(x); (y) => (y). Он возвращает второй список после оценки первого вызова.

person rurban    schedule 18.12.2013

Вы можете создать фильтр исходного кода:

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];
}
person Denis Ibaev    schedule 18.12.2013
comment
Исходные фильтры - это ошибка. Не используйте их для реализации расширений синтаксиса — в большинстве случаев Parse::Keyword является лучшим решением. - person amon; 19.12.2013
comment
@amon, см. Parse::Keyword: УСТАРЕЛО: писать расширения синтаксиса в Perl. НЕ ИСПОЛЬЗУЙТЕ! - person Denis Ibaev; 19.12.2013
comment
Все модули расширения синтаксиса в той или иной степени нарушены — ответ tobyink на этот вопрос показывает, как преодолеть ограничения Parse::Keyword. Этот модуль по-прежнему лучше всего подходит для непосредственного использования Perl C API. - person amon; 19.12.2013
comment
@amon и исходные фильтры являются основной частью дистрибутива Perl. - person Denis Ibaev; 19.12.2013
comment
@amon, Parse::Keyword использовал функции API, представленные в Perl 5.14. А старые версии? - person Denis Ibaev; 19.12.2013
comment
Это вопрос оценки того, какой подход менее неверен. Проблема с исходными фильтрами заключается в том, что они не компонуются, могут легко запутаться и должны анализировать Perl, который, как известно, является полным по Тьюрингу. Parse::Keyword имеет некоторые заметные проблемы с замыканиями, но не имеет недостатков исходных фильтров. Filter находится в Core, потому что он необходим для анализа исходных файлов с экзотическими кодировками, а не потому, что это рекомендуемый способ расширения синтаксиса. - person amon; 19.12.2013
comment
@amon Я не говорил, что Filter рекомендуется. Но это возможный, а не неправильный способ решения этой проблемы. - person Denis Ibaev; 19.12.2013