On Sat, 14 Aug 2021 at 15:05, Larry Garfield <la...@garfieldtech.com> wrote:
>
> On Sat, Aug 14, 2021, at 7:48 AM, G. P. B. wrote:
> > On Sat, 14 Aug 2021 at 10:55, Deleu <deleu...@gmail.com> wrote:
> >
> > > Hi Jordan,
> > >
> > > Does it make sense to explain in the RFC the difference between never and
> > > mixed in this context? The RFC vaguely mentions that never can never be
> > > used directly, but if it's limited to abstract class and interfaces, isn't
> > > that already impossible to use directly? Or does it mean that the type
> > > would "be allowed" on any function, but because of its intrinsic behavior
> > > it would always fail if used in non-abstract methods?
> > >
> > > Another clarification I'd be interested is about dropping the type
> > > declaration entirely (e.g. https://3v4l.org/a4bfs), because of covariance
> > > (or contravariance, I never know), sub classes can completely drop type
> > > declaration entirely. Will never not allow this? Why? Why not? If it does
> > > allow this, does it really differ from not having any type declaration on
> > > the abstract function?
> > >
> > > My knowledge in this area is practically null so if I'm asking stupid
> > > questions that are easily explained by some blog post I'd he happy to read
> > > it.
> > >
> >
> > never and mixed are on opposite sides of the type hierarchy.
> > mixed is the top type, meaning it's the supertype of any other types, and
> > any other type is a subtype of mixed (excluding void which is not really a
> > "type" if you look at it, from my understanding, in a type theory way).
> > never on the other side is the bottom type, meaning it's the subtype of any
> > other type, and any other type is a supertype of never.
> > Finally a lack of type declaration is treated as mixed.
> >
> > Liskov's substitutions rules dictate that return types are co-variant i.e.
> > more specific, and argument types are contra-variant i.e. more general.
> > This is why any return type can be replaced by never and any argument type
> > can have it's type dropped/changed to mixed.
> > As such replacing never by mixed in an argument is totally possible as
> > mixed is a wider type than never.
> >
> > How I personally see never as an argument type is that you require a
> > mandatory argument but you leave the type constraint up to the
> > implementation.
> >
> > A recent example which bit us in php-src/the PHP documentation is the
> > interface of ArrayAccess, all of the $offset parameters have a mixed type.
> > Until recently the ArrayObject/ArrayIterator had incorrect stubs [1] by
> > indicating that the argument was of type int|string, which practically is
> > the case as SPL's ArrayAccess handler will throw a TypeError on different
> > types, however this is done manually within the call and not when the call
> > is made.
> > Ideally the $offset parameter would be of type never such that SPL, and
> > userland, can specify what type of offsets they accept, be that the usual
> > int|string, only int for list-like objects, only string for dictionary-like
> > objects, maybe even object|int|string to allow GMP objects for arbitrary
> > precision.
> > Whereas every implementer of ArrayAccess is forced to accept mixed for the
> > $offset and need to manually enforce the type.
> >
> > This is the "power" of the never type as an argument type.
> >
> > Best regards,
> >
> > George P. Banyard
> >
> > [1]
> > https://github.com/php/php-src/pull/7215/files#diff-e330df347cac9e68d2d07a06535c534cd8c2438a1af703cd4245b8ce91ec65afL9-R10
>
> So... if I am following correctly, the idea is to allow `never` to be used in 
> an interface/abstract method only, as a way to indicate "you must specify a 
> type here of some kind; I don't care what, even mixed, but you have to put 
> something".  Am I following?
>
> I'm not sure on the type theory of it, but it does feel like a hack at first 
> blush.

You don't have to specify a type.
When overriding the method in a child interface or abstract class, it
must have a parameter in the same place (and ideally with same name,
to support named argument calls).
The parameter in the overridden method may or may not have a type hint.
The parameter type hint can be "never", as in the parent, but then you
cannot call the method.

See also this older discussion, https://externals.io/message/100275#100300

Example:

interface Fruit {..}
interface Apple extends Fruit {..}
interface Banana extends Fruit {..}

interface AbstractFruitEater {
  function eat(EMPTY_TYPE $fruit);
}

interface BananaEater extends AbstractFoodEater {
  function eat(Banana $banana);
}

interface AppleEater extends AbstractFoodEater {
  function eat(Apple $apple);
}

// In an ideal world, UniversalFruitEater would extend every other
FruitEater type, but that's not really possible.
interface UniversalFruitEater extends AbstractFoodEater /* ,
BananaEater, AppleEater */ {
  function eat(Fruit $fruit);
}

----

Btw, I wonder what this means for optional parameters.
Currently, "null" is not allowed as a parameter type hint, but it
would be a natural bottom type for "?*".
How narrow-minded of PHP!

E.g.

interface AbstractOptionalFruitEater {
  function eat(null $fruit);  // Not allowed, but would make sense here.
}

interface OptionalBananaEater extends AbstractFoodEater {
  function eat(?Banana $banana);
}

interface OptionalAppleEater extends AbstractFoodEater {
  function eat(?Apple $apple);
}

----------

I am not sure yet how useful this really is in practice, if we cannot
"close the circle" and have UniversalFruitEater extend all the other
FruitEater interfaces, and if we don't have generics.

The first idea would be a mapper that chooses a suitable eater like so:

abstract class MapperEaterBase implements UniversalFruitEater {
  public function eat(Fruit $fruit) {
    // IDE will get confused here, because it cannot verify that the
eater from findSuitableEater() is actually suitable.
    return $this->findSuitableEater($fruit)->eat($fruit);
  }
  abstract protected function findSuitableEater(Fruit $fruit):
AbstractFruitEater;
}

This would be a kind of userland "method overloading".
The "map of eaters" could be built with reflection, or with repeated
try/catch until one of them works.

However, I am still undecided whether this is the best way to achieve this.

I am currently building a system similar to this, but here every
implementation has a method that reports which types are supported.
My equivalent to the eat() method simply accepts anything, and then
uses instanceof internally to reject parameters with the wrong type.

The "never" type for parameters could provide some new possibilities here.


-- Andreas

>
> --Larry Garfield
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: https://www.php.net/unsub.php
>

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to