12

When an eval statement is within the scope of a lexical variable, that variable should be within the lexical context of the evaluated block. Moreover, lexical variables should be available in the lexical context of subs.

Yet this doesn't work:

use warnings;

{
    package VLE;

    my $ln10 = 2.302585092994045684017991454684;

    sub reval {
        say eval $_[0] if @_;
    }
}

package VLE;

reval( q($ln10) );

It results in:

Variable "$ln10" is not available at (eval 1) line 1.

But if I (uselessly) use the lexical variable anywhere in the block, it's suddenly available in the eval:

use warnings;

{
    package VLE;

    my $ln10 = 2.302585092994045684017991454684;

    sub reval {
        say eval $_[0] if @_;
        my (undef) = $ln10;
        return 0
    }
}

package VLE;

reval( q($ln10) );

prints

2.30258509299405

Why does this happen?

Edit:

The destruction of references isn't the issue, since this code (which maintains a reference to $ln10) also fails:

use warnings;

{
    package VLE;

    my $ln10 = 2.302585092994045684017991454684;

    sub reval2 {
        say eval $_[0] if @_;
        my (undef) = $ln10;
        return 0
    }

    sub reval {
        say eval $_[0] if @_;
        return 0
    }
}

package VLE;

reval( q($ln10) ); # still fails
reval2( q($ln10) ); # works
alexchandel
  • 532
  • 6
  • 15
  • I think using the generic quotes `q`, `qq`, `qx` etc. with ordinary parentheses is bad practice. `q($ln10)` looks far too much like a subroutine call, and is far better as `'$ln10'` here. Even if you wanted to quote a string containing single quotes, something like `q/$str = 'aaa'/` would be preferable, as the slash is Perl's common "alternative quote". I've edited your question. – Borodin Oct 05 '17 at 16:35
  • I use `q()` often because I frequently test snippets of Perl in the terminal with `perl -E '... say join("\n", split("", "foo ".q($foo))) ...'`, often with a multiline program inside `' '`, including the two above, and parentheses make it easy to keep track of block/quotation level when single-quotes are unavailable, especially in the absence of syntax highlighting as in the terminal. There are great reasons to do all the things you think are bad practice. – alexchandel Oct 05 '17 at 18:31
  • I even prefer snippets without any single-quotes, since I can paste them directly into the terminal then without using a heredoc. – alexchandel Oct 05 '17 at 18:36
  • I don't understand what you mean by *"all the things [I] think are bad practice"*. I am saying here only that I think `q(...)` is bad practice because it is easily confused with a subroutine call. What you do frequently when testing snippets of code is irrelevant here, and in any case I think `perl -E '... say join("\n", split("", "foo ". q/$foo/)) ...'` is simpler if you are counting parentheses. What are all these "great reasons"? – Borodin Oct 05 '17 at 18:42
  • *"I even prefer snippets without any single-quotes"* Then use slashes. But please don't emulate a function call: even spaces become significant within `q( a b )`. And we're still discussing *my opinion*. – Borodin Oct 05 '17 at 18:44
  • I've seen *"parentheses make it easy to keep track of block/quotation level when single-quotes are unavailable"* and *"I prefer snippets without any single-quotes, since I can paste them directly into the terminal without using a here-doc"* and it's clear that you have a self-centric view of programming. With any luck you will write something that affects other people and will be read by other people. Once you start writing on Stack Overflow you are presenting your code to people who work in many different ways, and would appreciate something more readable. Your way of working is irrelevant. – Borodin Oct 05 '17 at 18:53
  • The elaboration of ln(10.0) also worries me. `my $ln10 = log 10` would have sufficed: you're not testing the size or accuracy of your perl's floating point here. If your work isn't for other people then you have missed the point. You may be enthralled by your own insights for a while, but that isn't a purpose. – Borodin Oct 05 '17 at 19:04
  • *"is easily confused with a subroutine call"*/*"I think … is simpler"*/*"please don't emulate a function call"* None of us are remotely confused by this. Maybe you're just bad at reading? – alexchandel Oct 15 '17 at 20:54
  • *"The elaboration of ln(10.0) also worries me."* Wrongly. There's an important reason for it. But you assumed you knew what we're doing. Wrongly. – alexchandel Oct 15 '17 at 20:55
  • *"Your way of working is irrelevant."* Right back at you, bud. If you're angered by insignificant trivialities or idiosyncrasies, then stay off the internet. – alexchandel Oct 15 '17 at 20:59
  • If there's an "important reason" to avoid `log 10` then it needs a comment as it's an obvious edit. And I know *exactly* what you're doing: you're asking a question on Stack Overflow, where readability is way more important than what *you* prefer and what is convenient for *your* coding habits. `q($ln10)` is far too easily overlooked as a subroutine call, in particular because it also has a "variable" as its "parameter". *"None of us are remotely confused"* well hopefully not because you wrote it and understand what it's supposed to do. Write your code for others to read. – Borodin Oct 16 '17 at 08:49
  • *"If you're angered by insignificant trivialities or idiosyncrasies, then stay off the internet"* If you think this is an insignificant triviality then you're in the wrong job: the ability to pay attention to details is essential to coding well. And I'm not talking about my way of working; this is about your confusing style. I don't care how you fix it, but I think it's a significant issue. And the ability to accept constructive criticism is also essential to being a good programmer. – Borodin Oct 16 '17 at 08:58
  • No, a comment wasn’t needed in the original context, nor was style unclear; I’m sorry you assumed so wrongly of it, and that we’re all equally animated by pedantry in casual Q&A. – alexchandel Dec 07 '18 at 18:31

1 Answers1

13

A subroutine does not close over (capture) all visible lexical variables, but only those that are referred to within the subroutine body. This is a pretty integral part for reference counting to work properly. Which variables are captured is determined at compile time, and the variables are captured when the subroutine definition is executed (compile time for named subs, run time for anonymous subs).

Here your first reval does not capture the $ln10 variable. Therefore, it is not available inside the eval. Because the eval string is a runtime value, it cannot be taken into account when determining which variables should be captured.

More precisely, the $ln10 variable is destroyed when the enclosing block is left, because no further references to it exist. So this code would work because the $ln10 variable still exists while the eval is executed:

use warnings;
{
    package VLE;
    my $ln10 = 2.302585092994045684017991454684;

    sub reval {
        say eval $_[0] if @_;
    }

    reval(q($ln10));
    # $ln10 variable exists until here
}

The second eval does capture the variable so its lifetime is extended until the point in time where the eval is executed, and everything works as expected.

amon
  • 57,091
  • 2
  • 89
  • 149
  • 1
    The block is also important. The `package` is not a lexical scope on its own. That's what `our` is for. – simbabque Oct 05 '17 at 16:08
  • Reference destruction isn't entirely applicable here, since maintaining a reference still causes it to fail (see my edit). – alexchandel Oct 05 '17 at 18:42
  • @alexchandel Oh, that's very interesting! I have a hunch why that might be the case (the “pad“ table mapping lexical variables to scalar objects is destroyed when the scope is left, so the inner sub can't find the variable value even if the value continues to exist), but at this level we're deep into implementation details of the Perl interpreter. It's better to treat that as undefined behaviour, and only expect variables in an eval that you explicitly close over. – amon Oct 05 '17 at 19:01