Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Factory Pattern in Perl6

by hardburn (Abbot)
on May 13, 2016 at 22:39 UTC ( #1163000=perlquestion: print w/replies, xml ) Need Help??

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

I'm trying to write a Factory pattern in Perl6, and I need to pass the same arguments to the constructor of each class. All the examples of Perl6 Factories out there right now seem to have neglected trying to pass arguments.

In Perl5, I would probably set a variable to hold the classname, and then pass the args after we figure out where we want to go.

my $class = $type == 0 ? 'ClassA' : $type == 1 ? 'ClassB' : 'ClassC'; my $obj = $class->new( foo => 1, bar => 2 );

Alternatively, you can wrap up the arguments and rely on list flattening to take care of it:

my %args = ( foo => 1, bar => 2 ); my $class = $type == 0 ? ClassA->new( %args ) : $type == 1 ? ClassB->new( %args ) : 'ClassC';

My first attempt in Perl6 followed the first method above:

my $class; given $type { when 0 { $class = ClassA } when 1 { $class = ClassB } default { $class = ClassC } } my $obj = $class.new( foo => 1, bar => 1 );

But this resulted in an object from Any, not the class I wanted.

I also took a stab at the second method, attempting to use the | list flattening operator:

my @args = ( foo => 1, bar => 1 ); my $obj; given $type { when 0 { $obj = ClassA.new( |@args ) } when 1 { $obj = ClassB.new( |@args ) } default { $obj = ClassC.new( |@args ) } }

Along with a few variations, most of which failed with Default constructor for 'ClassA' only takes named arguments.

Help for either option would be enlightening.


"There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

Replies are listed 'Best First'.
Re: Factory Pattern in Perl6
by raiph (Chaplain) on May 14, 2016 at 03:26 UTC
    (update: my first guess was wrong. See rest of this thread.)

    My first guess is that your class declarations (which you didn't list) declare attributes without a public accessor rather than with one (ie you're using has $!foo instead of has $.foo). The official docs touch on this here. I'll discuss this more if it turns out to be the issue and you reply here requesting more info.


    You didn't include enough code for me to confidently figure out what's going wrong. The first method works for me:

    use v6.c; say $*PERL.compiler.version; class ClassA {}; class ClassB {} class ClassC { has $.foo; has $!bar; }; my $class; given 2 { when 0 { $class = ClassA } when 1 { $class = ClassB } default { $class = ClassC } } say my $obj = $class.new( foo => 1, bar => 1 );

    This displays:

    v2016.04 ClassC.new(foo => 1)

    The second method should work too. That's incorrect. An update to correct my mistake follows. To insert named arguments you need a container like my %args, eg:

    class ClassC { has $.foo } my %args = :foo; ClassC.new(|%args)

    A Positional container as declared by my @args inserts positional arguments into an argument list. The "Default constructor ... only takes named arguments" comes up if you call a .new method that, through inheritance, calls the positional argument version of the new method from the root class Mu, eg:

    class C {} C.new(1)

    If you want a constructor that takes positional arguments you have to write one yourself like this one declared in the Duration class shipped with Perl 6:

    class Duration ... { has Rat $.tai = 0; ... method new($tai) { self.bless: tai => $tai.Rat } ... }

    So the Duration class's .new can accept a positional argument, eg:

    say Duration.new(22); # Duration.new(tai => 22.0)

    Hopefully that's of some use.

      Thanks for your reply. I had to set this project aside for a little while, but I was able to return to it tonight and whittle down the actual problem.

      After some playing around, the reason turned out to be the specific object being tested having a BUILDALL. Commenting out the BUILDALL method allowed the object to be constructed in the right class.

      To narrow it down from there, the BUILDALL started by calling callsame, as discussed in https://doc.perl6.org/language/objects. Changing that to nextsame (as shown in https://doc.perl6.org/language/traps) allowed the object to be constructed as expected.


      "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

        If commenting out a custom BUILDALL fixes things then, almost certainly, the custom BUILDALL has a bug. If you share it I'll see if I can spot a bug.

        nextsame means about the same as callsame; return (but I think the compiler avoids the additional callframe on the stack), i.e. call the next multi candidate (with the exact same arguments) and don't bother running any more of the code that follows the nextsame.

        I would expect a multi method that starts with a nextsame to behave as if the method didn't exist, i.e. exactly the same as just commenting the method out. So your s/callsame/nextsame/ unfortunately doesn't narrow things down.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1163000]
Approved by stevieb
Front-paged by BrowserUk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (6)
As of 2019-09-22 09:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    The room is dark, and your next move is ...












    Results (273 votes). Check out past polls.

    Notices?