DT::Wrapper API/semantics

2003-08-14 Thread Dave Rolsky
So I started working on this and it's trickier than I thought.  So rather
than go ahead and implement something really complicated only to find out
that it doesn't do what people want, I thought I should try to get some
consensus on functionality first.

The basic problem is that people want to be able to decorate datetime
objects in various ways, for example by turning off validation in
constructors, or caching various calls, or whatever.

Subclassing doesn't work well here, because if someone wants to cache
_and_ ignore validation, it wouldn't be possible, assuming both modules
override new().

So what we really want is to create arbitrary "chains" (class hierarchies)
of the modules.

So assuming our base is DateTime, we might sometimes want a hierarchy like
this:

  DT
   |
  is parent of
   |
  DT::Cache
   |
  is parent of
   |
  DT::UpperCaseAllWords

and maybe sometimes we want:

  DT
   |
  is parent of
   |
  DT::UpperCaseAllWords
   |
  is parent of
   |
  DT::NeverPrintTheNumber9
   |
  is parent of
   |
  DT::Mixin::AddAFooMethod

But we don't want to make permanent changes to any class hierarchy, and we
want to be able to create different hierarchies as needed, on the fly.

We also want a nice API.  I could do the former fairly easily (I think)
with a somewhat ugly API like:

  DT::Wrapper->construct( classes => [ ... ],
  constructor => 'new',
  params  => \%params, );

but that API sucks.

So what I think we really want is this:

  my $Wrapper = DT::Wrapper->wrapper( [$class1, $class2, $class3] );

where $class1 is a subclass of $class2, and $class2 is a subclass of
$class3.

If !$class3->isa('DateTime') then we need to add DateTime.pm to the list.

Then we want to be able to do:

  my $wrapped_dt = $Wrapper->any_constructor(%params);

  print $wrapped_dt->year;

  # foo is a new method provided by one of the wrapping classes
  print $wrapped_dt->foo;

The trick here is that we A) need to catch all constructor calls and tweak
the return value, because the object returned needs to be wrapped.


Anyway, does this API sound sane?  And if it does, anyone have any really
clever implementation ideas?  I have some scary ones involved AUTOLOAD and
constructing classes on the fly.


-dave

/*===
House Absolute Consulting
www.houseabsolute.com
===*/


Re: DT::Wrapper API/semantics

2003-08-08 Thread John Siracusa
On Friday, August 8, 2003, at 10:21 PM, Joshua Hoblitt wrote:
This is the 'decorator infrastructure' that I've been whining for.  
It's kind of a melting pot for 'add-ons' or 'plug-ins'.  The idea is 
to throw DT, DT::Cache, DT::Foo, DT::Validator, etc. into the 'pot' 
and end up with a factory that creates object with features from all 
of the input classes.
I'm with you, except that I wonder if DT::Cache won't be weighed down 
by the overhead of the decorator chaining implementation.  Dave's post 
was somewhat scary... :)

-John



Re: DT::Wrapper API/semantics

2003-08-09 Thread Joshua Hoblitt
> > Anyway, does this API sound sane?  And if it does, anyone have any really
> > clever implementation ideas?  I have some scary ones involved AUTOLOAD and
> > constructing classes on the fly.
>
> help me understand it -
> Does something like DT::Incomplete could be a wrapping class?

No.  This is the 'decorator infrastructure' that I've been whining for.  It's kind of 
a melting pot for 'add-ons' or 'plug-ins'.  The idea is to throw DT, DT::Cache, 
DT::Foo, DT::Validator, etc. into the 'pot' and end up with a factory that creates 
object with features from all of the input classes.

-J

--


Re: DT::Wrapper API/semantics

2003-08-09 Thread Dave Rolsky
On Fri, 9 Aug 2003, Rick Measham wrote:

> On Sat, 2003-08-09 at 06:55, Dave Rolsky wrote:
> > So what I think we really want is this:
> >
> >   my $Wrapper = DT::Wrapper->wrapper( [$class1, $class2, $class3] );
>
> Maybe my approach has some holes, but have a look at the attached and
> see what you think ...

The big hole is that you set @ISA when you create the decorator, but what
if someone creates two _different_ decorators?  In that case you've just
broken the first one you made.

Also, we want to be able to use SUPER::foo inside the decorating classes.
For example, Josh's DT::Cache will obviously need to call it's parent's
new() method.

This means that we can't just use multiple inheritance, but instead we
need to create an actual chain of inheritance.


-dave

/*===
House Absolute Consulting
www.houseabsolute.com
===*/


Re: DT::Wrapper API/semantics

2003-08-09 Thread Rick Measham
On Sat, 2003-08-09 at 06:55, Dave Rolsky wrote:
> So what I think we really want is this:
> 
>   my $Wrapper = DT::Wrapper->wrapper( [$class1, $class2, $class3] );

Maybe my approach has some holes, but have a look at the attached and
see what you think ...

#!/usr/bin/perl

#---
package DateTime::AddOneDay;
#---

use DateTime;

# Calling new here gives us one day later than the args asked for.
sub new {
	my $class = shift;
	my $date = DateTime->new(@_)->add( days => 1);
	return bless $date, $class;
}

# Decorator's job is to add one day
sub add_one_day {
	return shift->add(days => 1);
}


#---
package DateTime::StrfPirate;
#---

# Only one thing here!
sub strftime {
	return "Your strftime method has been pirated by a decorator {evil laugh}.";
}


#---
package DateTime::Now;
#---

use DateTime;

# If now is called on this module we return DateTime->now() 
# .. figure that's what people would expect
sub new {
	my $class = shift;
	my %args = @_;
	# Trim out any params that will cause DateTime::now() to keel over
	my %useargs;
	$useargs{locale} = $args{locale} if $args{locale};
	$useargs{time_zone} = $args{time_zone} if $args{time_zone};
	# There must be a better way to do that!
	return bless DateTime->now(%useargs), $class;
}

# Decorator's real job is to set the time to the current time
sub setnow { 
	my $self = shift;
	my $now = DateTime->now();
	$self->set(
		hour => $now->hour,
		minute => $now->minute,
		second => $now->second,
		nanosecond => $now->nanosecond
	);
	return $self;
}


#---
package DateTime::Decorator;
#---

# called 'create' so that a call to 'new' calls the right new :)
sub create {
	my $class = shift;
	# Don't do Param::Validate because we've been passed all sort of
	# things depending on our decorator path
	my %args = @_;
	# Create our inheritance path. In the real solution, we'd check if
	# DateTime was already stated.
	@ISA = (@{delete $args{decorators}}, 'DateTime');
	# Bless nothing so we have something
	my $self = bless \{}, $class;
	# And now we have something we can create our object
	return bless $self->new(%args), $class;
}

sub decorate {
	# unshift more decorators onto @ISA
}

sub decorators {
	return @ISA;
}

#---
package main;
#---


my $datetime1 = DateTime::Decorator->create(
	decorators => ['DateTime::AddOneDay', 'DateTime::Now'],
	year => 2003,
	day => 12
);
print '$datetime1->datetime: ' . $datetime1->datetime . "\n";
print '$datetime1->setnow->datetime: ' . $datetime1->setnow->datetime . "\n\n";

my $datetime2 = DateTime::Decorator->create(
	decorators => ['DateTime::StrfPirate', 'DateTime::Now'],
	# no need for other constructors ..
	# ::StrfPirate has no new so we try the next decorator
	# ::Now has a new so it gets used .. and it doesn't need any params.
);
$datetime2->add(hours => 123);
print '$datetime2->datetime: ' . $datetime2->datetime . "\n";
print '$datetime2->setnow->datetime: ' . $datetime2->setnow->datetime . "\n";
print '$datetime2->strftime("%c"):   ' . $datetime2->strftime("%c") . "\n";



Re: DT::Wrapper API/semantics

2003-08-10 Thread Rick Measham

On Sat, 2003-08-09 at 06:55, Dave Rolsky wrote:
> So what I think we really want is this:
>
>   my $Wrapper = DT::Wrapper->wrapper( [$class1, $class2, $class3] );
>

On Fri, 9 Aug 2003, Rick Measham wrote:
> > Maybe my approach has some holes, but have a look at the attached and
> > see what you think ...
> 

On Sun, 2003-08-10 at 00:40, Dave Rolsky wrote:
> The big hole is that you set @ISA when you create the decorator, but what
> if someone creates two _different_ decorators?  In that case you've just
> broken the first one you made.

Oh dang! Of course! Should have thought of that. Pity we can't get @ISA
as a part of the object

> Also, we want to be able to use SUPER::foo inside the decorating classes.
> For example, Josh's DT::Cache will obviously need to call it's parent's
> new() method.

Hmmm .. ok .. so If DT::Cache isn't the last module in the array then it
won't work? Seems to me that decorators should be able to be anywhere in
the list. What if some other decorator comes along that has to be last
in the chain? Then you can only use one or the other.

> This means that we can't just use multiple inheritance, but instead we
> need to create an actual chain of inheritance.

Another suggestion: Look at Sex as a possibly adaptable option. Sex is a
module written tounge-in-cheek by Michael Schwern but if you take all
the grunting out, it may be useful:
http://search.cpan.org/author/MSCHWERN/Sex-0.69/Sex.pm

Yet another: Each decorator has a 'decorate' method that takes a list of
modules to decorate itself with. Basically it grabs the one off the end
and 'uses' what it grabbed. Then it calls decorator in the grabbed
module, passing it whatever is left of the list.

Both of these may have the same problems as my original solution. I'm
thinking out loud and I'm not an OO guru :)

Cheers!
Rick



Re: DT::Wrapper API/semantics

2003-08-14 Thread Dave Rolsky
On Fri, 8 Aug 2003, Flavio S. Glock wrote:

> Dave Rolsky wrote:
> >
> > Anyway, does this API sound sane?  And if it does, anyone have any really
> > clever implementation ideas?  I have some scary ones involved AUTOLOAD and
> > constructing classes on the fly.
>
> help me understand it -
> Does something like DT::Incomplete could be a wrapping class?

No, this is so that we can mix and match additional behaviors that would
otherwise be done as subclasses.  Joshua's caching class is one obvious
example.  Another might a class that didn't die on invalid params, but
converted them to something valid.


-dave

/*===
House Absolute Consulting
www.houseabsolute.com
===*/


Re: DT::Wrapper API/semantics

2003-08-14 Thread Flavio S. Glock
Dave Rolsky wrote:
> 
> Anyway, does this API sound sane?  And if it does, anyone have any really
> clever implementation ideas?  I have some scary ones involved AUTOLOAD and
> constructing classes on the fly.

help me understand it - 
Does something like DT::Incomplete could be a wrapping class?

- Flavio S. Glock