Perl: гарантирована ли единичная оценка констант? - PullRequest
3 голосов
/ 11 августа 2011

Третий Perl вопрос от меня через два дня. Некоторые скажут, что я недостаточно усердно занимаюсь исследованиями, хотя скажу, что помогаю поддерживать активную секцию: P В любом случае, я громко размышляю в надежде, что мне ответят.

Без лишних слов позвольте мне процитировать утверждение из константы документации прагмы:

Когда в выражении используется константа, Perl заменяет ее на значение во время компиляции, а затем может дополнительно оптимизировать выражение.

Я просто хочу пояснить: означает ли это, что все выражения оцениваются только один раз за выполнение программы? Потому что это также говорит о том, что они построены с использованием встроенных сабвуферов, что само по себе кажется мне оценочным для использования. Я имею в виду, если вы получаете подпрограмму, которая возвращает выражение, она переоценивает его за вызов, верно? Если они не используют вложенные переменные или переменные состояния для оценки только один раз, но я не знаю.

Конечно, могу ли я гарантировать, что это будет оцениваться только один раз?

#!/usr/bin/perl

use 5.014;
use autodie;
use strict;
use warnings;

use constant TEST => do {
    say 'Testing...';
    5;
};

say TEST foreach (1..4);

Кажется, в этом конкретном примере я могу; «Тестирование ...» печатается только один раз. Но гарантировано ли это для всех выражений, которые я к ним добавляю?

Да, да, да. Я должен использовать Readonly на CPAN. К сожалению, я пришел из серии мыслей Python, что вы должны придерживаться стандартного способа сделать что-то как можно больше, поэтому я придерживаюсь устаревшей константы , потому что это основная прагма.

В принципе, если я добавлю длинный сложный конвейер sort / grep / map в константу, могу ли я на 100% гарантировать только одну оценку?

Ответы [ 3 ]

6 голосов
/ 11 августа 2011

Оптимизатор глазка заменяет вызовы постоянных подпрограмм значением, возвращаемым этой подпрограммой. В optree во время выполнения этого саба нет. Для иллюстрации:

$ perl -MO=Concise -E\
  'sub answer () { 42 } for (1 .. 10) { say "The answer is ", answer }'
h  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 49 -e:1) v:%,2048 ->3
g     <2> leaveloop vK/2 ->h
7        <{> enteriter(next->d last->g redo->8) lKS/8 ->e
-           <0> ex-pushmark s ->3
-           <1> ex-list lK ->6
3              <0> pushmark s ->4
4              <$> const(IV 1) s ->5
5              <$> const(IV 10) s ->6
6           <$> gv(*_) s ->7
-        <1> null vK/1 ->g
f           <|> and(other->8) vK/1 ->g
e              <0> iter s ->f
-              <@> lineseq vK ->-
8                 <;> nextstate(main 48 -e:1) v:%,2048 ->9
c                 <@> say vK ->d
9                    <0> pushmark s ->a
a                    <$> const(PV "The answer is ") s ->b
b                    <$> const(IV 42) s ->c
d                 <0> unstack v ->e

Здесь интерес представляет

c                 <@> say vK ->d
9                    <0> pushmark s ->a
a                    <$> const(PV "The answer is ") s ->b
b                    <$> const(IV 42) s ->c
d                 <0> unstack v ->e

Что показывает, что аргументами операции say являются константная строка «Ответ есть» и константное целое число 42. Не существует опкода entersub, который представлял бы дополнительный вызов.

4 голосов
/ 11 августа 2011

Правая часть

use constant PI => do { 4 * atan2 1, 1 };

является просто выражением [1].Он оценивается при выполнении инструкции.Поскольку оператор является use, он выполняется во время компиляции.Поскольку люди обычно не помещают объявления констант в циклы, они выполняются только один раз. [2]К тому времени, когда компиляция переходит к следующей строке, определена следующая подпрограмма:

sub PI () { 3.14159265358979 }

Это то, что происходит с каждым оператором use constant.Выражение в правой части может быть произвольно сложным, но значение подпрограммы, созданной constant, всегда будет простым значением выражения во время компиляции константы.Механизм распространения этого постоянного значения в остальной части файла - это подпрограмма с постоянным значением, но эта подпрограмма содержит значение исходного выражения, , а не самого выражения.

[1]: Один, который я украл из http://perldoc.perl.org/perlsub.html#Constant-Functions - который, кстати, содержит более полное обсуждение того, что Perl делает с встроенными подпрограммами.

[2]: И если он выполняется более одного раза, он предупреждаето переопределении функции - и только последние палки переопределения.

3 голосов
/ 11 августа 2011

Прагма constant является синтаксическим сахаром для объявления встроенных подпрограмм.

Когда вы пишете:

use constant TEST => do {
    say 'Testing...';
    5;
};

Это в точности соответствует следующему:

BEGIN {
    require constant;
    constant->import(TEST => do {say 'Testing...'; 5});
}

Вы можете расширить блок do, чтобы сделать порядок выполнения более понятным:

BEGIN {
    require constant;
    say 'Testing...';
    constant->import(TEST => 5);
}

Поскольку constant является просто синтаксическим сахаром, его можно удалить, что приводит к следующему коду:

BEGIN {
    say 'Testing...';
    *TEST = sub () {5};
}

Таким образом, когда perl компилирует код, он встречает блок BEGIN во время компиляции и немедленно выполняет его. Таким образом, во время компиляции выводится строка Testing... и объявляется подпрограмма TEST. Так как TEST объявлен с прототипом () (не принимает аргументов) и его тело разрешается в одно постоянное значение, подпрограмма является кандидатом для встраивания. Затем остальная часть источника компилируется.

Всякий раз, когда perl встречает вызов TEST() в последующем исходном коде, он заменяет этот вызов на 5 во время компиляции.

...