The pattern is based on type safety[?], which is itself based on Abstract classes, or the concept of types it puts forward, rather. We confound the subject with Anonymous subroutine objects[?]. We use type safety[?], Class as type code[?] and New object from existing[?]. Run and return successor[?] is a fundamental idea to the Lambda closure[?] idea of currying, and we demonstrate it in the second example.
Currying is a universe of single argument functions. This sounds absurd and useless, and would be except for the tenets of Lambda closures. This pattern develops when state is accumulated incrementatally: see accumulate and fire. Accumulate and fire comes about when there are too many arguments to pass all at once. Attempting to pass them all at once loose us the flexibility of being able to set things up, run, change a few things, run, and so on.
For example, lets say we're playing roulette. We can pick a color and perhaps a few numbers.
package Roulette::Table;
sub new {
my $class = shift; my $this;
# if new() is called on an existing object, we're providing additional # constructors, not creating a new object
if(ref $class) { $this = $class; } else { bless $this, $class; }
# read any number of and supported type of arguments
foreach my $arg (@_) { if($arg->isa('Roulette::Color')) { $this->{'color'} = $arg; } elsif($arg->isa('Roulette::Number')) { my $numbers = $this->{'numbers'}; push @$numbers, $arg; } elsif($arg->isa('Money')) { if($this->{'money'}) { $this->{'money'}->combine($arg); } else { $this->{'money'} = $arg; } } }
return $this;
}
sub set_color { new(@_); } sub add_number { new(@_); } sub add_wager { new(@_); }
The constructor, //new()//, accepts any number or sort of object of the kinds that it knows about, and skuttles them off to the correct slot in the object. Our //set// routines are merely aliases for //new()//. //new()// may be called multiple times, directly or indirectly, to spread our wager over more numbers, change which color we're betting on, or plunk down more cash. I don't play roulette - I've probably butched the example. Feel free to correct it. Use the little //edit// link. People won't be doing everything for you your entire life, atleast I hope.
We still have the problem of having an object exist in an indeterminate state. If we apply AnonymousSubroutineObjects, we get something much closer to the original idea of currying. Rather than storing state in an object as it is built up, store it in a LambdaClosure that is object aware:
package Roulette::Table;
use MessageMethod;
sub new { my $class = shift; my $this; my $curry;
bless $this, $class;
$curry = MessageMethod sub {
my $msg = shift;
if($msg eq 'spin_wheel') { die "Inconsistent state: not all arguments have been specified"; }
if($msg eq 'set_color') { $this->{'color'} = shift; }
if($msg eq 'add_number') { $this->{'numbers'} ||= []; my $numbers = $this->{'numbers'}; push @$numbers, $arg; }
if($msg eq 'add_add_money') { if($this->{'money'}) { $this->{'money'}->combine($arg); } else { $this->{'money'} = $arg; } }
if($msg eq 'is_ready') { return 0; }
if($this->{'money'} and $this->{'color'} and $this->{'numbers'}) { return $this; } else { return $curry; }
};
return $curry;
}
sub spin_wheel { # logic here... }
sub is_ready { return 1; }
This second example doesn't support repeated invocations of //new()// to further define an unfinished object. It could, but it would detract from the example. Add it for backwards compatability if for any reason. More radically, we don't accept any constructors. We return an entirely new object that has the sole purpose of accepting data before letting us at the actual object.
Representing two different states of an object with two different objects is the subject of an ongoing debate as well as StateVsClass.
Rather than using TypeSafety to check the class membership of objects passed in, we could just as easily accept NamedArguments. The choose is a matter of what feels right, and what is adequate without being overkill.
In brief, returning a custom object, partially configured by some argument, ready to either do work or accept more configuration, is the act of currying. More correctly, constructing a function to accept single arguments and return another function, or converting an existing function to such, is currying.
sub create_roulette_table { my $color; my $money; my $numbers; return sub { $color = shift; return sub { $money = shift; return sub { push @$numbers, shift; return sub { # play logic here }; }; }; }; }
# to use, we might do something like:
my $table = create_roulette_table()->('red')->('500')->(8); $table->(); # play $table->(); # play again
# or we might do something like:
my $table_no_money = create_roulette_table()->('red')->('500'); my $table; $table = $table_no_money->(100); $table->(); # play $table->(); # play again -- oops, lost everything $table = $table_no_money->(50); $table->(); # play some more
This is stereotypical of currying as you'd see it in a language like Lisp. The arguments are essentially untyped, so we take them one at a time, in a specific order. Also like Lisp, the code quickly migrates across the screen then ends aburptly with a large number of block closes (the curley brace in Perl, paranthesis in Lisp). The Lisp version makes heavy use of RunAndReturnSuccessor. If we wanted to adapt this logic to spew out GeneratedMethods, where each method generated wasn't tied to other generated methods, we would need to explicitly copy the accumulated lexical variables rather than simply binding to them. For example, //my $color = $color; my $money = shift;// would prevent each anonymous routine returned from sharing the same //$color// variable, although without further logic, they would all have the same value. This amounts to the distinction between instance and class data.
Understanding the Lisp-ish example isn't critical to using this idea. It merely serves to give us some context to the idea, and a counter-example to the ObjectOriented approach. It also clearly demonstrates the advantages of having partially constructed objects laying around: we don't need to construct a whole new table just to put some more money down, but we have the power of creating objects to represent state at the same time.
Search Encyclopedia
|
Featured Article
|