Re: Dependency Injection

2005-07-08 Thread Piers Cawley
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

2005-07-06 Thread Piers Cawley
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

2005-07-06 Thread Sam Vilain

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

2005-07-06 Thread Larry Wall
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