Programming with Moose/Problems solved/Scalar-Defer

Scalar::Defer's lazy is a method of computing a value once and caching it away, while Memoize is a method of storing the functions input and output into a cache table for the purpose of future calls. Both of these functionalities are largely covered by Moose's native lazy=>1, default=>. This deadly combo (lazy and default) native to Moose caches a function's output, and delays the execution until the first runtime request.

Procedurally, a function gets arguments or operates on globals -- if it does neither than it is presumably immutable[1]. Object Orientationally, a method[2] often gets its variables from the object (its environment) and sometimes modifies the object. Take for instance a method like ->shout, which might check to see if the object can shout() and then it might shout what the object is $self->thinking. With this in mind, let's see what Memoize does...

‘Memoizing’ a function makes it faster by trading space for time. It does this by caching the return values of the function in a table. If you call the function again with the same arguments, "memoize" jumps in and gives you the value out of the table, instead of letting the function compute the value all over again.

You might ask, what's wrong with that? Well, for starters it isn't object oriented[3]; and, doing anything with a method reference is ugly and nasty and should be avoided. Moose's Lazy/Default combo replaces a small but significant subset of Memoize's functionality. If you simply want to cache the return of a runtime function then Moose's native functionality might better suite you.

Finally, the purpose of Memoize is always to make something faster and that isn't Moose's goal in any way shape or form. What functionality Moose delivers with lazy/default is for a totally different reason, all of which more so pertains to programmer productivity which is exactly the goal of Moose. The logical drawback of the way Moose goes about this is that the object will cache only one call to the function, requiring many objects for many calls.

As for the Scalar::Defer, this probably better fits the functionality Moose provides, because the goal of Scalar::Defer, directly collides with that of Moose's Lazy/Default. Let's sum up with a list of things the Lazy/Default combo can be said to do.

  • Calculates the function only once
  • Caches the output
  • Defers evaluation until the first call

The old way

edit

I've (Evan Carroll) have personally never seen memoize used before on a method. Not to say it couldn't be done, or hasn't been... But, rather than contrive an example to make Moose more of the win, I'll present you with a typical vanilla Memoize. That goes to say this could be made more similar in theory, but not in practice.

This example will use lazy/default for the purpose of delaying execution and storing the result; it is often used simply for this. However, it is also often used for delaying a database connection until needed in runtime. These are two totally separate applications of the same technology.

An Example (memoize)

edit
use Memoize;

memoize( 'complex_operation' );

sub complex_operation {
	my $num = shift;

	my $result = $num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
	;

}

print complex_operation( 1 );

Invoking the Moose

edit

Moose is going to get the same effect a slightly different — object oriented — way.

An Example (lazy/default)

edit
package MyMoose;
use Moose;

has 'foo_var' => (
	isa  => 'Int'
	, is => 'ro'
);

has 'foo' => (
	isa       => 'Int'
	, is      => 'rw'
	, lazy    => 1
	, default => \&build_foo
);

sub build_foo {
	my $self = shift;

	my $num = $self->foo_var;

	my $result = $num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
		+$num+$num+$num+$num+$num+$num+$num
	;

}

package main;

my $m = MyMoose->new({ foo_var => 1 });

print $m->foo;

Another Example

edit

I feel obligated to show you a very popular application of this technology.

package MyApp::DB;
use Moose;

use DBI;

has 'dbh' => (
	isa       => 'Object'
	, is      => 'ro'
	, lazy    => 1
	, default => \&build_dbh
);

sub build_dbh {
	my $self = shift;

	my $dbh = DBI->connect(
		'dbi:Pg:dbname=myDB;host=localhost'
		, 'username'
		, 'password'
	) or die "Can not connect to DB $DBI::errstr";

	$dbh;

}

New Syntax

edit

For clarity and those who just can't learn by example:

lazy
Lazy can be reduced to a simple: do this at runtime. It permits delayed execution.
default
Default is always used in combination with lazy, tells Moose what should happen when the first call to the getter is made. The combination of lazy/default essentially does a few things:
  1. Delays execution until runtime
  2. Caches away variable for later calls
  3. Permits dynamic overload

So if you don't want to the default/lazy combo, just send it something.

References

edit

Memoize v1.01: POD. Date 2001-09-21. Access date 2007-10-21

Footnotes

edit
  1. ^ An immutable function simply means for every X you will get the same Y. All functions are ispo facto immutable if they have no variables (X) to influence their output (Y). If you have to think about this you should probably retire from programming.
  2. ^ A method is simply a function in the objects namespace.
  3. ^ Not everything has to be object oriented! This isn't java; we support multiple paradigms in our language, and we know that.
  4. ^ So you can violate this immutability by calling the ->clearer, we will talk about this later.