http://qs1969.pair.com?node_id=11128524

QM has asked for the wisdom of the Perl Monks concerning the following question:

I've been closing over hashes for years, for subs that need some persistent store, but not wanting to invoke memoize.

Below I've closed over a scalar and a hash. I get a warning of:

Use of uninitialized value $FIXED_STRING in hash element at ...

With hashes, this just works, but apparently the hash is empty, and that's fine. But the scalar value within the sub is undef.

What am I missing?

What is the easiest way to fix this, while still using a scalar to store a string, that is later used as a hash key?

#!/usr/bin/env perl # demo of closure around sub, with persistent string use strict; use warnings; for my $q ('a'..'g') { do_something($q); print "Done computing\n" } { 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"; } } } }

-QM
--
Quantum Mechanics: The dreams stuff is made of

Replies are listed 'Best First'.
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]
Re: Closure Over Scalar?
by davido (Cardinal) on Feb 18, 2021 at 16:46 UTC

    Consider the following code:

    #!/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.


    Dave

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 (++).


    🦛

      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.
        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

      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.

Re: Closure Over Scalar?
by stevieb (Canon) on Feb 18, 2021 at 16:33 UTC

    You can force initialization at compile time by wrapping the assignment in a BEGIN block:

    my $FIXED_STRING; BEGIN { $FIXED_STRING = 'fixed_string'; } my %persistent;
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 { ... }

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

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"; } } }
    Bill
      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

        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.
        Bill
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:  <%-{-{-{-<

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

Re: Closure Over Scalar?
by Anonymous Monk on Feb 19, 2021 at 13:10 UTC