Laruence,

Hey:
>    Just one question, usage?  why we need this? (run-time check is
> slow and I can not think out of a case then we must need it)
>

In practice, the run-time check shouldn't be that slow. In fact, I just did
a quick micro-benchark without actually implementing caching, and it's only
8% slower than an interface check. To put that in perspective, it adds on
average .387e-8 seconds per function call.

And that's without caching.

With caching, it should be no slower than a standard interface. In fact, it
could potentially be *faster* than an interface. The primary reason is that
the interface check happens at class definition time regardless of if it's
used or not. A cached protocol check would only be run when it's actually
hinted against.

So this does have the potential to actually increase the performance of a
codebase overall. So no, "run-time checks" are not really slow (they are no
more expensive than compile-time checks) as long as we can cache them
(still working on an effective cache now)...

As a proof-of-concept, I implemented a primitive cache. Here's the results
comparing an Interface check to a Protocol check and Native:

Interface in 0.50202393531799 seconds, 5.0202393531799E-7 seconds per run
Protocol in 0.48089909553528 seconds, 4.8089909553528E-7 seconds per run
Native in 0.3850359916687 seconds, 3.850359916687E-7 seconds per run

Now, Interface and protocol area always within about 2% of each other, and
which is faster changes per run, so I think it's safe to say this method is
not slow...


>    Go does that is not a reason...
>

 I never said it was. I quoted it to show one inspiration for the idea.

As far as what the use-case is, there are a few:

1. Library implementers don't need to declare a dependency for a third
party library to use the interface from it.

This may sound trivial, but imagine this. Right now Zend and Symfony have
very similar providers for certain tasks. One of the ones that comes to
mind (besides logging) is caching. If you want to use a Zend component in
an Symfony app while still using caching today, you'd need to shim together
an adapter to satisfy the zend cache interface with the symfony cache
interface. Which means your project now depends on all three, as well as
requiring the zend cache code for the sole purpose of loading the interface
required by the component.

That's loading a LOT of code that you don't need, just to avoid having to
setup two caches...

Instead, the zend component could depend on the narrow api that it needs.
It only calls the ->get($key) and ->set($key, $value) methods, so create a
(new?) interface with that limited API, and then type against it as a
protocol. Now that interface still would need to be loaded, but the Symfony
class can resolve that interface directly. Without the need to load
anything from Zend cache (said caching interface doesn't need to be there)
or wire anything else for you.

This may seem like a triviality, but it's actually quite significant.
Because...

2. It's dependency inversion applied to the type system.

Think of it like this. Dependency Injection is dependency inversion, it
pushes the requirement to resolve a dependency onto the creator instead of
on the object itself.

Protocols are also dependency inversion, it pushes the requirement to
determine if a dependency is resolve onto the receiver instead of the
creator of the object.

It is *not* a replacement for interfaces. It's not intended to be. Instead,
it's useful along side for cases when you want interoperability and
duck-style-typing without the overhead of coupling the packages together
via interfaces.

3. It allows avoiding interface-hell

Right now, every time you want to create a polymorphic dependency, you need
to create an interface for it to allow for others to resolve that with a
class from a different tree. This can lead to situations where you have
literally dozens of interfaces which you may or may not need. Additionally,
you're put in a position by classes like PDO where you can't have a
polymorphic dependency because there's no interface defined.

This change allows for using a Class as the protocol, so you could
type-hint against the API of a class without needing to create a separate
interface for it. This wouldn't be useful for something you substitute
often, but would be VERY useful for libraries which wrap dependencies that
aren't changed often, but may need to be. So instead of creating a PDO
wrapper just to add a series of interfaces for it, just hint against <PDO>.
That way you're getting an object that looks like PDO, but doesn't need to
be PDO if you don't want it to be...

There are definitely more, but the first one I think is the big one. It
decouples while still providing interface safety...



Anthony


PS: here's the code for said benchmark:

interface Foo {
        public function foo();
}

class Bar {
        public function foo() {}
}

class Baz implements Foo {
        public function foo() {}
}

function benchmark($func, $times, $arg) {
        $s = microtime(true);
        for ($i = 0; $i < $times; $i++) {
                $func($arg);
        }
        $e = microtime(true);
        return $e - $s;
}
$times = 1000000;
$interface = benchmark(function(Foo $foo) {}, $times, new Baz);
echo "Interface in $interface seconds, " . ($interface / $times) . "
seconds per run\n";
$protocol = benchmark(function(<Foo> $foo) {}, $times, new Bar);
echo "Protocol in $protocol seconds, " . ($protocol / $times) . " seconds
per run\n";
$native = benchmark(function($foo) {}, $times, new Bar);
echo "Native in $native seconds, " . ($native / $times) . " seconds per
run\n";

Reply via email to