Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Re: What's the best way to use Any YAML (not YAML::Any)

by kcott (Archbishop)
on Mar 15, 2014 at 05:46 UTC ( [id://1078423]=note: print w/replies, xml ) Need Help??


in reply to What's the best way to use Any YAML (not YAML::Any)

G'day djerius,

One of the main reasons for avoiding the use of 'string eval' is that it doesn't provide compile time warnings. If you use a BEGIN block, you can get around this.

BEGIN { my @yaml_classes = qw{YAML::XS YAML::Syck YAML::Tiny YAML}; first { eval "use $_; 1" } @yaml_classes or die "Unable to load any of these YAML classes: @yaml_classe +s"; }

That will give a compile time warning which, is not only very obvious with respect to the problem, but also will obviate the need for any specific testing to check if a YAML class was found and loaded, i.e. Test::More's use_ok('Your::Module::Name'), which I'd hope you already have, would suffice.

Unable to load any of these YAML classes: YAML::XS YAML::Syck YAML::Ti +ny YAML at ... BEGIN failed--compilation aborted at ...

If you need to know which YAML class was loaded and, as your code suggests, this is stored in a variable with package scope, I'd recommend something other than the all-too-common $class. You'd need to declare that variable outside the BEGIN block.

my $yaml_class; BEGIN { my @yaml_classes = qw{YAML::XS YAML::Syck YAML::Tiny YAML}; $yaml_class = first { eval "use $_; 1" } @yaml_classes or die "Unable to load any of these YAML classes: @yaml_classe +s"; } print $yaml_class;
"My module doesn't care which YAML is around, just that it can Dump and Load."

I'd probably consider testing for that in the BEGIN block also. The following example clearly documents the intent and would only need a change, in a single and obvious place (i.e. near the top of the module code), if that requirement ever changed.

BEGIN { # YAML classes to attempt to load (in order of preference) my @yaml_classes = qw{YAML::XS YAML::Syck YAML::Tiny YAML}; # Expecting to import these functons from whatever YAML class is l +oaded my @yaml_imports = qw{Dump Load}; my $yaml_class = first { eval "use $_ (\@yaml_imports); 1" } @yaml +_classes or die "Unable to load any of these YAML classes: @yaml_classe +s", " (using import list: @yaml_imports)"; }

Update (improved solution): I've changed that final BEGIN block (i.e. the changes are in the block immediately above here).

After posting, I realised that the original solution (see spoiler below) had some drawbacks: it loaded the YAML class before checking @EXPORT; it didn't allow imports from @EXPORT_OK; and, it would fail for all @yaml_classes if any particular class successfully loaded but then failed the @EXPORT test. The "improved solution" has none of these problems.

As already stated (i.e. "original solution (see spoiler below)"), but apparently not clear to some, I repeat, the original solution is hidden in the spoiler below.

BEGIN { my @yaml_classes = qw{YAML::XS YAML::Syck YAML::Tiny YAML}; my $yaml_class = first { eval "use $_; 1" } @yaml_classes or die "Unable to load any of these YAML classes: @yaml_classe +s"; # Only expecting whatever YAML class is loaded to export these fun +ctions my @yaml_funcs = qw{Dump Load}; no strict 'refs'; my %yaml_exports = map { $_ => 1 } @{$yaml_class . "::EXPORT"}; $yaml_exports{$_} or die "$yaml_class doesn't export '$_'" for @ya +ml_funcs; }

[Minor Update: I've added some additional text, to the "Update (improved solution)" text, to make it clear that the original code is hidden in the spoiler.]

-- Ken

Replies are listed 'Best First'.
Re^2: What's the best way to use Any YAML (not YAML::Any)
by djerius (Beadle) on Mar 15, 2014 at 16:00 UTC
    Thanks. A couple of comments:
    • Checking for Dump/Load is prudent, but all of the listed YAML modules implement the common Dump/Load API, so I don't think it's absolutely necessary.
    • The check for Dump/Load in your last version assumes the included module uses @EXPORT. I wouldn't rely on that, as there are alternates to Exporter which don't use that, and while these modules might currently use it, that behavior is not in the API. If they switch to something else, this'll break.

      A better way might be to perform an explicit import, e.g. something like

      eval { $yaml_class->import( 'Dump'); 1; } or croak( "Can't find Dump in $yaml_class\n" );
      or, if you can trust that the function isn't AUTOLOADED, look at the module's namespace for the function
      Package::Stash->new( $yaml_class )->has_symbol( '&Dump' ) or croak( "Can't find Dump in $yaml_class\n" );
      The only foolproof method is to just wait for a runtime error:
      my $Dump = $yaml_class . "::Dump"; [... later in the code ...] my $var = eval { &$Dump(...) } or croak( "bad return or Dump not imple +mented\n" );
    There's also another problem, which I didn't recognize until late last night (and didn't get around to posting about before your reply), namely that if there's a YAML module already loaded, it should use that. I've updated the OP above to illustrate my current code.
      "Checking for Dump/Load is prudent, but all of the listed YAML modules implement the common Dump/Load API, so I don't think it's absolutely necessary."

      Can you guarantee that you'll only ever use "the listed YAML modules"? Can you guarantee that you'll never want to use another function beyond Dump and Load in this, or a subclassed, module?

      Yes, it's prudent. No, it's not absolutely necessary. It's two lines of code that you can choose to add to your module or not.

      "The check for Dump/Load in your last version assumes the included module uses @EXPORT."

      No, that check occurred in the original version, not the update. The original solution was hidden in a spoiler (for anyone interested in seeing the changes that were made). I thought that "original solution (see spoiler below)" made this clear but apparently not: I've added additional text making this clearer.

      -- Ken

        Can you guarantee that you'll only ever use "the listed YAML modules"?
        I can guarantee that all modules in the list have Load & Dump. If one doesn't, my module can't use it, so it won't be in the list.

        If I were to use a proxy module which didn't guarantee that API, I would have to check for it. The only one that I know of, YAML::Any, does offer that interface.

        Can you guarantee that you'll never want to use another function beyond Dump and Load in this, or a subclassed, module?
        I can guarantee that my module will only ever use Load & Dump.

        I can't say anything about subclasses, but if they need a particular API, they'll have to ensure that it is met.

        No, that check occurred in the original version, not the update. The original solution was hidden in a spoiler (for anyone interested in seeing the changes that were made). I thought that "original solution (see spoiler below)" made this clear but apparently not: I've added additional text making this clearer.
        For some reason, I was unable to view the code in the spoiler: clicking on the link didn't reveal it properly; I obviously commented on the wrong code. My apologies for the confusion.

        Thanks for your input.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (6)
As of 2024-03-29 15:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found