in reply to Re: Override new in Moose for flyweight objects
in thread Override new in Moose for flyweight objects

I'd argue that "new", especially in perl, is that factory. That being said, I don't have a problem with renaming it, but I'm not sure there's a better name. At one point, I had the same issue, just without Moose partly because it didn't exist at the time, where I wanted to load different objects (from disk, but the source didn't really matter), of a limited number of types - so I'd have Product->create("productName") which would load the product if it isn't already, store it in the cache, and return the cached version. (And similar for other types, using a common loader in a common base class.)

But, as a general rule, I see new as a factory that generates the required objects. Which does make it marginally different from (and significantly more useful than) the new keyword in other languages. Conceptually, I'm asking for an object. But the fact that, under the covers, magic is done, usually doesn't matter. If I'm asking for an immutable object, then the fact it gets reused on each call to new is totally and utterly immaterial. And because of this, I can also just re-use it in other places that don't know that this is reusing objects from a cache, that expect to call new and get objects back. Because they call new and get objects back. It duck types perfectly. What more do I need? But if I rename it, then I lose the duck typing, and someone calling new getting new objects for a class where that is the wrong thing means that it no longer duck types properly, and that is its own host of problems.

I'm not saying it's perfect, but, in my experience, it solved more problems to leave new duck typing what I mean than to rename it. In the above case, I ended up having create just call new, or maybe the other way around, I don't remember now, just to get the duck typing working, and things were much simpler after that. After all, that's why I used perl - to make my life easier. If I wanted hard, I'd use Java. :)

Replies are listed 'Best First'.
Re^3: Override new in Moose for flyweight objects
by tobyink (Canon) on Apr 08, 2020 at 03:23 UTC

    Ah, but Zydeco's factory methods don't make things hard. They actually save you a whole keystroke!

    package MyApp { use Zydeco; class Widget { has id; } } my $foo = MyApp::Widget->new( id => 1 ); my $bar = MyApp->new_widget( id => 2 ); # factory: one character few +er!

    Implementing the flyweight pattern:

    package MyApp { use Zydeco; class Widget { has id; factory new_widget ( Int *id ) { state %objects; $objects{ $arg->id } //= $class->new( %$arg ); } } } my $foo = MyApp->new_widget( id => 42 ); my $bar = MyApp->new_widget( id => 42 ); die unless $foo == $bar;

      I don't measure hard by keystrokes. That's not irrelevant, but there are more important things than keystrokes. Don't take this the wrong way, I'm not advocating Cobol or Java here, but there are much bigger deals than keystrokes.

      Now, I also realise you're probably just playing with a bit of sarcasm, so I just want to point out where traditional new wins out. Maintainability. Every perl programmer expects to create new objects through new. That's why we have that convention - we stole it from other languages. It made things really easy. There was nothing inherent in perl that says it must be new, we just do it by convention. And, as I said before, it's not even technically correct, it's only loosely analogous, since perl's constructors, whether called new or something else, are more factory like (since it's actually bless doing the work, technically, not new, and we actually call that in our constructors, unlike every other language where the object reference is created for you prior to the constructor being called, which makes this much more like an object factory in other languages). However, regardless of the mechanics, it's still the convention.

      This means that the learning curve to other objects that also use new as their factory is that much less. It means that code that just calls new and expects an object back Just Works. Following convention means it's easier for everyone.

      That you do something weird under the covers inside your own "private" classes, types that aren't intended to be instantiated directly, but through other factories that are more traditional in their factoriness (yes, that is now a word), doesn't matter quite so much. Though even there, I would still encourage following convention as much as possible for the guy who gets to follow you in maintaining that package (having inherited a couple of modules over time, I always appreciate when there is little to learn about its innards because most of it matches general perl convention).

      And, in your specific example, which I also understand is a very tiny bit of a significant ecosystem providing some DSL to provide a lot of functionality, I would point out, what does that keystroke save? Because I'm gonna have to look it up each time to know which non-standard constructor to use. The problem here, at least from my perspective, is unnecessary mental load. And by unnecessary, I mean that it doesn't seem to provide any benefit over the standard convention, which makes it more to remember, even if convention is that one extra keystroke longer. I already have to remember all the new ways of doing things in the rest of your ecosystem, but those, at least at first glance, look to provide functionality and thus value - bringing down a lot of boilerplate that I might otherwise skip and thus not check things that I can use your system to handle for little cost. So learning and remembering that stuff seems to be likely to be a worthwhile addition to my knowledgebase. (I'm assuming - I haven't looked that closely, and so I am only going on the quality of code I've seen from you in the past. I don't know you for generally writing Acme-style or -quality code.) But that new constructor style... well, perl definitely allows it. I'm just not seeing the benefit.

      But maybe that's just me. Most of my professional life has been more about maintenance than writing new code, and so that's just what I see.

        The main motivation is not to save one keystroke (that just ends up happening because "_" is one keystroke fewer than "::"). The main motivation is dependency injection. When you do MyApp::Widget->new() you are hard-coding the class name. Hard-coding is code smell. There are many Perl programmers who would agree that hard-coding is bad, but don't even think of using class names like that as hard-coding. But it is hard-coding and it makes your code less adaptable. The idea in Zydeco is instead of doing MyApp::Widget->new(), you do $self->FACTORY->new_widget(). Now the widget class isn't hard-coded, and if you need to use a different widget class (such as a mock widget for your test suite) you can just wrap new_widget.

        package MyApp { use Zydeco declare => [qw(TestingRole)]; my $testing = true; role TestingRole { ...; } class Widget { ...; } if ($testing) { around new_widget (@args) { my $widget = $self->$next(@args); Moo::Role->apply_roles_to_object($widget, TestingRole->role); return $widget; } } } my $factory = 'MyApp'; print $factory->new_widget, "\n";

        (Note that the declare bit it only so we can declare TestingRole as a bareword for use later on. In general you don't need to declare the existence of roles and classes beforehand.)

      Of course, if what you really care about is the one extra keystroke you could use the old package delimiter: MyApp'Widget->new(id => 1)

      (just kidding)