Re: Closure Over Scalar?
by choroba (Cardinal) on Feb 18, 2021 at 15:57 UTC
|
my with an assignment is tricky. The declaration of the variable happens at compile time, but the assignment happens at runtime. In this case, the assignment happens after the loop. So, when running the loop, the variable has been declared, but hasn't been assigned to. You need to move the closure block before the loop.
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [Watch: Dir/Any] [d/l] |
Re: Closure Over Scalar?
by davido (Cardinal) on Feb 18, 2021 at 16:46 UTC
|
#!/usr/bin/env perl
use strict;
use warnings;
print "1. Before calling foo\n";
foo();
print "2. After calling foo\n";
{
print "3. Beginning of block\n";
sub foo {print "4. Inside foo()\n";}
print "5. End of block\n";
}
print "6. After block\n";
The order printed will be 1, 4, 2, 3, 5, 6. In your code, your definition of $FIXED_STRING happens in position 3, which happens after the sub call (position 4). The 'my' declaration happens at compiletime. The definition happens at runtime, and run time happens line by line unless flow is altered by a loop, subroutine, conditional, exception, or other control of flow mechanisms.
Really the best solution is to manufacture your closure through a subroutine:
my $foo_stuff = make_foo();
$foo_stuff->{'foo'}->('bar');
$foo_stuff->{'foo'}->('baz');
$foo_stuff->{'dump_foo'};
sub make_foo {
my $FIXED_STRING = 'fixed_string';
my %persistent;
return {
foo => sub {
my $x = $_[0];
$persistent{$x}{$FIXED_STRING} = rand();
},
dump_foo => sub {
for my $k (keys %persistent) {
print "$k: $persistent{$k}{$FIXED_STRING}\n";
}
}
};
}
This strategy would certainly work reliably (assuming I didn't submit a typo).
I'm not thrilled with needing to return accessor subs for both setting and dumping. It feels like we're going in the direction that is satisfied by object systems. But it pains me to say that because, I love the elegance of subs manufacturing subs capturing closures.
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Closure Over Scalar?
by hippo (Bishop) on Feb 18, 2021 at 17:18 UTC
|
You have received really good answers already (points all round) so if you don't mind I'll just don my consulting hat for a moment and provide a different viewpoint.
You have a scalar variable $FIXED_STRING to which you assign a literal string once and never change it. Bonus points to you for choosing an appropriate name for your scalar! However, if this is a real indicator of your situation why not side-step the problem entirely by using a constant instead? In that case your block becomes:
{
use constant FIXED_STRING => 'fixed_string';
my %persistent;
sub do_something {
my $x = $_[0];
$persistent{$x}{FIXED_STRING} = rand;
END {
for my $k (keys %persistent) {
print "$k: $persistent{$k}{FIXED_STRING}\n";
}
}
}
}
and you do not need to worry about compile-time vs run-time or returning subs or any of that. This might be considered a work-around and that's just fine - sometimes a work-around is precisely what is required.
Update: Haarg has spotted the flaw here and supplied a fix in his reply (++).
| [reply] [Watch: Dir/Any] [d/l] |
|
This doesn't work as you intended. The bareword FIXED_STRING in a hash access will be used as the string 'FIXED_STRING' , not as an expression that would evaluate the constant. To use the constant, you need to prevent it from being treated as a bareword. $persistent{$x}{+FIXED_STRING} is one possibility.
| [reply] [Watch: Dir/Any] [d/l] |
|
Yes. The reason I don't like string literals as hash keys, is it's too easy in some contexts to create data on one hash key, look for it with another key, and decide that nothing interesting happened. I'd much prefer the interpreter tell me "no such variable" -- I'd rather have the explicit message, rather than a quiet failure.
And missing out the + to avoid string interpolation of the bareword is something I'm likely to do.
In production code, I try never to have string literals, except where they are assigned to variables or constants. And since, in Perl, constants are a bit funny, I use scalars as being less likely to be accidentally overwritten.
I liked the idea of restricted keys, such as in Hash::Util, though I can still mistype string literals when accessing hashes, so "constant" scalars are still valuable to me.
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] [Watch: Dir/Any] [d/l] |
|
I do like the constant way of doing things (++) the best in this case. Primarily because this is a hard-coded variable that doesn't appear that it'll change.
Also, constants are usually declared/defined at the top of a file, so if a change is needed, they're not hard to find within the code.
| [reply] [Watch: Dir/Any] |
Re: Closure Over Scalar?
by stevieb (Canon) on Feb 18, 2021 at 16:33 UTC
|
my $FIXED_STRING;
BEGIN { $FIXED_STRING = 'fixed_string'; }
my %persistent;
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Closure Over Scalar?
by LanX (Saint) on Feb 18, 2021 at 16:03 UTC
|
you initialize my $FIXED_STRING = 'fixed_string'; only AFTER calling the sub.
Not really a closure problem, could happen with package vars too.
One best practice to avoid such problems is to use a "generator sub" with the whole closure-context like in your block and returning \&do_something
update
like:
sub generate {
my $FIXED_STRING = 'fixed_string';
my %persistent;
return sub {
...
}
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Closure Over Scalar?
by BillKSmith (Monsignor) on Feb 18, 2021 at 20:29 UTC
|
This seems to be what use feature 'state' is made for.
#!/usr/bin/env perl
# demo of closure around sub, with persistent string
use strict;
use warnings;
use feature 'state';
for my $q ('a'..'g') {
do_something($q);
print "Done computing\n"
}
sub do_something {
state $FIXED_STRING = 'fixed_string';
state %persistent;
my $x = $_[0];
$persistent{$x}{$FIXED_STRING} = rand;
END {
for my $k (keys %persistent) {
print "$k: $persistent{$k}{$FIXED_STRING}\n";
}
}
}
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
This seems to be what use feature 'state' is made for.
Yes, state has fallen out of my lexicon from disuse. A very good point, I don't need a closure, and the assignment happens as expected!
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] [Watch: Dir/Any] [d/l] |
|
It is not an issue in your example, but could be in your production code. Initialization of state hash and array variables was not available before v5.28.
| [reply] [Watch: Dir/Any] |
Re: Closure Over Scalar?
by AnomalousMonk (Archbishop) on Feb 19, 2021 at 02:48 UTC
|
An approach that hasn't been mentioned so far is to encapsulate a
function and its data-to-be-closed-over in a module.
A my variable in a module is absolutely
private unless a getter/setter is explicitly defined for it.
All code in the
module is executed at the point in script compilation at which the
module is use-ed.
Initialization and checking of any complexity can be done.
(In some other languages, this is known as Compile Time Function
Evaluation - CTFE - and is a Big Deal.)
An extremely simple module with no exportation or OO can be used.
CloseOver.pm:
package CloseOver;
use strict;
use warnings;
my $FIXED_STRING = 'fixed_string'; # could be a constant
my %persistent = (42 => { $FIXED_STRING => 123 });
sub something {
my ($x,
) = @_;
$persistent{$x}{$FIXED_STRING} = rand;
}
END {
for my $k (keys %persistent) {
print "$k: $persistent{$k}{$FIXED_STRING}\n";
}
}
1;
Output:
Win8 Strawberry 5.8.9.5 (32) Thu 02/18/2021 21:14:15
C:\@Work\Perl\monks\QM
>perl
use strict;
use warnings;
use CloseOver;
CloseOver::something($_) for 1 .. 3;
^Z
42: 123
1: 0.69775390625
3: 0.8636474609375
2: 0.555877685546875
Update: Minor edit for clarity.
Give a man a fish: <%-{-{-{-<
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Closure Over Scalar?
by ikegami (Patriarch) on Feb 18, 2021 at 23:36 UTC
|
The assignment to $FIXED_STRING is found after the sub call. It hasn't been executed when the sub is called.
(And using a my var without first executing the my statement is undefined behaviour.)
Use
{
my $FIXED_STRING = 'fixed_string';
my %persistent;
sub do_something {
my $x = $_[0];
$persistent{$x}{$FIXED_STRING} = rand;
END {
for my $k (keys %persistent) {
print "$k: $persistent{$k}{$FIXED_STRING}\n";
}
}
}
}
for my $q ('a'..'g') {
do_something($q);
print "Done computing\n"
}
or use state.
use feature qw( state );
for my $q ('a'..'g') {
do_something($q);
print "Done computing\n"
}
sub do_something {
my $x = $_[0];
state $FIXED_STRING = 'fixed_string';
state $persistent = {};
$persistent->{$x}{$FIXED_STRING} = rand;
END {
for my $k (keys %$persistent) {
print "$k: $persistent->{$k}{$FIXED_STRING}\n";
}
}
}
Seeking work! You can reach me at ikegami@adaelis.com
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Closure Over Scalar?
by Anonymous Monk on Feb 19, 2021 at 13:10 UTC
|
| [reply] [Watch: Dir/Any] |