Why do some users quote classnames in Perl?
Asked Answered
D

2

12

Looking at Type::Tiny, I see that the class name in the call to Type::Tiny->new is quoted in the official docs,

my $NUM = "Type::Tiny"->new(
   name       => "Number",
   constraint => sub { looks_like_number($_) },
   message    => sub { "$_ ain't a number" },
);

Why is this? Is this mere style? Is there any performance ramifications for this practice?

Drumbeat answered 26/11, 2019 at 2:9 Comment(0)
D
8

Take a simpler example

package Foo { sub new { die 7  } };
package Bar { sub new { die 42 } };
sub Foo { "Bar" }
Foo->new();

In the above example, the constant Foo resolves to "Bar", so so this calls "Bar"->new not "Foo"->new. How do you stop the subroutine from resolving? You can quote it.

"Foo"->new();

As for the performance implication, things are not made worse by using a string rather than a bareword. I've confirmed the optree generated by O=Deparse is the same. So as a general rule, it seems it's better to quote the Classnames if you value correctness.

This is mentioned in Programming Perl, (sadly in the context of indirect method invocation)

... so we'll tell you that you can almost always get away with a bare class name, provided two things are true. First, there is no subroutine of the same name as the class. (If you follow the convention that subroutine names like new start lowercase and class names like ElvenRing start uppercase, this is never a problem). Second, the class been loaded with one of

use ElvenRing;
require ElvenRing;

Either of these declarations ensures that Perl knows ElvenRing is a module name, which forces any bare name like new before the class name ElvenRing to be interpreted as a method call, even if you happen to have declare a new subroutine of your own in the current package.

And, that makes sense: the confusion here can only happen if your subroutines (typically lowercase) have the same name as a class (typically uppercase). This can only happen if you violate that naming convention above.

tldr; it is probably a good idea to quote your classnames, if you know them and you value correctness over clutter.

Side note: alternatively you can stop resolution of a bareword to a function by affixing a :: to the end of it, for example in the above Foo::->new.


Thanks to Ginnz on reddit for pointing this out to me, and to Toby Inkster for the comment (though it didn't make sense to me on first read).

Drumbeat answered 26/11, 2019 at 2:9 Comment(9)
@mob yea, the Perl book mentions that too (explicitly), not sure if I should put that there or not? lol.Drumbeat
@mob I added that too, as a footnote. Though I'm not sure I like it.Drumbeat
Foo:: has the advantage that it warns if Foo isn't an existing packagePyuria
Related: How does Perl parse unquoted bare words?Pyuria
Try::Tiny is a better example than Foo. In your code using Foo-> you can be expected to know that there is no such subroutine in your package. But if you are using Try::Tiny and a number of other cpan/other externally sourced modules, who's to say if one of them is using a Try package, and if so, if there is a Tiny sub in it?Contour
@Pyuria Foo::->new and "Foo"->new both seem to warn, for me. Can you produce an example where only one warns?Drumbeat
Also of note, the second "requirement" in that paragraph from PP applies specifically to indirect object notation since the method name comes first, it isn't ambiguous in normal method invocation to have a sub new symbol defined.Excruciate
@EvanCarroll Foo::->new warns at compile time (and dies at run time), while "Foo"->new only dies at run time (if you use warnings 'FATAL' => 'all', then it matters for instance). As for an example: perl -wcE '"Foo"->new' doesn't warn, but perl -wcE 'Foo::->new' does.Moffitt
Note that neither quoting nor Paamayim-Nekudotayim-ing the class name can save you from someone doing open *FOO, see https://mcmap.net/q/836572/-prevent-strings-from-being-interpreted-as-a-file-handle/1030675.Danieu
J
1

Explicitly quoting the class name rather than using a bareword (which is treated as a string) is one of three ways to avoid syntactic ambiguity. The Invoking Class Methods section of the perlobj documentation explains.

Because Perl allows you to use barewords for package names and subroutine names, it sometimes interprets a bareword’s meaning incorrectly. For example, the construct Class->new() can be interpreted as either 'Class'->new() or Class()->new() .In English, that second interpretation reads as “call a subroutine named Class(), then call new() as a method on the return value of Class().” If there is a subroutine named Class() in the current namespace, Perl will always interpret Class->new() as the second alternative: a call to new() on the object returned by a call to Class().

See this odd case in action with the demo below.

#! /usr/bin/env perl

use strict;
use warnings;

sub Type::Tiny { print "Returning Bogus\n" ; return "Bogus" }

sub Type::Tiny::new { print "Type::Tiny::new\n" }

sub Bogus::new { print "Bogus::new\n" }

my $class = "Type::Tiny";

Type::Tiny->new;
Type::Tiny::->new;
"Type::Tiny"->new;
$class->new;

Its output is

Returning Bogus
Bogus::new
Type::Tiny::new
Type::Tiny::new
Type::Tiny::new

The rest of the aforementioned documentation section shows how to protect against surprising behavior or inadvertent errors.

You can force Perl to use the first interpretation (i.e., as a method call on the class named "Class") in two ways. First, you can append a :: to the class name:

Class::->new()

Perl will always interpret this as a method call.

Alternatively, you can quote the class name:

'Class'->new()

Of course, if the class name is in a scalar Perl will do the right thing as well:

my $class = 'Class';
$class->new();

Applying to your question, all of the calls below are equivalent.

Type::Tiny::->new( … );

"Type::Tiny"->new( … );

my $class = "Type::Tiny";
$class->new( … );

Appending :: to the end has the advantage of producing a helpful warning. Say you accidentally typed

Type::Tinny::->new;

That produces

Bareword "Type::Tinny::" refers to nonexistent package at ./try line 15.
Can't locate object method "new" via package "Type::Tinny" (perhaps you forgot to load "Type::Tinny"?) at ./try line 15.
Jameson answered 27/11, 2019 at 22:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.