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

Perldoc perlobj states:

Sometimes you want to call a method when you don't know the method name ahead of time. You can use the arrow form, replacing the method name with a simple scalar variable containing the method name

But what about when you know part of the method name ahead of time, just not all of it? I want to call a method using some known text plus a scalar variable. For example, see the method called on "$caller" in either of the following, neither of which works:

$$database_handle = DBI->connect(map {exists $arguments{$_} ? $argume +nts{$_} : $caller->"database_${_}"()} qw(data_source_name username p +assword taint)) or die "Could not connect to the database: $DBI::errs +tr";

... produces the error ...

String found where operator expected at Storage.pm line 46, near "->"d +atabase_${_}"" (Missing operator before "database_${_}"?)

... or maybe this will work ...

$$database_handle = DBI->connect(map {exists $arguments{$_} ? $argum +ents{$_} : $caller->database_$_()} qw(data_source_name username pas +sword taint)) or die "Could not connect to the database: $DBI::errst +r";

... nope ...

Scalar found where operator expected at Storage.pm line 47, at end of +line (Missing operator before ?) syntax error at Storage.pm line 47, near "->database_$_"

The only way I have found to make this work -- after several SuperSearches and trying perl5.6 and perl5.8 -- is to create a new simple scalar variable and use this as the method call, as such:

$$database_handle = DBI->connect(map {my $s="database_$_";exists $ar +guments{$ _} ? $arguments{$_} : $caller->$s()} qw(data_source_name username pass +word taint)) or die "Could not connect to the database: $DBI::errstr" +;

The above code compiles fine. It is a bit kludgy though. Is there any way to accomplish a method call that mixes an interpolated variable and some known text, sort of like a double quoted string?

Replies are listed 'Best First'.
Re: Double quoted string as method call
by Zaxo (Archbishop) on May 04, 2004 at 21:59 UTC

    Avoiding comment on your DBI code, since I don't understand it, I think you want, $obj->${"text_${_}"};

    After Compline,
    Zaxo

      Close Zaxo, I think you were looking for $obj->${\"text_$_"}() (note the reference taken).

        Thank you!

        I have compiled and run and tested, and it does seem this works!

        It doth confuse cperl-mode in emacs, tho!

        Cperl-mode understands $caller->$ {\ "database_$_"}() (note spacing) just fine, however.

        Whether humans will be able to parse such funny spacing remains to be seen ;->

      UPDATE: Actually this compiles fine but doesn't actually work, see first response below for reason why. Thanks anyway.

      ---------- Old message ----------

      Thank you!

      I should note, however, that in my case this failed with a syntax error until I added a () to the end of the, as such:

      $obj->${"text_${_}"}();

      This may have to do with the particulars of my code.

      (As for the DBI call, that stuff can safely be ignored. I would note it was actually flawed insofar as I tried to pass in taint information incorrectly, the correct parameter order is dsn, user, pass, then a hashref with other optional config info, which I usually compose as {Taint=>1}.)

        $obj->${"text_${_}"}();
        I don't think that does what you want it to: it does one too many levels of indirection. if $_ has the value "foo", then I presume you want to do the equivalent of
        $obj->text_foo()
        In fact, supposing you have a variable called $text_foo with value bar, then what you are actually doing is the equivalent of
        $obj->bar()
Re: Double quoted string as method call
by jarich (Curate) on May 05, 2004 at 01:51 UTC
    The only way I have found to make this work -- after several SuperSearches and trying perl5.6 and perl5.8 -- is to create a new simple scalar variable and use this as the method call, as such:
    $$database_handle = DBI->connect( map { my $s = "database_$_"; exists $arguments{$_} ? $arguments{$_} : $s->() } qw(data_source_name username password taint) ) or die "Could not connect to the ". "database: $DBI::errstr";
    The above code compiles fine. It is a bit kludgy though. Is there any way to accomplish a method call that mixes an interpolated variable and some known text, sort of like a double quoted string?

    As far as I can work out there isn't such a way. :( In fact, to do what you're doing up there in the map you have to have strict refs turned off. You've probably turned them off for this whole section because otherwise you could well be having trouble with $$database_handle.

    I can offer you two alternatives to this problem, however. Both strict compliant. If your database_* subroutines merely return a string value, ie:

    sub database_username { return "fred"; }
    then hash defaults might be the way to go:
    my %defaults = ( data_source_name => ".....", username => "fred", password => "fred", taint => { Taint => 1 } ); # then later... %arguments = (%defaults, %arguments); my $dbh = DBI->connect( @arguments{ qw(data_source_name username password taint) }) or die "Could not connect to the database: ". "$DBI::errstr";
    When assigning to hashes, later keys of the same name overwrite the values from the earlier keys. This means that if "data_source_name" is provided in %arguments the %arguments version is kept. Obviously keys which are not included in %arguments remain those of the default hash.

    The @arguments{ qw/.../ } structure is a hash slice. This ensures we get the values out in the order we want them to appear.

    This ideas works if your database_* subroutines are ONLY returning simple stuff. If you want your database_* subroutines to read these values from a file or something similar and to do so only if needed then you might want to do something like the following:

    my %defaults = ( data_source_name => \&database_data_source_name, username => \&database_username, password => \&database_password, taint => \&database_taint, ); # and later: my $dbh = DBI->connect ( map { exists $arguments{$_} ? $arguments{$_} : $defaults{$_}() } qw(data_source_name username password taint) ) or die "Could not connect to the database: ". "$DBI::errstr"; # and somewhere else: sub database_data_source_name { # read stuff from file.. # do other stuff return #something }
    This has the added advantage of looking a whole lot nicer than using a string as a coderef. What we're doing here is creating a hash of references to our subroutines. It's not until we do $defaults{$_}() that these subroutines actually get executed.

    I hope this helps,

    jarich

    Update: Heh. I post my node and already stand corrected. Well done Anonymous Monk. If you do wish to make your code strict compliant, however, you may find my suggestions useful.

    map { exists $arguments{$_} ? $arguments{$_} : ${\"database_$_"}->() }

      Apologies, but it appears you have posted my code incorrectly in the very first part of your post. Particularly, at the alleged $s->(). I am not sure how key this is to your assertion my code is not strict-compliant .

      I have re-read what I posted and cannot find where I ever call $s as the object or class. I call $s as a method to $caller, ie $caller->$s.

      In any case, I have a close relative of the code I posted running fine under strict:

      $$database_handle = DBI->connect(( map { my $s="database_$_"; exists $arguments{$_} ? $arguments{$_} : $caller->$s() } (qw(data_source_name username password)) ), {Taint=>$arguments{taint}||$caller->database_ta +int} ) or die "Could not connect to the database: $DBI:: +errstr" unless defined $$database_handle;

      Please note $$database_handle is not meant to be a symbolic reference. You would need to see the whole module. $database_handle is a reference to a reference (to a handle), which I create earlier in the method because the particular reference (handle) used depends on whether the method is called on a class or an object.

      Thanks for the rest of your post -- I will read it over. But, again, no problems with stricture!

        My apologies, you're completely right.

        I dropped the $caller bit for my testing and then promptly forgot that I had done so and therefore incorrectly concluded that since what I had wasn't strict compliant, what you had couldn't be either.

        I considered that $$database_handle could be either a symbolic reference or a scalar reference and that is why I wrote that it too could cause you problems under strict, rather than it does.

        I should have checked my assumptions.

        All the best,

        jarich

Re: Double quoted string as method call
by Anonymous Monk on May 05, 2004 at 01:13 UTC
Re: Double quoted string as method call
by ozone (Friar) on May 05, 2004 at 07:54 UTC
Re: Double quoted string as method call
by Belgarion (Chaplain) on May 04, 2004 at 21:57 UTC

    Update As other people below be have mentioned, the code I posted here doesn't work at all. That's what happens when I post just before leaving work. My code does work with a hashref, but does nothing for a method call. D'oh.

    I'm going to take a stab at this and say that you need braces around the call, like so:

    $caller->{"database_${_}"}()

    I haven't actually tested this, but it compiles cleanly. More knowledgeable monks will probably have a better solution and explanation.

      Unfortunately your idea doesn't work, at least on 5.8.4 on debian or 5.8.3 on win32. I think this is just an example of the constructs getting too complicated for the perl parser to handle, but I'm not sure exactly why. The work around is simple:
      my $base = 'foo_'; my $obj = new obj; for(qw/baz qux stuff/) { my $meth = "${base}_$_"; $obj->$meth; }
      Which works perfectly.

      I find it a little odd that the $obj->"meth" form doesn't work, since the reverse works perfectly, at least for packages: "package"->$meth

      Pardon, but it appears you are selecting a value from a dereferenced hash rather than making a method call on an object or class. No?

      The latter is my goal, not the former. Perhaps I should have been clearer!

      Thanks anyway.

Re: Double quoted string as method call
by ihb (Deacon) on May 05, 2004 at 15:16 UTC

    This is a bit ugly, but I'll post it anyway since no one has mentioned can().

    Since you expect the method to exist, you can do

      $obj->can('...')->($obj => @args)

    It works because can() returns a code reference to the subroutine it found (undef if none), and then you simply provide the object as the first argument.

    ihb

      I don't think that's ugly at all. In fact, it's my prefered method of dynamically calling methods. Seems to me it's the most official way of doing it (for timtowtdi definitions of "official").

      "There is a thin line between ignorance and arrogance, and only I have managed to erase that line." - Dr. Science

        I find it ugly because

        • you have to specify the object twice for one method call
        • if you're assumption about the existance of the method is wrong you get a very poor error message
        • it uses unnecessary dereferencing

        ihb