multi
subs, it would trigger this bug in Syntax::Keyword::MultiSub. To fix that, you had to manually patch the latter module:
--- old/lib/Syntax/Keyword/MultiSub.xs 2021-12-16 10:59:30 +0000
+++ new/lib/Syntax/Keyword/MultiSub.xs 2022-08-12 10:23:06 +0000
@@ -129,6 +129,7 @@
redo:
switch(o->op_type) {
case OP_NEXTSTATE:
+ case OP_DBSTATE:
o = o->op_next;
goto redo;
Not good. However, Syntax::Keyword::MultiSub 0.03 has been released with that patch included, so that's a relief.
However, I was still struggling with switching WebService::OpenSky to MooseX::Extended
but under the debugger, we have a serious problem.
The 'add_attribute' method cannot be called on an immutable instance at ...
When you use MooseX::Extended
, it takes years of best practices that have evolved for Moose and applies them to your Moose classes. Using this module is sort of equivalent to this:
package My::Class {
use v5.20.0;
use Moose;
use MooseX::StrictConstructor;
use feature qw( signatures postderef postderef_qq );
no warnings qw( experimental::signatures experimental::postderef );
use namespace::autoclean;
use Carp;
use mro 'c3';
... your code here
__PACKAGE__->meta->make_immutable;
}
1;
That's right: you no longer need to remember to declare your classes as immutable, or even end them with a true value. But ... automatically making your classes immutable has a trade-off. It relies on B::Hooks::AtRuntime and when using that under the debugger, the hook is triggered too early, making your classes immutable before Moose has built them! Oops.
Early versions of MooseX::Extended
fixed this by automatically disabling the immutable
behavior when running under the debugger, but I reverted that after haarg filed a ticket, explaining the issue. B::Hooks::AtRuntime was patched to fix this, but I still get the error with the new code, so this is still an unresolved issue.
That's where WebService::OpenSky
comes in. I wanted to use MooseX::Extended
in some non-trivial code, but the debugger issue kept biting me. I finally fixed it with this:
package WebService::OpenSky::Moose;
use MooseX::Extended::Custom;
use PerlX::Maybe 'provided';
# If $^P is true, we're running under the debugger.
#
# When running under the debugger, we disable
# __PACKAGE__->meta->make_immutable
# because the way the debugger works with B::Hooks::AtRuntime
# will cause the class to be made immutable before the Moose class
# is fully built. This causes the code to die.
sub import ( $class, %args ) {
MooseX::Extended::Custom->create(
includes => 'method',
provided $^P, excludes => 'immutable',
%args
);
}
I think it's an elegant little hack, with the caveat that your code behavior might change under the debugger. Very frustrating. However, for those who want to use MooseX::Extended
, I'm thinking about this change:
use MooseX::Extended
debugger_excludes => [qw/autoclean immutable/];
That disables both namespace::autoclean
and the immutable behavior under the debugger, but you have to ask for this behavior rather than have it happen silently. That being said, I don't like this proposal. We are changing the behavior and other modules can trigger $^P
to be true, so I can't guarantee we're truly under the debugger. You could have key features disabled in production code.
MooseX::Extended
is a lovely module for writing Moose classes in a safer manner, but this little nit is frustrating
Suggestions welcome.
]]>The basics are pretty easy to learn, but it gives you a good amount of power. It also allows you to easily define custom versions so you can just slap use My::Custom::Moose;
(or role) at the top or your code and it works just fine.
You can now disable just about any features in it you don't want. You can also include experimental features, such as multimethods (based on number of args) and async/await.
Check out the github repo if you'd like to contribute.
]]>MooseX::Extended
is coming along well and is ready for testing. See Introducing MooseX::Extended for a decent introduction on how to make writing your Moose code safer and easier.
]]>
It's based on years of experience being the lead designer of the Corinna project and trying to figure out how we can get a version of Moose which is safer and easier to use, including removing a lot of boilerplate. This code:
package My::Class {
use MooseX::Extreme;
... your code here
}
Is sort of the equivalent to:
package My::Class {
use v5.20.0;
use Moose;
use MooseX::StrictConstructor;
use feature qw(signatures postderef);
no warnings qw(experimental::signatures experimental::postderef);
use namespace::autoclean;
use Carp;
use mro 'c3';
... your code here
__PACKAGE__->meta->make_immutable;
}
There's not need to end packages with a true value and MooseX::Extreme
makes your class immutable for you.
But what's allowed in the constructor? I've regularly face the following problem:
package Some::Class;
use Moose;
has name => (...);
has uuid => (...);
has id => (...);
has backlog => (...);
has auth => (...);
has username => (...);
has password => (...);
has cache => (...);
has this => (...);
has that => (...);
Which of those should be passed to the constructor and which should not? Just because you can pass something to the constructor doesn't mean you should. Unfortunately, Moose defaults to "opt-out" rather than "opt-in" for constructor arguments. This makes it really easy to build objects, but means that you can pass things to the constructor and it won't always work the way you want it to.
There's an arcane init_arg => undef
pair to pass to each to say "this
cannot be set via the constructor," but many developers are either unaware of
this is simply forget about it. MooseX::Extreme
solves this by separating
has
into param
(allowed in the constructor, but you can also use
default
or builder
) and field
, which is forbidden in the constructor.
We can rewrite the above as this:
package Some::Class;
use MooseX::Extreme;
param name => (...);
param backlog => (...);
param auth => (...);
param username => (...);
param password => (...);
field cache => (...);
field this => (...);
field that => (...);
field uuid => (...);
field id => (...);
(Note: has
is still available)
And now you can instantly see what is and is not intended to be allowed in the constructor.
It works with Perl versions from v5.20.0 onwards, has CI setup on github against all major Perl versions it supports, and even has some interesting fixes to make it work in the debugger.
In short, I'm trying to apply a list of safe (and conservative) defaults to Moose--I've deliberately omitted some features that would probably be overkill--and mostly just uses normal, sane modules.
It still needs more tests, so I won't release it right away, but it reduces boilerplate and and applies defaults that are generally popular. So what I should I rename it to before releasing to the CPAN?
]]>That's right, the Tau Station MMORPG Kickstarter is live and we didn't mean to. However, apparently Kickstarter doesn't allow you to "unlaunch" a campaign.
It may not have been our launch window, but we're owning this.
Share this!
Tau Station is the world's first Biblio-RPG. It's a massive, immersive, narrative sci-fi MMO. Missions in most games are things like "kill five rabid dogs and get a dagger." BORING. Our missions are rich, immersive, short stories where you control the outcome.
It's 400,000 plus lines of Perl, with a PostgreSQL backend.
]]> If you want more technical information, here's a presentation I gave in Amsterdam a couple of years ago.If you want people to see the awesome things Perl can do, please support us. Even if you don't donate, share the kickstarter link.
Or play the game. It's free and plays in any browser (even mobile ones). It also works with screen readers and other assistive devices.
I love the universe of Tau Station. The stories are great and our narrative designers have blown me away. Our players are pretty awesome too. They've already built:
]]>I had a partially working implementation, complete with test suite. My intent was that I would finalize it, publish it, and P5P would have a working test suite to validate their results. I was following the Pugs model of writing up a working implementation to flesh out the bugs.
In this case, it was a mistake. Sawyer argued, and I agreed, that with P5P, there was a group of brilliant developers that could implement Cor, but I had to actually have a spec to implement. By focusing my time on the spec instead of the implementation, I could work on creating a beautiful OO implementation that felt "perlish" without getting constantly bogged down in implementation details. We can (and will) revisit design when the implementation details show the inevitable problems.
So in coordination with Sawyer and Stevan, I wrote a Cor spec. They shot large parts of it down pretty quickly, so eventually the current Cor spec arose.
It has some issues. Largely, we have problems with data. The data we use with objects has several different aspects we need to worry about.
Moose/Moo and friends, while being the overwhelmingly dominant choise of Modern Perl developers working on OO, suffer from conflating those various needs and trying to stuff most of that into a single has
declaration. That leads to the following, perfectly non-sensical, Moose code (which nonetheless runs just fine):
#!/usr/bin/env perl
use v5.24.0;
use warnings;
use DDP;
package Foo {
use Moose;
has attribute => (
is => 'ro',
isa => 'HashRef',
writer => 'set_foo',
required => 1,
builder => '_build_attribute',
);
sub _build_attribute {
return { this => 'value' };
}
sub this {
my $self = shift;
return $self->{attribute}{this};
}
}
my $foo = Foo->new;
say $foo->this;
my $attribute = $foo->attribute;
$attribute->{this} = 'whoops!';
say $foo->this;
my %encapsulation_violation = ( this => 'that' );
$foo->set_foo(\%encapsulation_violation);
say $foo->this;
That prints out:
value
whoops!
that
But what does that attribute even mean?
First, it's read-only, but has a writer.
Second, even without the writer, it returns a hashref which we can change and cause action at a distance by changing the state of the object.
Third, why is the attribute "required" (which implies it needs to be passed to the constructor) and yet still has a builder?
These are the sort of threads we need to disentangle. At first, we thought simply that we'd allow easy slot (data) declaration and since it was trivial for developers to quickly write accessors, it would offer an affordance for creating immutable objects which expose minimal state. And then I wrote this example of how it could be done with a mutable accessor:
method x ($x = undef) {defined $x ? self->x($x) : self->x}
See the bug? How can you set x
to undef
, if you need to?
This is better:
method x ($x = undef) {@_ == 1 ? self->x($x) : self->x}
But now we're re-exposing @_
, something we'd like to avoid, and we're making it easier for developers to write buggy code. Note that the example method I quickly wrote is something I regularly warn developers not to do! I'm very comfortable with Perl OO and I wrote a very common bug that we don't need to have. In fact, even my local sample implementation doesn't have this bug because I know it's a common, silly bug!
Sawyer has recently gotten back from a P5P event and Stevan is on vacation, so there will be a delay, but that's a good thing. We need time to sort through this and many other issues which have been raised.
Patience! 😃
]]>It's being called "Cor" to distinguish it from current OO systems.
This is an example:
You can read the full description of Cor here.
Feel free to comment on the proposal here or on the gist.
]]>Because they hired so many Python developers to work in their data science area, they had more and more Python creeping into non-data science areas. Their Python devs didn't do much Perl and vice versa. Thus, while AlphaCorp said they'd rather not split themselves over multiple programming languages, they really had no choice. And that's a problem for Perl's future.
]]> Now that Perl 6 has been renamed to Raku, many people are happy because the confusion over whether or not Perl 6 is an upgrade to Perl has been removed. However, that's not enough. We need people to use Perl, to want to write in Perl.Python has dominated the dynamic programming market and one of the many reasons is simple: data science. It's no secret that corporate interest in data science has skyrocketed:
Google Trends for "Data Science" since 2004
I've heard repeatedly from data scientists that they don't care what tools they use so long as they can do their job, but Perl is a non-starter for them. Python, however, has tons of rich libraries that data scientists can use to do their job.
If you're not familiar with data science, it's useful to understand the difference between analysis and analytics. Though data science today tends to lump all of its work under the term "analytics" (probably because it sounds more technical), that doesn't really explain what's going on.
Analysis is breaking raw data down into discreet information you can use to understand something. In short, analysis is about what happened in the past. Companies have been doing advanced analysis for decades.
Analytics, however, is the use of tools--often AI--that take existing data and predict the future. Perl's (mostly) great for slicing and dicing and analyzing data, but Python excels at analytics because it has plenty of tools for it. There's numpy, Pandas, matplotlib and tons of machine learning tools. If you want to figure out how to put them all together, here's a free Python Data Science Handbook.
Short of figuring out how to put together a top-notch data science team to build the appropriate libraries in Perl (and that takes money, time, and expertise), Perl is going to continue to fall short because one of the hottest (and legitimate!) topics in software right now is an area that Perl doesn't seem to cover very well.
It probably goes without saying that AI is closely related to this and Perl falls short there, too.
How can we fix this?
]]>I am in favor of this change, because it reflects an ancient wisdom:
"No one sews a patch of unshrunk cloth on an old garment, for the patch will pull away from the garment, making the tear worse. Neither do people pour new wine into old wineskins. If they do, the skins will burst; the wine will run out and the wineskins will be ruined. No, they pour new wine into new wineskins, and both are preserved."
Unsurprisingly, it's a Biblical quote.
But what does this mean?
]]> Well, first, it looks like raku is now going to be the official name of Perl 6.Second, it will hopefully put to rest many of the deeply divisive arguments people in the Perl community have had over the rename. Though support for a rename was overwhelming, there was a loud minority who objected. But through it all, one thing remained clear: everyone meant well.
There's more to be said there, but I think that's enough: everyone meant well. That's something everyone (including myself), could stand to keep in mind more often. Even if someone strongly disagrees with you, they're generally meaning well and we should approach things from that spirit.
But that's behind us now (hopefully). With Larry blessing the change, it's time for us to look to the future and put the past behind us.
So what does raku offer us?
Given the amazing performance improvements made in raku in the past year (and it often outperforms Perl in many areas), and the roadmap for many more performance improvements, this is a perfect time for raku to be launched as a new language.
One of the many things I love about raku is that when I've given presentations about it, I constantly hear from developers about how feature X solves a thorny problem they have, but many developers identify a different "feature X." Raku isn't a one-trick pony designed to scratch a particular itch; it's become a robust, mature specification with a powerful implementation which gives it capabilities which far surpass many other languages. And the awesome concurrency model is icing on the cake.
But whither Perl? I've already been contacted by a reporter about what the rename means for the future of Perl and raku. I've had some developers contact me asking if we can rename Perl to Perl 7 now. I think there's going to be some interesting times ahead, but I don't know how versioning is going to work for Perl yet. There are a number of viable alternatives and I expect that debate will also be contentious.
For Perl to have a new, major release, a few things should probably happen. These assume that a major release is the time when perhaps we can break backwards-compatability.
There's probably more, but those are just my thoughts off the top of my head. It's an exciting time and I'm looking forward to seeing what the future brings for both Perl and raku.
]]>Imagine the following, hypothetical Perl 5 OO syntax. Inheritance is handled via is
and inheritance order is assumed to parent class declaration order. Thus, UnlovedChild
inherits from MissingFather
first.
What do you think the output should be?
class MissingFather {
method shout() { say "I'm outta here!" }
}
class DrunkenMother {
method shout($message) { say "$message!" }
}
class UnlovedChild is MissingFather, DrunkenMother {}
UnlovedChild->shout("Where's my beer?!")
]]>
I currently have this code mostly working locally and if you run it you get an error like:
`Too many arguments for subroutine 'MissingFather::shout' at ...
That's because method resolution in Perl is based on the name of the method (or, sadly, the subroutine) and we can easily select the wrong method. If we were to identify methods by name and arity (the number of arguments), we could say that MissingFather
doesn't listen and thus has a shout/0
method while DrunkenMother
just yells back whatever you say to her via shout/1
. It's quite reasonable to expect that when UnlovedChild
shouts, his DrunkenMother
shouts back at him because we tried to call shout/1
, not shout/0
, but because we inherit from MissingFather
first, we get shout/0
and a fatal error, instead of the behavior we were looking for.
So if we go with arity-based method resolution (and C3), we can at least try to call the method the developers expect. I'm not saying this is perfect, but we can try. Even in a linear tree (A -> B -> C), calling a method on C might just skip a similarly named method in B and go straight to A because A matches the arity. This system works reasonably well for other languages, such as Clojure and Prolog. Further, because it's arity-based and doesn't require complex resolution, it should be simple to implement.
But there are some issues with this.
First, should we do this? The UnlovedChild
behavior might be arguably wrong because:
MissingFather
and DrunkenMother
as it might in the real worldSecond, if we use arity-based dispatch, what about forward declarations?
sub foo; # legal
sub foo($bar); # no signatures allowed on forward declarations
Third, how does role-based composition work with arity? If your class consumes a role with a foo/2
method and you implement a foo/1
method, is that a conflict? If it isn't, we've a hybrid of identifying methods by name or name+arity. If it is, we've gone down the road of arity-based multi-dispatch. I imagine this would likely entail very, very significant changes to the Perl core.
And if you think we should get into types, and putting those into signatures, you can read more about the complexity of finding the right method.
I would love to hear your thoughts.
]]>While the proposed name was "camelia", Damian Conway made a strong argument in favor of "raku" and it appears the community is leaning towards this name for various reasons.
This post is my attempt to summarize what's going on for those who have missed it. Any errors are, of course, Damian's. (just kidding!)
]]> What follows isn't about facts. It's about opinions. If someone feels that X is awesome, it doesn't matter if you disagree. That's still their opinion. Further, you can try to change their mind, but be aware that when someone strongly disagrees with you, you usually want to start discussion from the points you can agree on and then slowly move to the points on which you disagree. However, that's not been the story of Perl 5/6. People disagree and immediately jump to disagreements rather than trying to find common ground.The far, far too terse backstory: the Perl 6 community seems to be split between those who view Perl 6 as a sister language to Perl 5 and those who view Perl 6 as a successor to Perl 5.
The Perl 5 community, meanwhile, is split between "f*ck yeah" and "f*ck you".
To say that this issue has been bitterly divisive would be an understatement.
When Perl 6 was announced, it was seen the way that Perl 2, Perl 3, Perl 4, and Perl 5 were seen: replacements for "$VERSION - 1". Over time, it became clear that though Perl 6 was in the same family as Perl 5, a straightforward migration path was unlikely. One only needs to look at the problems with Python 2 and Python 3 and the upgrade obstacles with their minor syntactic differences to understand that an upgrade from Perl 5 to Perl 6 isn't trivial.
It was, if I'm not mistaken (if I am, remember that it's Damian's fault 😃), that Carl Mäsak, is the person who first suggested that Perl 6 be considered a sister language to Perl 5 in the way that C++ might be a sister language to C (though the upgrade path on the latter is probably easier). But that didn't satisfy some people. In fact, there were/are still people in the Perl 6 community who view Perl 6 as the successor language. To many people in the Perl 5 community, this says "Perl 5 is dead; wait for Perl 6".
Not only have many Perl 5 developers been offended by effectively being told by some Perl 6 developers that their language is dead or dying, but it had real-world financial consequences. I can't tell you how many times I've talked to potential clients who've told me "yeah, our Perl 5 codebase is old and we want to upgrade, but we have to wait for the next version (Perl 6) because any upgrade will be throwing money away."
And they've been waiting two decades. Many of them have stopped waiting and abandoned Perl.
The BBC, the world's largest broadcaster, had Perl everywhere. They decided to
ditch it. MongoDB claims that their clients aren't developing any new
projects in
Perl,
so they're ditching Perl 5 support. Other companies are continuing this trend
and this is, as potential clients have told me, because they're tired of
waiting for Perl 6. They think Perl 6 is the successor to Perl 5 and given
the name, it makes perfect sense. Combine that with the negative press about
Perl 5 and you get into "nobody got fired for buying IBM" territory, but
s/Perl/$other_language/g
.
Also (I can't remember if this was said publicly or not), but there are those who've suggested Perl 6 solutions and been rejected out of hand because "Perl".
Liz summed it up succinctly as follows:
Perl 6 was initially conceived to be the next version of Perl. It took way too long to mature to an initial release. Meanwhile, people interested in taking Perl 5 along, took back the reigns and continued developing Perl 5.
Having two programming languages that are sufficiently different to not be source compatible, but only differ in what many perceive to be a version number, is hurting the image of both Perl 5 and Perl 6 in the world. Since the word "Perl" is still perceived as "Perl 5" in the world, it only seems fair that "Perl 6" changes its name.
Since Larry has indicated, in his video message to the participants of PerlCon 2019 in Riga, that the two sister languages are now old and wise enough to take care of themselves, such a name change would no longer require the approval of the BDFL.
This is the video to which she refers:
Out of the 143 emoji responses to her initial post, 134 were clearly supportive with only 5 being unsupportive (4 being neutral).
What's worse is that Perl 6 is often tainted by the name "Perl". Routinely I see in numerous online discussions that people refuse to even consider Perl 6 because they hate Perl. Or there are younger people who think of Perl as "their grandfather's language" (in much the same way people in my generation view COBOL). So there are people in the Perl 6 community who want to change the name simply to avoid an unjustified negative perception of the language. In fact, it's been suggested that the rename of Perl 6 might allow more people to come into the language by side-stepping this issue.
So yeah, there's bitterness and the Perl community not only needs to heal, but we need to find a way forward for both languages. The suggestion to change the name of Perl 6 to "raku" is effectively designed to make this happen. Perl 5 can figure out how to get beyond the branding issue that's been plaguing it and Perl 6 can do the same thing.
Perl 6 performance has now gotten to the point where it's often comparable to Perl 5 or surpasses it. It still needs some work in this area, but the work is clear, the goals are straightforward, and Perl 6 is going to easily oustrip Perl 5 in terms of performance. If the Perl 6 community wants to rename, it's perhaps the perfect time to do so. It doesn't look like a bad choice if performance is a primary concern.
But all of this brings me to the most important point.
We're in a bubble. If you're reading this, you're probably part of the Perl commmunity. Most of what I've said above isn't news to you. But who's outside the community?
People who sign contracts. People who decide what their next project will be built in. People who decide if they're going to rebuild their legacy Perl 5 system in Perl 5 or a language they don't have to justify to their bosses.
In short, for many people the debate comes down to whether or not they have a financial future in working with a language they love. But they don't get to make that decision; people who control the purse strings do. Trademark law often deals with the concept of confusing similarity because the market can easily get confused when two names are so very similar. And as any competent salesperson can tell you, when your customer is confused, they don't buy.
So that's where we are. We have a very confusing issue which, it appears, that much of the Perl 6 community agrees needs to be sorted. Much of the Perl 5 community appears to feel the same way.
None of this should be intended as a critique of what's gone on in the past because, frankly, I don't think anyone expected that this confusion would drag out for two decades. The various shifts we've made over time (from successor, to sister language, to creating an alias for the language), have led to the point we're at now.
As for myself: I would definitely program in Perl 6 if I could make a living at it. Right now that's not possible, so I'm still developing in Perl 5. I think Perl 6 corrects many core issues with Perl 5 (powerful OO, gradual typing, working concurrency, robust signatures, a cleaner syntax, etc.), but I also love the quirks of Perl 5. Sue me.
So let me end with a quote from Neo from the Matrix:
I know you're out there. I can feel you now. I know that you're afraid... you're afraid of us. You're afraid of change. I don't know the future. I didn't come here to tell you how this is going to end. I came here to tell you how it's going to begin. I'm going to hang up this phone, and then I'm going to show these people what you don't want them to see. I'm going to show them a world without you. A world without rules and controls, without borders or boundaries. A world where anything is possible. Where we go from there is a choice I leave to you.
Update: The above quote from the Matrix was intended solely to be vaguely humorous, vaguely relevant, and not at all serious. However, as my 8-year old daughter is quick to remind me: "your jokes aren't funny, papa." As with my daughter, please take it as a joke that lay there and twitched (though I suspect she wouldn't phrase things quite the same way).
]]>Long version: people have been asking me why I've not been as visible in the Perl community as I used to be (including at least one person asking if I was still involved in Perl). Well, that's a long story.
]]> Our company, All Around the World, is called in when there's a hard problem to solve, or a company simply doesn't have enough resources to get things done. We work remotely and I even give guidance on how to build and manage remote teams. Keeping clients happy is important, but very time-consuming. Ironically, companies approach us because of my heavy involvement in the Perl community, and this lessens the amount of time I have for the Perl community. But I've squeezed it in anyway.One simple hack I wrote has saved so much grief with this process. I can run this from the command line:
bin/dev/rename-module.pl Veure::Old::Module::Name Veure::New::Module::Name
And it automatically renames the module for me, updates all references to it, creating directories as needed, renaming test classes built for it, and uses git
.
It is, of course, a bit of a hack and obviously uses a few custom tools we've built ourselves, but curiously, it hasn't botched a rename a single time, including one I did this morning which resulted in a diff of over 1,000 lines.
Though "lines of code" isn't a great metric, if you count our Perl, our Javascript, our CSS, and our templates, we're pushing in on half a million lines of code in Tau Station. Even tiny tools like this can make life much easier. Now that we're not afraid to make sweeping namespace reorganizations, it's easier to keep on top of technical debt.
]]>By now you've probably heard of the Object-Relational Impedance Mismatch, which is just a fancy way of saying "collections of objects and databases aren't the same thing."
One problem which is particularly difficult is handling "syndicate credits". Syndicates ("guilds" in other MMORPGs) can tax their members and every time a member gets credits deposited in their bank account, a certain percentage goes to the syndicate. So let's say two syndicate members each earn 100 credits at the same time, paying 10% tax to their syndicates. It can look like this:
Logically, the syndicate should end up with an extra 20 credits. But if you do a naïve:
$syndicate->update({ credits => $credits + $tax });
... you can easily wind up with 10 credits because the "Process #2" read the total credits before "Process #1" wrote them out. Fortunately, there's an easy fix to this.
]]> We built a system that allows us to write declarative Perl to describe these behaviors and this system does a "select for update" on affected rows when it starts. Thus, if I perform an action that impacts my syndicate, the syndicate record is locked until I'm done.That turned out to be problematic because with many syndicate members, many people were trying to get a lock on the syndicate record, even if they weren't using it! We now have a simpler solution that we've dropped in our dbic base class:
=head2 C<atomic_increment>
$syndicate->atomic_increment(
{
experience => $added_xp,
credits => $added_credits,
}
);
This method takes a hashref and will increase the value of each field (key) by
its value. For the above, it is equivalent to:
UPDATE syndicate
SET experience = experience + $added_xp
credits = credits + $added_credits
WHERE syndicate_id = ?
This ensures that our update will be against the current value of the field
and not just against whatever value our object has when it was created (in
other words, if something else changes those values, this method is safer).
Note that this will generate both an C<UPDATE> and a C<SELECT>.
=cut
sub atomic_increment ( $self, $fields ) {
my %fields = %$fields; # shallow copy
my $dbh = $self->result_source->schema->storage->dbh;
foreach my $field ( keys %fields ) {
my $increment = $fields{$field};
# paranoia. This should never come from untrusted data, but just in
# case ... (thanks Mark!)
my $quoted_field = $dbh->quote_identifier($field);
# if you want subtraction, pass in a negative number
$fields{$field} = \[ "$quoted_field + ?", $increment ];
}
$self->update( \%fields );
# if we don't do this, the values of each field will be the numerical
# value of the reference
$self->discard_changes;
}
Note that because the value we're using is a reference and not a string, DBIC knows that we want to use that array reference to generate literal SQL.
Yes, that's two extra database hits we didn't have before (but we have a potential solution to that, too), but it allows us to forego most locks. And because the SQL update is credits = credits + $added_credits
instead of credits = $new_value
, we don't have to worry that our dbic object grabbed stale data.
If you want this quality for your code, don't forget to drop me a line. We do awesome things with DBIx::Class
, Catalyst
, and other technologies. We'll fix your database while we're at it.
use strict;
use warnings;
use v5.24;
use feature "signatures";
no warnings 'experimental::signatures';
use utf8::all;
use Carp;
Both of those approaches are dead wrong. The "ad hoc" pragma list means it's hard to be sure what features are or are not available. The "standard boilerplate" approach means cutting-n-pasting and then hating yourself when you have to change that standard boilerplate.
Modern::Perl is a nice middle ground for avoiding this, but it may not be the features you want. For example, our free-to-play narrative browser game, Tau Station, doesn't use the C3 MRO because we don't use multiple inheritance (note: we enforce C3 on our DBIx::Class classes, of course).
Instead, we have our custom boilerplate, wrapped up in Veure::Module
.
package Veure::Module;
use 5.24.0;
use strict;
use warnings;
use feature ();
use utf8::all;
use Veure::Carp;
use Import::Into;
sub import {
my ($class) = @_;
my $caller = caller;
warnings->import;
warnings->unimport('experimental::signatures');
strict->import;
feature->import(qw/signatures :5.24/);
utf8::all->import;
Veure::Carp->import::into( $caller, qw(carp croak vcarp confess) );
}
sub unimport {
warnings->unimport;
strict->unimport;
feature->unimport;
utf8::all->unimport;
Veure::Carp->unimport;
}
1;
__END__
=head1 NAME
Veure::Module - Use modern Perl features
=head1 SYNOPSIS
use Veure::Module;
=head1 DESCRIPTION
This module is a replacement for the following:
use strict;
use warnings;
use v5.24;
use feature 'signatures';
no warnings 'experimental::signatures';
use utf8::all;
use Veure::Carp qw(carp croak);
Now, for all of our modules, we can just drop use Veure::Module;
at the top and our development work is much easier, and standardized.
The use of strict
and warnings
is obvious.
utf8::all
might be controversial, but we've found it helpful.
Veure::Carp
is our internal version of Carp
(if called from Template Toolkit, it tells us the exact template name and also dumps environment variables).
A future version of this might include the autodie
pragma. We only need to add it in one place and it should be backwards-compatible (we'll see).
The subroutine signatures feature, however, is the killer feature. If you're not using them, you should be, even if they're still experimental. We have a few clients who've made the switch and I've spoken to many others who have, too. This is the one experimental feature that more and more Perl shops are adopting. The reason is simple: your code will be more correct. Pass in the wrong number of arguments and it will die a horrible death. This has saved us so much grief in production that it's worth risking its experimental status.
When you're working on an "enterprise" code base, having standards is critical. I know that's something that many Perl devs don't like to hear (I've seen a dev get let go because they wouldn't adjust their brace style), but if you don't have standards, codebases get messy quickly. It's never too late to start. I know that Veure::Module
won't meet your exact needs, but the idea of building standards into a single module is worth the trouble. Combine that with Perl::Critic
and Perl::Tidy
and you're well on your way to reigning in the headache of large codebases.
And for what it's worth (I know that LOC is a terrible metric), Tau Station is now around 1/3 of a million lines of code, if you also count what we've built on the front end.
As always, drop me a line if you need expert Perl software development.
]]>