> -----Original Message-----
> From: Larry Garfield [mailto:la...@garfieldtech.com]
> Sent: Friday, April 15, 2016 9:13 PM
> To: internals@lists.php.net
> Subject: Re: [PHP-DEV] Re: Improving PHP's type system
> 
> On 4/15/16 12:28 PM, Christoph Becker wrote:
> > On 15.04.2016 at 17:42, Larry Garfield wrote:
> >
> >> I think there's 2 general use cases for union types that would be
> >> "good things", which are different for & and |, and have very little...
> >> intersection.  (*yaaaaaaa!*)
> >>
> >> The OR case is for cases where the language doesn't support a unified
> >> case at all.  The most obvious example here is array|Traversable.  If
> >> I want "a thing I can foreach()", then PHP right now has no way of
> >> saying that syntactically.  You have to type on array, or
> >> Traversable, or not type at all.  array|Traversable is what you
> >> really want,
> > It is not what I would want, though.
> >
> >> because those
> >> DO have an overlap (foreach-ablility), PHP is just incapable of
> >> representing that otherwise.
> > Maybe we should consider to accept an array as Traversable?  Actually,
> > I wonder why that's not already the case.
> 
> It's been asked a few dozen times, but never went anywhere.  Mainly, I think,
> Traversable implies object, which implies certain passing semantics.  Array is
> a primitive, so has different passing semantics.
> There's probably other subtle issues like that which have kept the engine-
> gurus from trying to make it work.
> 
> My assumption here is "if it were that easy someone would have done it
> already".  (Which may not be an entirely accurate assumption, but seems
> logical given how often it's been asked for.)
> 
> >> A similar example would be callable|SomeInterface.  An interface can
> >> specify a signature for __invoke(), which gives you documentation on
> >> the format that is expected for a callable.  However, you can't
> >> strictly enforce that because then you don't allow for a function or
> >> closure that fits the same method signature.  That means you have to
> >> leave it untyped.  This, I argue, would be better *and* reasonably type 
> >> safe:
> >>
> >> interface MiddlewareInterface {
> >>    function __invoke(RequestInterface $req, ResponseInterface $res);
> >> }
> >>
> >> function middleware_builder(callable|MiddlewareInterface $m) {
> >>    // ...
> >> }
> >>
> >> As that self-documents that MiddlewareInterface is the callable
> >> signature we need, but still allows an arbitrary callable to be passed.
> >> It's not perfect (I could pass a string of a function name that
> >> doesn't have that interface and it would still explode), but it is an
> >> improvement over middleware_builder() having no type specification at
> >> all, as is the case today.
> > In my opinion, `callable' is to weak a type hint to be really useful,
> > and it would be better if we would improve that (generics come to mind).
> >   Then you wouldn't need MiddlewareInterface at all and be not afraid
> > that somebody passes in an incompatible function.
> 
> One of the key language design points I think we should be keeping in mind is
> that, on the whole, single-purpose features are inferior to more general
> capabilities that implicitly grant the same capability.

It's a good rule-of-thumb, but I don't think it makes a lot of sense here.

Compared to the handful (or less) of situations where two different types of 
objects in fact have similar signatures, and like Christoph suggested - 
arguably, one should be considered a part of the other - in the vast majority 
of cases, lumping together two or more class types makes no sense.  
Unfortunately, I'm much less optimistic than you are about it 
not-being-used-because-it-makes-no-sense.

For uncommon but existent cases like the Symfony Request scenario you 
described, it's entirely reasonable to conduct the type check in userland code.

Creating a generic feature that makes sense in a handful of situations, while 
at the same time being one that's waiting-to-be-abused in the vast majority of 
the rest (or as Tom put it, a 'footgun') is a pretty poor bargain IMHO.

> On the flipside, the & is mostly useful for where you need multiple interfaces
> for something.  For instance, there's the PSR-7 ResponseInterface.  Drupal
> also has a number of interfaces for value objects to indicate their 
> cacheability
> metadata, such as CacheableMetadataInterface.  But that applies to more
> than just Responses, of course, so having it extend ResponseInterface is not
> good.  So how can I specify that I need an object that is BOTH
> ResponseInterface AND CacheableMetdataInterface?  That's an entirely
> reasonable thing to do, but currently PHP doesn't allow for it at all.
> Even having a custom interface that extends both of those doesn't help,
> because then my class needs to implement the child interface, not both
> parents.

It could actually implement all of them - the two parents, and the child.  That 
sounds like a pretty good, explicit way that requires no introduction of any 
new syntax, concepts or engine complexity in order to do what you're 
describing.  This works fine:

interface foo { function foo(); }
interface bar { function bar(); }
interface baz extends foo,bar {}

class impl implements foo, bar, baz {
  function foo(){}
  function bar(){}
}

function sth(baz $b){}

One thing we could consider is adding some intelligence for these cases, and 
for interfaces that only extend other interfaces (without adding new 
signatures) - a class would be considered to implement that interface if it 
implements all of the 'parent' interfaces that the child interface extends: 

class impl implements foo, bar {
  function foo(){}
  function bar(){}
}

function sth(baz $b){}   <-- would work, as impl implements both foo and bar, 
and baz does nothing but extending those.

I'm not sure that's necessary, and believe the current mechanisms and syntax 
satisfy these use cases already, but it's probably a possibility.

Zeev

Reply via email to