So, I got to thinking about stuff. One of the more annoying things about writing nicely decoupled objects and applications are those occasions where you want an object to be able to create objects in another class. Say you've provided a singleton interface to your logging system. The naive implementation goes something like:
require Logger; has $.logger; method logger { $.logger //= Logger.new } method whatever { ./logger.debug("About to do stuff"); .do_some_stuff; ./logger.debug("Did stuff"); ... } But that's problematic because we've backed knowledge of the particular class that handles logging into our class. The bad solution to this is to subclass our class if we want a different Logger class (that conforms to the same interface). A slightly better solution is to parametrize the logger class name, probably with a class variable -- but doing that means you have to remember to set the variable for every class you use. Or you could use a global, but globals are bad... It'd be really cool if you could essentially write the naive implementation above and have your application/test/webserver harness decide which particular class will be taken to be the concrete implementation. Something like this: role Logger is Injected { method debug {...} method info {...} ... } Logger::DBI does Logger { ... } Logger::Debugger does Logger { ... } Then the harness that actually sets up the application would simply do use Logger::DBI :dsn<...>, :user<...>, :password<....> and Logger::DBI would install itself as the default Logger class. The question is, how does one write Injected to make this work? Or what features do we need to be able to write Injected? Thinking about this, it shouldn't be too hard to implement 'Injected' in Perl 5: sub Injected::AUTOLOAD { no strict 'refs'; die "If something's already been instantiated you're screwed" if ref($_[0]) or $Injected::injecting; local $Injected::injecting = 1; my $target_role = $_[0] my @possibles = grep {/(.*)::$/ && $1->isa($target_role)} keys %::; die "Too many possibles" if @possibles > 1; if (@possibles) { *{"$target_role\::} = *{$possibles[0]}; } else { my $default_package = $target_role->default_class; eval "require $default_package" or die "Can't find a default package"; *{"$target_role\::"} = *{"$default_package\::"}; } &{$target_role->can($AUTOLOAD)}(@_); } NB, that's completely untested code, but it, or something like it, should work.