Ralph,

On Tue, Aug 14, 2012 at 3:30 PM, Ralph Schindler
<ra...@ralphschindler.com>wrote:

>
>  Well, I like it at first glance. There are two main problems that I see
>> with it:
>>
>> 1. It still requires a separate decorator class for every combination of
>> interfaces you want to decorate. So, for example if you wanted to
>> decorate a Foo interface, but sometimes it's used with Iterator as well.
>> Then you'd need two classes. If there was a third interface, you'd need
>> four separate classes. Same problem with class bloat minus a lot of the
>> boilerplate.
>>
>
> You'd need a class anyway for your custom logic to live, that custom logic
> is the reason you want a Proxy in the first place.  In your example, you
> want to proxy and object, but for one of the method, presumable an
> expensive method, you want to be able to cache the output before needing to
> go to the actual object.
>
> If Iterator was required for the Foo contract/interface, that would have
> bee in the Foo interface definition as well.
>

No, it would be required by the class itself. That's the point. I don't
want to decorate an interface, but an instance. Otherwise it makes
decorating real world classes much more difficult (which was the pain I'm
looking to clean up).


> Even in your example, you still have this:
>
>   class Bar extends SplDecorator {}
>
> But what you are really intending is:
>
>   class Bar extends MagicToMakeThisActLikeAnyType {}
>
> Ultimately, the instance you are decorating and decorator itself need to
> be "type equivalent".  Meaning the generally accepted understanding of
> is_a() and instanceof constructs need to also apply to the decorator object
> just as it would any other object.


It sounds to me like you haven't tried to use decorators for any complex
logic. Making it type equivalent leads to very vebose code. And a PITA to
write. This is one of the reasons that traits are so heralded. Because
problems that are easy to solve with decorators (in general) are difficult
to solve with PHP, so people wind up writing copy/paste systems (which is
what traits are) to solve them in a more brittle and less flexible manner.

I'm after flexibility in addition to enforcement. They are not opposites.
The whole point of interfaces is not typing, but a contract. There's
nothing stopping you from making an Iterator which uses the valid() class
to do logic. The accepted contract though says what it should do. But
interfaces are only half of that contract. We assume that if you implement
an interface that you're going to abide by the whole contract. But without
interface level preconditions, postconditions and invarients, it's not an
"All or nothing" approach. By using interfaces I can gain some compile-time
assurance that the contract is met, but only manual review will confirm
this.

What I'm proposing here can be abused. Sure, it can be used to do all sorts
of nasty things. But so can traits. So can eval. So can inheritance. So can
regular expressions. That doesn't mean that they aren't incredibly useful...


>  2. It's still not possible to decorate a typed class. So I can't
>> decorate PDO without first extending PDO with a user-supplied interface.
>>
>
> I don't follow.  What do you mean by a "typed class"?


A class which is checked for directly. There is plenty of user-land code
that uses PDO as a type hint. That means I'd need to extend PDO to add
functionality, thereby rendering decorators useless (and instead causing
all sorts of issue). And even if I wanted to make a new class for my using
to decorate, I'd need to create an interface and class:

interface PDOLike {
}
class MyPDO extends PDO implements PDOLike {
}

That winds up creating really weird coupling and hard to reuse code. So
that means that I either need to depend on my PDO instance, or not be able
to use decorators at all. Not a good tradeoff if you ask me.


>  And I don't understand how this breaks LSP... We're not extending the
>> decorated class. In fact, we can add construct time checking to ensure
>> the original interfaces are all enforced (and even go a step further and
>>
>
> That is where we are going to differ.
>
> Simply because your object responds to all the same methods of, for
> example, the FooInterface, does not make it a FooInterface subtype.  It
> just means that in the "duck typing" sense of the phrase, it can act like a
> FooInterface for people that are not necessarily concerned that it's
> actually not is_a() FooInterface.
>

But the flip side is true as well. Just because a class implements a
FooInterface doesn't mean it can act like a FooInterface is supposed to.
That's one reason that relying on interfaces alone for that much is a
mistake. It takes review. Or (in other languages) formal contracts to
ensure that the contract is enforced.

And since interfaces in PHP are weak signature based contracts, we can
implement them dynamically without absolutely any break in the contract.
That's why this style system (letting a decorator assume the interfaces of
the parents, as long as the decorator doesn't violate the interface) can
work in PHP. If we had actual contracts, it wouldn't work.

In other words, yes, it will pass all sorts of method_exists() checks, but
> it'd never be able to pass an instanceof, is_a, or is_subclass_of() check
> on FooInterface.


Which is what's possible today. I'm talking about changing that to let it
pass those checks, because it's decorating, not becoming a type of itself.


>  ensure that all method signatures are the same that are overridden). LSP
>> is not a compile time concern, it's a purely runtime concern (in PHP at
>>
>
> When I say "compile time" I mean PHP's compile time, in so far as PHP is a
> compile and execute style interpreted language.
>

But the contract is not enforced at compile time. Only half of it is (the
issuer, or the fact that the class says it signed the contract). The other
half is done at runtime when a method asks for a specific contract (the
signer).


> The following script demonstrates the enforcement of subtype expectations:
>
> <?php
> interface Foo { public function bar(); }
> class Bar implements Foo { public function bar(array $a) {} }
> ?>
>
> That will produce: PHP Fatal error:  Declaration of Bar::bar() must be
> compatible with that of Foo::bar() in ...
>
> That is an LSP check, and it is done at PHP's compile time.  The rule
> "Preconditions cannot be strengthened in a subtype" is violated since you
> attempted to add an argument to the bar() method of the Foo contract.


Let's get one thing clear here. It's not a LSP check. In that exact example
it is. But in general, it also prevents the LSP valid:

interface foo { public function bar(array $a); }
class Bar implements Foo { public function bar($a); }

That's 100% in compliance with LSP. Yet it's disallowed. The reason is that
interfaces are not primarily a LSP enforcer. They are primarily a contract
enforcer. LSP is a usual side-effect, but it's not a guarantee.


>  least). The fact that interface contracts are enforced at compile time
>> is nice, but it's not a strict necessity to enforcing LSP.
>>
>
> Why not do it at compile time? When php is building up its class entry
> table, it should be in its best interest to not have incomplete or invalid
> types in there, right?


Because you can't know at compile time without doing a bunch of weird and
wonky things. Which completely blow away the benefits of decorating.

For example, let's say you want to decorate an iterator to also expose the
ArrayAccess interface. That way you can access an iterators elements by key
directly. That's fine and doable today:

class ArrayAccessDecorator implements Iterator, ArrayAccess {
    /* Proxy Methods Here */
    public function offsetGet($key) {
        if (!isset($this->data[$key]) {
            while ($this->valid()) {
                if ($key == $this->key()) break;
                $this->next();
            }
            if (!isset($this->data[$key])) return false;
        }
        return $this->data[$key];
    }
}

Where it internally caches the iterated items. It doesn't work anywhere,
but it decorates real world functionality onto an iterator. Now, let's say
that we also have a reason to implement an observer decorator, to let other
classes observe the iteration as it happens.

class ObservableDecorator implements Iterator, SplSubject {
    /* SPL Methods Here*/
    /* Proxy Methods Here */

    public function next() {
        $this->parent->next();
        $this->notify();
    }
}

Now, today, we can't decorate an iterator with both. We'd have to choose
one or the other, because the second decorator would nuke the type
information of the first. So we're left with a dirty ass hack:

class ArrayAccessObservableDecorator extends ObservableDecorator implements
ArrayAcces {
    public function __construct(ArrayAccess $parent) {}
}

Which is exactly the type of thing this proposal is targeted to prevent.


> But, I do support getting more duck typing facilities in PHP, and reusing
> traits in places where we need more compiler assisted copy/paste when
> boilerplate is cumbersome.
>

But that's exactly the point. Traits are not (and never were) designed to
solve this problem. I'm talking about runtime composability of
functionality. Not compile time. I want to be able to construct my objects
from other interactions (like database settings), not just be limited to
having to copy/paste for everything.

And I don't need to resort to duck typing for it to work. All I need is the
ability to decorate at will. I'm not talking about nuking the meaning of
the interfaces (as they'd be proxied to). But more just gaining the
flexibility to decorate without having to muck up everything a dozen times
for every possible interface combination of my decorated class.

Anthony

Reply via email to