Simulating multiple, lazy attributes
Lazy attributes are wonderful. They allow us to postpone generating attribute values for any number of reasons: it’s expensive and we don’t want to do it unless we need it, it should be initialized after instantiation because it depends on other attributes, etc. And it does this without our having to worry about the value being around: if we need it, it’ll be generated on the fly without any extra effort on our part.
As an example, let’s say we have a simple config file that defines key/value pairs. We need to find out the author’s name, which has the key ‘author’ in the config file. We could create a lazy attribute as such:
Simple, yes? Now, whenever you need the authors name, you have it.
So, let’s now say that a couple days later, you realize that you also need to get the author’s email from the config file (key ’email’):
Except… Hm. We’re now loading and parsing the config file twice. Though it’s likely to be very low cost to do that (assuming a local, simple config file on the filesystem), it still feels wrong. Besides, what happens when you run into a situation like this and the base set of data (e.g. what load_config() is returning) is expensive to generate?
There are a couple things we could do here: we could create a config attribute, make it lazy and load the config, then change our attributes to pull their value out of the config attribute; we could create a new class to handle the config, and setup a config attribute that delegates to it; etc, etc. That’s a lot of work, however, and work that doesn’t need to be done if we leverage other parts of Moose correctly.
One of the easy, often overlooked ways to do this is to use the tools Moose itself gives us: native attribute traits and accessor currying.
In the above, we see one attribute being created. Note the
is => 'bare';
this keeps the attribute from generating the reader, writer or accessor
methods. We’re applying the “Hash” native trait, and using the delegation it
provides to create custom accessors that pull from the hash without needing
the end user to provide keys to a generic lookup.
Note that with either of these approaches gives us the same interface to someone using our class:
This isn’t always appropriate, but if you ever find yourself with multiple attributes whose values can all be generated through one builder, then this may be a good starting approach.
It’s certainly the laziest one :)