Using perl `my` within actual function arguments

Viewed 119

I want to use perl to build a document graph as readably as possible. For re-use of nodes, I want to refer to nodes using variables (or constants, if that is easier). The following code works and illustrates the idea with node types represented by literals or factory function calls to a and b. (For simple demo purposes, the functions do not create nodes but just return a string.)

sub a (@) {
    return sprintf "a(%s)", join( ' ', @_ );
}   

sub b (@) {
    return sprintf "b(%s)", join( ' ', @_ );
}   

printf "The document is: %s\n", a(
    "declare c=",
    $c = 1, 
    $e = b( 
        "use",      
        $c,         
        "to declare d=",
        $d = $c + 1 
    ),      
    "use the result",
    $d,     
    "and document the procedure",
    $e      
);

The actual and expected output of this is The document is: a(declare c= 1 b(use 1 to declare d= 2) use the result 2 and document the procedure b(use 1 to declare d= 2)).

My problem arises because I want to use strict in the whole program so that variables like $c, $d, $e must be declared using my. I can, of course, write somewhere close to the top of the text my ( $c, $d, $e );. It would be more efficient at edit-time when I could use the my keyword directly at the first mention of the variable like so:

…
printf "The document is: %s\n", a(
    "declare c=",
    my $c = 1,
    my $e = b(
        "use",      
        $c,         
        "to declare d=",
        my $d = $c + 1
    ),      
    "use the result",
    $d,     
    "and document the procedure",
    $e      
);

This would be kind of my favourite syntax. Unfortunately, this code yields several Global symbol "…" requires explicit package name errors. (Moreover, according to documentation, my does not return anything.)

I have the idea of such use of my from uses like in open my $file, '<', 'filename.txt' or die; or in for ( my $i = 0; $i < 100; ++$i ) {…} where declaration and definition go in one.

Since the nodes in the graph are constants, it is acceptable to use something else than lexical variables. (But I think perl's built-in mechanims are strongest and most efficient for lexical variables, which is why I am inclined into this direction.)

My current idea to solve the issue is to define a function named something like define which behind the scenes would manipulate the current set of lexical variables using PadWalker or similar. Yet this would not allow me to use a natural perl like syntax like $c = 1, which would be my preferred syntax.

3 Answers

I am not certain of the exact need but here's one simple way for similar manipulations.

The example in the OP wants a named variable inside the function call statement itself, so that it can be used later in that statement for another call etc. If you must have it that way then you can use a do block to work out your argument list

func1(  
    do { 
        my $x = 5;
        my $y = func2($x);  # etc 
        say "Return from the do block what is then passed as arguments..."; 
        $x, $y
    }
);

This allows you to do things of the kind that your example indicates.

If you also want to have names available in the subroutine then pass a hash (or a hashref), with suitably chosen key names for variables, and in the sub work with key names.

Alternatively, consider normally declaring your variables ahead of the function call. There's no bad thing about it while there are many good things. Can throw in a little wrapper and make it look nice, too.


More specifically

printf "The document is: %s\n", a( do {
    my $c = 1;
    my $d = $c + 1;
    my $e = b( "use", $c, "to declare d=", $d );
    # Return a list from this `do`, which is then passed as arguments to a()
    "declare c=", $c, $e, "use the result", $d,"and document the procedure", $e 
} );

(condensed into fewer lines for posting here)

This do block is a half-way measure toward moving this code into a subroutine, as I presume that there are reasons to want this inlined. However, since comments indicate that the reality is even more complex I'd urge you to write a normal sub instead (in which a graph can be built, btw).

according to documentation, my does not return anything

The documentation doesn't say that, and it's not the case.

Haven't you ever done my $x = 123;? If so, you've assigned to the result of my $x. my simply returns the newly created variable as an lvalue (assignable value), so my $x simply returns $x.

Unfortunately, this code yields several [strict vars] errors.

Symbols (variables) created by my are only visible starting with the following statement.

For better of for worse, it allows the following:

my $x = 123;

{
   my $x = $x;
   $x *= 2;
   say $x;   # 246
}

say $x;   # 123

I want to use perl to build a document graph as readably as possible.

So why not do that? Right now, you are building a string, not a graph. Build a graph of objects that resolve to a string after the graph has been constructed. You can build those object with a tree of sub calls (declare( c => [ use( c => ... ), ... ] )). I'd give a better example, but the grammar of what you are generating isn't clear to me.

Your argument list makes two references each to $c, $d and $e. If you prefix the first reference with my, it will be out of scope by the time Perl gets around to parsing the second reference it won't be in scope until the next statement, so the second reference would refer to a different variable (which may violate strict vars).

Declare my ($c,$d,$e) before your function call. There is nothing wrong or inelegant about doing that.

Related