Re: Dependency Injection
Larry Wall [EMAIL PROTECTED] writes: On Wed, Jul 06, 2005 at 11:47:47PM +0100, Piers Cawley wrote: : Or you could use a global, but globals are bad... Globals are bad only if you use them to hold non-global values. In this case it seems as though you're just going through contortions to hide the fact that you're trying to do something naturally global. You might argue that a singleton object lets you hide the implementation of the delegation from the world, but there's really not much to implement if you just want to tell the world that whenever the world says LOGGER the world is really delegating to LOGGER::DBI. Plus if you really need to reimplement you just vector the world through LOGGER::SELECTOR or some such. The thing about the version where the language handles the vectoring through Logger, or whatever, is that you can retrofit such indirection without having to make sure all your code uses the global. For instance, in the Test::* world, there's a huge number of test modules that all play nicely because they work with Test::Harness, and that's fine until you reach the point where you want to specialize Test::Harness itself (say you want to write a graphical test runner). With the global variable approach, you have to alter everything that uses Test::Harness, with an injection approach, you only have to make changes to Test::Harness itself. Hmm... thinking about this a little further, the interface I proposed can't be as automagical as simply loading a class that injects into your injectable class because said class could well inherit from another injecting class. You would have to make it a two step process like: use InjectableClass; use InjectingClass; InjectableClass.inject_with(InjectingClass); What I'm after is another way to hide implementation details from client classes and allow programmers to write natural code. One nice thing about a variable is that you're guaranteed to be able to temporize it: temp $*LOGGER = pick_a_logger(); I'm not sure that, in the particular case, that's all that useful. It'd be cool if, say in a debugging situation, I could say that, from now on, all messages to Logger from class Foo are actually dispatched to InstrumentedLogger, and then to turn it off again, but I'm not sure how I'd do that with a global. $Package::Name::OUR::*LOGGER = ::InstrumentedLogger perhaps?
Dependency Injection
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.
Re: Dependency Injection
Piers Cawley wrote: 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: FWIW this is exactly what I would like to achieve with the Date objects; allowing code that doesn't care about which Date representation they want the ability to just say any Date representation. Sam.
Re: Dependency Injection
On Wed, Jul 06, 2005 at 11:47:47PM +0100, Piers Cawley wrote: : Or you could use a global, but globals are bad... Globals are bad only if you use them to hold non-global values. In this case it seems as though you're just going through contortions to hide the fact that you're trying to do something naturally global. You might argue that a singleton object lets you hide the implementation of the delegation from the world, but there's really not much to implement if you just want to tell the world that whenever the world says LOGGER the world is really delegating to LOGGER::DBI. Plus if you really need to reimplement you just vector the world through LOGGER::SELECTOR or some such. One nice thing about a variable is that you're guaranteed to be able to temporize it: temp $*LOGGER = pick_a_logger(); On the other hand, there's no reason a role can't play with a global variable, and arbitrate the use of the global among all the classes that want to delegate through the global. But a role is not allowed to function as its own singleton object, because roles don't define objects. On the gripping hand, the package holding the role's namespace is a perfectly fine spot for such singular information. Every named package/module/class/role/subtype is a kind of global singleton hash of the symbol table persuasion. But if globals are inherently bad, we should remove them from Perl 6. Hmm, this raises the question of whether a role-private class variable actually belongs in the class's or in the role's namespace: role Baz { our $:x; } FooBar does Baz; BarFoo does Baz; Does $:x end up being a private global in the Baz package or a global shared between the FooBar and BarFoo packages? That kinda goes along with the question of whether attributes in general belong more to the role or the class into which the role is composed. It seems like all those cases can be useful, so maybe we need a syntax to distinguish role attributes from class attributes. To think of it another way, which delarations are to be taken literally, and which generically? And which way should be the default? For this particular use, you want some semantics resembling: role LOGGER { role:our $:logger handles Any; role:submethod BUILD ($logger) { fail Logger already set if defined $:logger; $:logger = $logger; } ... Not necessarily recommending that syntax, of course. Could just default declarators to class semantics and force role declaratoins to be explicit: role LOGGER { our $:LOGGER::logger handles Any; submethod LOGGER::BUILD ($logger) { fail Logger already set if defined $:logger; $:logger = $logger; } ... But that seems a bit klunky, especially given the way package names and twigils interact. And don't even think about using $?ROLE there. Plus we've never let you use our on a qualified name before. This really seems to be somewhat orthogonal to everything else, so maybe we need some orthogonal syntax: role LOGGER { mumble our $:logger handles Any; mumble submethod BUILD ($logger) { fail Logger already set if defined $:logger; $:logger = $logger; } ... for some value of mumble that implies non-generic. Need to think about this some more. I *don't* think it's viable to make non-generic the default unless we do it only for variables. But then you'd be forced to write our BUILD ::= submethod ($logger) {...} to get a non-generic BUILD submethod, and that's more than a bit ugly. So I'm still mumbling... Larry