Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

detecting an undefined variable

by LloydRice (Beadle)
on Sep 21, 2019 at 12:41 UTC ( [id://11106477]=perlquestion: print w/replies, xml ) Need Help??

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

How do I detect whether a variable has been defined in an enclosing context? I tried

my $myscale = 1; if ( defined $scale ) { $myscale = $scale; }
When $scale does not exist in the outer context, this fails. So then I tried
my $myscale = $scale // 1;
with the same result. But "defined" seems to want an existing object (function, array, etc.), So finally, I tried it this way ..
my $myscale = extscale // 1; sub extscale { return $scale; }
I have reread the Camel sections on "defined" and "exists". Neither of these does what I am looking for. Is there some way to do this?

Replies are listed 'Best First'.
Re: detecting an undefined variable
by haukex (Archbishop) on Sep 21, 2019 at 12:53 UTC

    Using an non-existant variable is a fatal compilation error under strict (which one should always use). This smells of using a variable as a variable name, which is generally considered a bad practice... why would you want to do this, what do you need this for?

    Normally, the solution to this kind of thing would be to use a hash instead, where the defined and exists checks apply (with the differences explained in their documentation).

    Enough rope to shoot yourself in the foot: For package variables, one can use if ( defined $::{'VARNAME'} ) ... (Update: although this checks for any symbol with that name, including arrays, hashes, subs, etc. - one could use peek_our from PadWalker instead, similar to the following example), and for lexical variables use PadWalker qw/peek_my/; if ( defined peek_my(0)->{'$VARNAME'} ) .... But don't do this.

    Update: Minor edits.

Re: detecting an undefined variable (updated: hash-element)
by LanX (Saint) on Sep 21, 2019 at 13:07 UTC
    Could you please give us more context what you're trying to achieve?

    This smells like an XY problem ...

    Since - like haukex already explained - hard coded variables must already be declared at compilation time under strict, there are not many possible use cases...

    Do you use strict, eval , my or our ?

    Most likely you just need to check a hash key $opt{scale} for existences with exists ...

    update

    ... or just definedness

    my $myscale = $opt{scale} // 1;

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: detecting an undefined variable
by Athanasius (Archbishop) on Sep 21, 2019 at 13:21 UTC

    Hello LloydRice,

    What you are trying to do is quite possible:

    use strict; use warnings; #my $scale = 42; my $myscale = init_myscale(1); print "\$myscale = >$myscale<\n"; sub init_myscale { my ($default) = @_; no strict qw( vars ); no warnings qw( uninitialized ); return eval $scale ? $scale : $default; }

    Output:

    23:16 >perl 2018_SoPW.pl $myscale = >1< 23:18 >

    But, as haukex and LanX have said, the fact that you want to do this strongly suggests that there is a flaw in your design.

    Update: return eval $scale ? $scale : $default; should be return eval defined $scale ? $scale : $default; to catch the case where $scale exists but has a false value ($state = 0). Note that this approach can’t be used to catch the case where $state exists but has the value of undef.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: detecting an undefined variable
by AnomalousMonk (Archbishop) on Sep 21, 2019 at 14:10 UTC

    I completely agree with the remarks of haukex, LanX and Athanasius regarding hashes, but if you just gotta do something like this, here's another way:

    c:\@Work\Perl\monks>perl -le "use 5.010; ;; use strict; use warnings; ;; sub extscale (); ;; my $myscale = extscale // 1; print $myscale; ;; sub extscale () { no strict 'vars'; no warnings 'once'; return $scale; } " 1
    This is entirely a compile-time solution. Note that the  extscale function must be either declared (as above) or defined before it is used. Note also the use of a prototype to achieve the desired effect.


    Give a man a fish:  <%-{-{-{-<

      Nice!

      Here a generic - albeit slower - solution for definedness

      use strict; use warnings; use Data::Dump qw/pp dd/; #my $scale =42; #our $scale =42; sub var { eval $_[0];# or die $@; } my $myscale = var('$scale') // 1; pp $myscale;

      and inspecting the errorstring in $@ can help checking for existence instead.

      Global symbol "$scale" requires explicit package name (did you forget to declare "my $scale"?) at (eval 1) line 1.

      update
      of course using eval directly is shorter

      use strict; use warnings; #my $scale =42; #our $scale =42; my $myscale = eval('$scale') // 1; print $myscale;

      but I prefer encapsulating such magic to be able to capture side effects like polluting $@ or potential injections by tainted input.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: detecting an undefined variable
by GrandFather (Saint) on Sep 22, 2019 at 02:25 UTC

    Having read the conversation so far, maybe I can help shed some light on the issue you face. It seems your root concern is that you pick-and-mix code from other places and are concerned that variables picked up along the way may cause the resultant patchwork code to behave badly.

    So the first thing is use strict; use warnings; are your friend. As a general thing Perl's scope rules (where an identifier refers to the same thing) are pretty sane. That in combination with strictures will pretty quickly weed out most badness. There are a couple of particular cases to think about:

    • If you paste in a sub then none of the variables in the sub can "leak" into the surrounding code.
    • If you paste in a block of code you can wrap it in {...} and guarantee that none of the variables can "leak" into surrounding code.
    • If strictures throw up errors then most likely code somewhere wants Global variables. Global variables should be avoided where possible because they tend to contribute to hard to maintain code.
    • Restrict variable to the smallest scope (set of {}) that makes sense.
    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

      In reply to Grandfather, yes, I pick-and-mix code from other places, but it is not a concern that such variables may cause the code to behave badly. But exactly the opposite. I WANT those variables, and having exactly their original purpose and value.

      May I digress? I had been writing Fortran and C since they were respectively invented. I could not deal with C++. I started with Perl shortly after Llama came out (94, 95 ish). Friends tell me I should switch to Python. But I do not for exactly the above (C++) reason. I love it that Perl pretty much successfully hides the Object code. I will be 80 in a few weeks and I am not likely to relearn much else from here on out.

      Thanks so much for all of your advice and concerns.

        " I WANT those variables, and having exactly their original purpose and value."

        In that case your concern should still be that the variables and patched in code behave badly. If you understand the original code well enough to transplant it then you should understand it well enough to put a nice wrapper around it so it doesn't interact badly with the rest of your code. The wrapper ensures you don't have undeclared variables so your original concern is moot. The trick of course is understanding the borrowed code well enough that it can thrive when transplanted.

        Just the process of wrapping the transplanted code in a sub and providing a well defined set of parameters and results will drive understanding of what the borrowed code needs and what it does. Strictures of course help that process by pointing out issues early. If you are seriously concerned that the code behave as expected the next level is to write a test suite against the code.

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: detecting an undefined variable
by BillKSmith (Monsignor) on Sep 21, 2019 at 16:21 UTC
    The Perl definition of many common words is not quite what you think. The question which you meant to ask is "How can I tell if a variable is 'declared'"? 'Defined' means that a variable has a value. This makes no sense if it is not 'declared'. 'Exists' refers to keys in a hash and has nothing to do with variables. The concept of storing the name of a variable in another variable is called 'symbolic reference'. It is never needed and always discouraged. In fact, one advantage of 'use strict' is that it prevents us from using symbolic reference by accident. To return to your original question, there is no run-time support to test if a variable is 'declared' ('Use strict' provides compile-time support). This would only be useful with symbolic reference which you should not be using anyhow.

    UPDATE: I took it as a challenge to do what you asked for using strict vars with eval.

    use warnings; use Test::Simple tests => 3; # Test wether or not the target of a symbolic reference is declared. my $xxx ; our $yyy; { use strict vars; my $sym_ref; $sym_ref = '$xxx'; ok ( do{eval $sym_ref; !$@}, 'declared lexical'); $sym_ref = '$yyy'; ok ( do{eval $sym_ref; !$@}, 'declared package'); $sym_ref = '$zzz'; ok (!do{eval $sym_ref; !$@}, 'not declared'); # Note: !do..... }

    This test does what you wanted, but not what you need because symbolic-refs only work with package variables. Of course once you know that the variable is declared, you could use LanX's idea and check if the name 'exists' in the symbol table. Even that could fail in special cases. DO NOT USE SYMBOLIC REFERENCE!

    Bill
      I mostly agree, but

      > Exists refers to keys in a hash and has nothing to do with variables.

      this statement is problematic because Perl's namespaces - including scratchpads° - are actually implemented as hashes.

      for instance it's perfectly possible to check the STASH for a package variable

      DB<3> p exists $main::{xxx} DB<4> $xxx = 1 DB<5> p exists $main::{xxx} 1

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      °) though you'd need PadWalker to access it.

        That doesn't check if the variable exists though, it checks if the stash for that name is populated with anything. Having a sub or file handle with the same name would cause the stash entry to exist, usually stored inside a GLOB. While the other types can be detected, it isn't possible to distinguish between an undefined and non-existent scalar by inspecting a GLOB in pure perl.

        Thanks for all the replies to my "detecting an undefined ..". I agree with the last reply, that I meant "declared" rather than "defined". It's not clear to me why what I'm trying to do should be so undisciplined (ill advised?). I often put together pieces of code from various places. So it's handy to check whether some variable already exists in another piece. In the example I gave, the code would be in a function being called from some other code, written at a different time. If I had then used a variable called $scale, it would be handy to know whether that variable existed in the calling code or whether I now had to declare and define it anew. If it existed before, I would want to use the existing value. If not, I would define it as
        my $scale = 1;
        That doesn't seem so undisciplined to me.
Re: detecting an undefined variable
by BillKSmith (Monsignor) on Sep 23, 2019 at 18:51 UTC
    It may be a bit late, but I finally understand your problem. It has nothing to do with symbolic reference. Sorry about that confusion. You want to initialize the variable $myscale with the value of $scale (if it is defined) and with '1' otherwise. Normally you would do this:
    my $myscale = $scale // 1;

    The problem is that $scale may be not defined because it is not declared. The code above causes a compile time error.

    The following code incorporates many of replies you have already received. Note that the same code must work in three situations. It is tempting to write the code as a function and call it wherever it is needed. This does not always work correctly because it would be testing declare and define in the scope of the function, not of the call.

    use strict; use warnings; use Test::Simple tests => 3; { # $scale is not declared my $myscale = do{ use strict 'vars'; no warnings 'uninitialized'; (do{eval '$scale'; $@} ) ? 1 : (eval '$scale') // 1; }; ok( $myscale == 1, 'not declared'); } my $scale; { # $scale declared, but not defined my $myscale = do{ use strict 'vars'; no warnings 'uninitialized'; (do{eval '$scale'; $@} ) ? 1 : (eval '$scale') // 1; }; ok( $myscale == 1, 'declared, but not defined'); } $scale = 7; { # $scale declared, and defined my $myscale = do{ use strict 'vars'; no warnings 'uninitialized'; (do{eval '$scale'; $@} ) ? 1 : (eval '$scale') // 1; }; ok( $myscale == $scale, 'defined'); }

    The logic is confusing. The first eval compiles the string '$scale' recognizing it as a variable. If it is not declared, this is an error under 'no strict vars'. That error is signaled with the system variable $@. Because $@ is the last value in the do-block, it is returned. We only care about its logical value. "True" means that there was an error and the variable is not declared. We return the default value (1). "False" means there was no error, the variable is declared. At this point we would like to do the normal assignment, but our code has to compile in all three cases. We call eval again. We know that there will not be an error. This time, we want the return value (the value of the variable). If that value is undef, we use the default value. The result of all this logic is returned by the outer do and assigned to $myscale.

    Bill

      Thanks, Bill. That's exactly what I need.

      Lloyd

        Now I will admit a little secret. The second method in your original post would work under
        no strict 'vars'; no warnings 'once';

        Without strict, if a variable '$state' is not already declared, perl will automatically declare a package variable '$state'. Of course this variable would not be defined so perl would assign your '1' to $mystate. This could lead to a very subtle bug if in the future you ever tried to declare a lexical variable '$state'. It is legal to have two variables with the same name. Hard to tell which one perl will think you mean.

        Bill
Re: detecting an undefined variable
by Anonymous Monk on Sep 21, 2019 at 14:51 UTC
    This smells of the "PHP-ism" $$varname, which has long ago also been frowned-out by that community and with excellent reason. Don't try to inject arbitrary values into the variable-name space – use hashes instead.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11106477]
Approved by haukex
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (5)
As of 2024-04-19 04:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found