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

Hello, Monks. It's been a while. I've flirted with Python , Go, and Ruby but still end up using Perl since it's most comfortable.

Anyway, on with the question: If I could think of the nomenclature I'd use it, but what I want to do is pre-define a string that contains a variable that will be assigned only later, and still adhere to the strict and warnings pragmas. Here is some pseudo-code:

my $url = "http://something.com/api_stuff/$GUID"; # do stuff make_call( '1234' ); sub make_call { my $GUID = shift; POST->$url; }

Is anything like this even doable? I could set $GUID as a global variable but I don't want other subs that might use $url in different context to get hosed up. I may end up having to use regexes or other methods.

Thanks!

Replies are listed 'Best First'.
Re: Strings with undefined variables?
by choroba (Cardinal) on Jan 03, 2019 at 23:26 UTC
    What you need is a "template". For a simple one, you can use String::Interpolate, but there are many more, some even implementing loops and template inheritance.
    #! /usr/bin/perl use warnings; use strict; use String::Interpolate; my $template = 'String::Interpolate'->new; $template->('http://something.com/api_stuff/$GUID'); make_call( '1234' ); sub make_call { my $GUID = shift; $template->{GUID} = $GUID; POST("$template"); } use Test::More; sub POST { is shift, 'http://something.com/api_stuff/1234', 'intepolates'; } done_testing();

    The same using Template:

    #! /usr/bin/perl use warnings; use strict; use Template; my $template = 'http://something.com/api_stuff/[%GUID%]'; make_call( '1234' ); sub make_call { my $GUID = shift; my $template_object = 'Template'->new; $template_object->process(\$template, {GUID => $GUID}, \ my $outpu +t); POST($output); } use Test::More; sub POST { is shift, 'http://something.com/api_stuff/1234', 'intepolates'; } done_testing();
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      Thanks! I'll check it out, but it may prove to be more overhead than I want for this application. Good to know in general, though.
      fnord
Re: Strings with undefined variables?
by tobyink (Canon) on Jan 03, 2019 at 23:32 UTC
Re: Strings with undefined variables?
by bliako (Abbot) on Jan 04, 2019 at 10:56 UTC
    Is anything like this even doable?

    Don't know what pabulum Pythonistas are used to, but for Perl we all know the answer to that ...

    Anyway, if you are going to use regexes then absolutely make sure that when you set your $url you do use single quotes so that any variable-looking entities in there do not get interpolated. The way you do it in your example $GUID will be interpolated to empty, and because you do not use strict/warnings you will not even know it when it disappears.

    Secondly, if you use regexes, your "variables" inside the $url are not necessary to look like perl variable names (e.g. $GUID). You can adopt any convention which makes the regex substitution easy and unambiguous. For example, and I am only trying to make a point by being extreme, you can use my $url = "http://something.com/api_stuff/<<<<VAR:GUID>>>>"; (note: single or double quotes in this case do not matter - there are no "Perl-looking" vars in there).

    Of course, it is also possible to use $GUID in $url (as you do) but you have some escaping to do. At least use ${GUID} to decrease ambiguity. But there is absolutely no reason to use the dollar-sign and that brings you back to my suggestion.

    For the really specific use-case you provide, I see no reason why you should use a variable like $GUID in your $url and then have it substituted. You could have a base URL which various functions take, along with GUIDs and other params and then they form a valid url by appending etc. That is, each function knows what URL format it has to return inherently, inside the function (and not globally as you do), and does whatever transformations are necessary with whatever input is required in order to produce and return the valid URL.

    Of course, there are lots and lots of ways to achieve what you want, this is just one since you mentioned regex.

    bw, bliako

      Thanks! Yeah, I expected I could do something like the following. Note that the actual use case involves several URLs that would like to store in a hash. (Also, I've been using Perl for almost 20 years, mostly in text processing. Only dabbled in Python and don't care for it much.)

      my %URLS = ( upload => 'https://somewhere.com/api1', process => 'https://somewhere.com/=GUID=/api2', ); my $guid = upload_file( $ARGV[0] ); my $rtn = process_file( $guid ); sub upload_file { my $fn = shift; # LWP::UserAgent stuff my $g = $ua->post( $URLS{ upload } . '?' . $fn ); return $g; } sub process_file { my $guid = shift; # LWP::UserAgent stuff ( my $this_url = $URLS{ process } ) =~ s/=GUID=/$guid/; my $result = $ua->post( $this_url ); return $result || 0; }
      fnord

        That's cool. And if you pass to your subs a hash of substitutions a-la 'GUID'=>123 you will have a very light template system ...

        bw, bliako

Re: Strings with undefined variables?
by jimpudar (Pilgrim) on Jan 05, 2019 at 01:58 UTC

    I have used the substitution method before with good success, wrapping keywords like this: %keyword%

    However, you might also consider using function calls...

    #! /usr/bin/env perl use strict; use warnings; use 5.10.0; sub url { 'http://something.com/api_stuff/' . shift } say 'POST->' . url('1234');

    πάντων χρημάτων μέτρον έστιν άνθρωπος.