Re: [PHP-DEV] Contravariance and the "empty type"

2017-08-24 Thread Andreas Hennings
This discussion made me have another look at the Generics RFC,
https://wiki.php.net/rfc/generics

It seems to me that the proposal violates LSP, because it does not
correctly implement contravariance.
Look at the part where it talks about instanceof.

interface Feline {}
class Cat implements Feline {}
class Tiger implements Feline {}

class Box {
  function entrap(T $feline) {}
}

$feline_box = new Box();
$cat_box = new Box();
$tiger_box = new Box();

$cat = new Cat();
$tiger = new Tiger();

assert($feline_box instanceof Box);  // -> ok.
assert($tiger_box instanceof Box);  // -> ok.
assert($cat_box instanceof Box);  // -> ok.

assert($cat instanceof Feline);  // -> ok.
assert($tiger instanceof Feline);  // -> ok.

$feline_box->entrap($cat);  // -> ok.
$cat_box->entrap($cat); // -> ok.
$tiger_box->entrap($cat); // -> Fatal error: Uncaught TypeError.


So, even with generics, we still need to think about contravariance.

We need to distinguish 3 types of type parameter on classes:
1. Those which are used in method return types.
2. Those which are used in method parameter types.
3. Those which are used in both.

For these 3 cases, the following rules would need to apply:
1. Contravariance.
2. Covariance
3. Identity.

E.g.

interface Fruit;
interface Banana extends Fruit;

interface Grower {
  function grow() : T;
}

interface Processor {
  function process(T $fruit) : T;
}

interface Eater {
  function eat(T $fruit);
}

// Covariance
var_dump(new Grower instanceof Grower); // => (bool) true
var_dump(new Grower instanceof Grower); // => (bool) false

// Identity
var_dump(new Processor instanceof Processor); // => (bool) false
var_dump(new Processor instanceof Processor); // => (bool) false

// Contravariance
var_dump(new Eater instanceof Eater); // => (bool) false
var_dump(new Eater instanceof Eater); // => (bool) true


The only supertype for all Eater<*> types would be Eater.
This super-eater has the absolute fruit allergy.




On Wed, Aug 23, 2017 at 9:18 AM, Michał Brzuchalski
 wrote:
> Hi Andreas,
>
> 2017-08-22 21:11 GMT+02:00 Andreas Hennings :
>
>> On Tue, Aug 22, 2017 at 10:39 AM, Nikita Popov 
>> wrote:
>> > On Tue, Aug 22, 2017 at 4:27 AM, Andreas Hennings 
>> > wrote:
>> >>
>> >> Hello list,
>> >> for a while I had this thought about contravariance and an "empty type".
>> >> I don't expect too much of it, for now I just want to share the idea.
>> >> Maybe this concept even exists somewhere in a different language, and
>> >> I am not aware of it.
>> >>
>> >> I think it has some overlap with generics,
>> >> https://wiki.php.net/rfc/generics.
>> >>
>> >> 
>> >>
>> >> I think I am not the first one to suggest allowing contravariance for
>> >> method parameters.
>> >> E.g. here, "PHP RFC: Parameter Type Widening"
>> >> https://wiki.php.net/rfc/parameter-no-type-variance
>> >>
>> >> From this RFC:
>> >> > Unfortunately “true” contravariance for class types isn't part of this
>> >> > RFC, as implementing that is far more difficult, and would require
>> >> > additional rules about autoloading and/or class compilation, which
>> might
>> >> > only be acceptable at a major release.
>> >>
>> >> For anyone not familiar with the term:
>> >>
>> >> interface I {
>> >>   function foo(J $arg);
>> >> }
>> >>
>> >> interface J extends I {
>> >>   function foo(I $arg);
>> >> }
>> >>
>> >> So: While return types in a child method should be either the same or
>> >> more narrow, the parameter types should be either the same or more
>> >> permissive.
>> >> Without this it would break Liskov substitution.
>> >>
>> >> ---
>> >>
>> >> Now for my actual proposal: The "empty type".
>> >> We can think of a type (class/interface or primitive) as a set or a
>> >> constraint on the kind of values that it allows.
>> >> There is a special type, "mixed", which allows all values. We could
>> >> also think of it as the union of all types.
>> >>
>> >> A natural extension of this concept, on the other end, would be a type
>> >> "nothing" or "empty", which would allow no values at all.
>> >> We could think of this as the intersection of all types.
>> >> In fact it is already sufficient to intersect just two distinct
>> >> primitive types to get this empty type:
>> >> "All values that are at the same time string and integer" clearly is
>> >> an empty type.
>> >>
>> >> How would this ever be useful?
>> >> If we write a base class or interface for a category of interfaces
>> >> that have a similar signature.
>> >>
>> >> 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);
>> >> }
>> >>
>> 

Re: [PHP-DEV] Contravariance and the "empty type"

2017-08-23 Thread Michał Brzuchalski
Hi Andreas,

2017-08-22 21:11 GMT+02:00 Andreas Hennings :

> On Tue, Aug 22, 2017 at 10:39 AM, Nikita Popov 
> wrote:
> > On Tue, Aug 22, 2017 at 4:27 AM, Andreas Hennings 
> > wrote:
> >>
> >> Hello list,
> >> for a while I had this thought about contravariance and an "empty type".
> >> I don't expect too much of it, for now I just want to share the idea.
> >> Maybe this concept even exists somewhere in a different language, and
> >> I am not aware of it.
> >>
> >> I think it has some overlap with generics,
> >> https://wiki.php.net/rfc/generics.
> >>
> >> 
> >>
> >> I think I am not the first one to suggest allowing contravariance for
> >> method parameters.
> >> E.g. here, "PHP RFC: Parameter Type Widening"
> >> https://wiki.php.net/rfc/parameter-no-type-variance
> >>
> >> From this RFC:
> >> > Unfortunately “true” contravariance for class types isn't part of this
> >> > RFC, as implementing that is far more difficult, and would require
> >> > additional rules about autoloading and/or class compilation, which
> might
> >> > only be acceptable at a major release.
> >>
> >> For anyone not familiar with the term:
> >>
> >> interface I {
> >>   function foo(J $arg);
> >> }
> >>
> >> interface J extends I {
> >>   function foo(I $arg);
> >> }
> >>
> >> So: While return types in a child method should be either the same or
> >> more narrow, the parameter types should be either the same or more
> >> permissive.
> >> Without this it would break Liskov substitution.
> >>
> >> ---
> >>
> >> Now for my actual proposal: The "empty type".
> >> We can think of a type (class/interface or primitive) as a set or a
> >> constraint on the kind of values that it allows.
> >> There is a special type, "mixed", which allows all values. We could
> >> also think of it as the union of all types.
> >>
> >> A natural extension of this concept, on the other end, would be a type
> >> "nothing" or "empty", which would allow no values at all.
> >> We could think of this as the intersection of all types.
> >> In fact it is already sufficient to intersect just two distinct
> >> primitive types to get this empty type:
> >> "All values that are at the same time string and integer" clearly is
> >> an empty type.
> >>
> >> How would this ever be useful?
> >> If we write a base class or interface for a category of interfaces
> >> that have a similar signature.
> >>
> >> 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);
> >> }
> >>
> >> One could imagine a component that has a list of AbstractFruitEater
> >> objects, and chooses one that is suitable for the given fruit, using
> >> instanceof.
> >> I think the correct term is "chain of responsibility".
> >>
> >> function eatApple(array $fruitEaters, Apple $apple) {
> >>   foreach ($fruitEaters as $eater) {
> >> if ($eater instanceof AppleEater) {
> >>   $eater->eat($apple);
> >>   break;
> >> }
> >>   }
> >> }
> >>
> >> 
> >>
> >> We can go one step further.
> >>
> >> The natural parameter type to use for param $fruit in
> >> AbstractFruitEater::foo() would not be the global EMPTY_TYPE, but
> >> something more specific:
> >> The projected intersection of all real and hypothetical children of
> >> interface Fruit.
> >> Obviously this does not and cannot exist as a class or interface.
> >>
> >> Practically, for the values it allows, this is the same as the global
> >> EMPTY_TYPE.
> >> But unlike the EMPTY_TYPE, this would poses a restriction on the
> >> parameter type in child interfaces.
> >>
> >> What would be the syntax / notation for such a projected hypothetical
> >> subtype?
> >> I don't know. Let's say INTERSECT_CHILDREN
> >>
> >> So, would the following work?
> >>
> >> interface Food {..}
> >> interface Fruit extends Food {..}
> >> interface Banana extends Fruit {..}
> >>
> >> interface AbstractFoodEater {
> >>   function eat(INTERSECT_CHILDREN $food);
> >> }
> >>
> >> interface AbstractFruitEater extends AbstractFoodEater {
> >>   function eat(INTERSECT_CHILDREN $fruit);
> >> }
> >>
> >> interface BananaEater extends AbstractFruitEater {
> >>   function eat(Banana $banana);
> >> }
> >>
> >> I'm not sure.
> >> Liskov would not care. Both AbstractFoodEater and AbstractFruitEater
> >> are useless on their own.
> >> Maybe there are other logical conflicts which I don't see.
> >>
> >>
> >> --
> >>
> >> Obviously with generics this base interface would no longer be relevant.
> >> https://wiki.php.net/rfc/generics
> >>
> >> interface FruitEater {
> >>   function eat(FruitType $fruit);
> >> }
> >>
> >> // This is not really 

Re: [PHP-DEV] Contravariance and the "empty type"

2017-08-22 Thread Andreas Hennings
On Tue, Aug 22, 2017 at 10:39 AM, Nikita Popov  wrote:
> On Tue, Aug 22, 2017 at 4:27 AM, Andreas Hennings 
> wrote:
>>
>> Hello list,
>> for a while I had this thought about contravariance and an "empty type".
>> I don't expect too much of it, for now I just want to share the idea.
>> Maybe this concept even exists somewhere in a different language, and
>> I am not aware of it.
>>
>> I think it has some overlap with generics,
>> https://wiki.php.net/rfc/generics.
>>
>> 
>>
>> I think I am not the first one to suggest allowing contravariance for
>> method parameters.
>> E.g. here, "PHP RFC: Parameter Type Widening"
>> https://wiki.php.net/rfc/parameter-no-type-variance
>>
>> From this RFC:
>> > Unfortunately “true” contravariance for class types isn't part of this
>> > RFC, as implementing that is far more difficult, and would require
>> > additional rules about autoloading and/or class compilation, which might
>> > only be acceptable at a major release.
>>
>> For anyone not familiar with the term:
>>
>> interface I {
>>   function foo(J $arg);
>> }
>>
>> interface J extends I {
>>   function foo(I $arg);
>> }
>>
>> So: While return types in a child method should be either the same or
>> more narrow, the parameter types should be either the same or more
>> permissive.
>> Without this it would break Liskov substitution.
>>
>> ---
>>
>> Now for my actual proposal: The "empty type".
>> We can think of a type (class/interface or primitive) as a set or a
>> constraint on the kind of values that it allows.
>> There is a special type, "mixed", which allows all values. We could
>> also think of it as the union of all types.
>>
>> A natural extension of this concept, on the other end, would be a type
>> "nothing" or "empty", which would allow no values at all.
>> We could think of this as the intersection of all types.
>> In fact it is already sufficient to intersect just two distinct
>> primitive types to get this empty type:
>> "All values that are at the same time string and integer" clearly is
>> an empty type.
>>
>> How would this ever be useful?
>> If we write a base class or interface for a category of interfaces
>> that have a similar signature.
>>
>> 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);
>> }
>>
>> One could imagine a component that has a list of AbstractFruitEater
>> objects, and chooses one that is suitable for the given fruit, using
>> instanceof.
>> I think the correct term is "chain of responsibility".
>>
>> function eatApple(array $fruitEaters, Apple $apple) {
>>   foreach ($fruitEaters as $eater) {
>> if ($eater instanceof AppleEater) {
>>   $eater->eat($apple);
>>   break;
>> }
>>   }
>> }
>>
>> 
>>
>> We can go one step further.
>>
>> The natural parameter type to use for param $fruit in
>> AbstractFruitEater::foo() would not be the global EMPTY_TYPE, but
>> something more specific:
>> The projected intersection of all real and hypothetical children of
>> interface Fruit.
>> Obviously this does not and cannot exist as a class or interface.
>>
>> Practically, for the values it allows, this is the same as the global
>> EMPTY_TYPE.
>> But unlike the EMPTY_TYPE, this would poses a restriction on the
>> parameter type in child interfaces.
>>
>> What would be the syntax / notation for such a projected hypothetical
>> subtype?
>> I don't know. Let's say INTERSECT_CHILDREN
>>
>> So, would the following work?
>>
>> interface Food {..}
>> interface Fruit extends Food {..}
>> interface Banana extends Fruit {..}
>>
>> interface AbstractFoodEater {
>>   function eat(INTERSECT_CHILDREN $food);
>> }
>>
>> interface AbstractFruitEater extends AbstractFoodEater {
>>   function eat(INTERSECT_CHILDREN $fruit);
>> }
>>
>> interface BananaEater extends AbstractFruitEater {
>>   function eat(Banana $banana);
>> }
>>
>> I'm not sure.
>> Liskov would not care. Both AbstractFoodEater and AbstractFruitEater
>> are useless on their own.
>> Maybe there are other logical conflicts which I don't see.
>>
>>
>> --
>>
>> Obviously with generics this base interface would no longer be relevant.
>> https://wiki.php.net/rfc/generics
>>
>> interface FruitEater {
>>   function eat(FruitType $fruit);
>> }
>>
>> // This is not really necessary.
>> interface BananaEater extends FruitEater {
>>   function eat(Banana $banana);
>> }
>>
>> So, would the "empty type" become obsolete? Maybe.
>> I did not arrive at a final conclusion yet. It still seems too
>> interesting to let it go.
>>
>> -- Andreas
>
>
> What's the purpose of this construction? I get the general idea (work around
> LSP variance restrictions 

Re: [PHP-DEV] Contravariance and the "empty type"

2017-08-22 Thread Nikita Popov
On Tue, Aug 22, 2017 at 4:27 AM, Andreas Hennings 
wrote:

> Hello list,
> for a while I had this thought about contravariance and an "empty type".
> I don't expect too much of it, for now I just want to share the idea.
> Maybe this concept even exists somewhere in a different language, and
> I am not aware of it.
>
> I think it has some overlap with generics, https://wiki.php.net/rfc/
> generics.
>
> 
>
> I think I am not the first one to suggest allowing contravariance for
> method parameters.
> E.g. here, "PHP RFC: Parameter Type Widening"
> https://wiki.php.net/rfc/parameter-no-type-variance
>
> From this RFC:
> > Unfortunately “true” contravariance for class types isn't part of this
> RFC, as implementing that is far more difficult, and would require
> additional rules about autoloading and/or class compilation, which might
> only be acceptable at a major release.
>
> For anyone not familiar with the term:
>
> interface I {
>   function foo(J $arg);
> }
>
> interface J extends I {
>   function foo(I $arg);
> }
>
> So: While return types in a child method should be either the same or
> more narrow, the parameter types should be either the same or more
> permissive.
> Without this it would break Liskov substitution.
>
> ---
>
> Now for my actual proposal: The "empty type".
> We can think of a type (class/interface or primitive) as a set or a
> constraint on the kind of values that it allows.
> There is a special type, "mixed", which allows all values. We could
> also think of it as the union of all types.
>
> A natural extension of this concept, on the other end, would be a type
> "nothing" or "empty", which would allow no values at all.
> We could think of this as the intersection of all types.
> In fact it is already sufficient to intersect just two distinct
> primitive types to get this empty type:
> "All values that are at the same time string and integer" clearly is
> an empty type.
>
> How would this ever be useful?
> If we write a base class or interface for a category of interfaces
> that have a similar signature.
>
> 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);
> }
>
> One could imagine a component that has a list of AbstractFruitEater
> objects, and chooses one that is suitable for the given fruit, using
> instanceof.
> I think the correct term is "chain of responsibility".
>
> function eatApple(array $fruitEaters, Apple $apple) {
>   foreach ($fruitEaters as $eater) {
> if ($eater instanceof AppleEater) {
>   $eater->eat($apple);
>   break;
> }
>   }
> }
>
> 
>
> We can go one step further.
>
> The natural parameter type to use for param $fruit in
> AbstractFruitEater::foo() would not be the global EMPTY_TYPE, but
> something more specific:
> The projected intersection of all real and hypothetical children of
> interface Fruit.
> Obviously this does not and cannot exist as a class or interface.
>
> Practically, for the values it allows, this is the same as the global
> EMPTY_TYPE.
> But unlike the EMPTY_TYPE, this would poses a restriction on the
> parameter type in child interfaces.
>
> What would be the syntax / notation for such a projected hypothetical
> subtype?
> I don't know. Let's say INTERSECT_CHILDREN
>
> So, would the following work?
>
> interface Food {..}
> interface Fruit extends Food {..}
> interface Banana extends Fruit {..}
>
> interface AbstractFoodEater {
>   function eat(INTERSECT_CHILDREN $food);
> }
>
> interface AbstractFruitEater extends AbstractFoodEater {
>   function eat(INTERSECT_CHILDREN $fruit);
> }
>
> interface BananaEater extends AbstractFruitEater {
>   function eat(Banana $banana);
> }
>
> I'm not sure.
> Liskov would not care. Both AbstractFoodEater and AbstractFruitEater
> are useless on their own.
> Maybe there are other logical conflicts which I don't see.
>
>
> --
>
> Obviously with generics this base interface would no longer be relevant.
> https://wiki.php.net/rfc/generics
>
> interface FruitEater {
>   function eat(FruitType $fruit);
> }
>
> // This is not really necessary.
> interface BananaEater extends FruitEater {
>   function eat(Banana $banana);
> }
>
> So, would the "empty type" become obsolete? Maybe.
> I did not arrive at a final conclusion yet. It still seems too
> interesting to let it go.
>
> -- Andreas
>

What's the purpose of this construction? I get the general idea (work
around LSP variance restrictions without generics), but I don't see how the
practical use would look like. After all, using the empty type as an
argument implies that the method may not ever be called, so wouldn't an
interface using it be essentially useless?

Nikita


[PHP-DEV] Contravariance and the "empty type"

2017-08-21 Thread Andreas Hennings
Hello list,
for a while I had this thought about contravariance and an "empty type".
I don't expect too much of it, for now I just want to share the idea.
Maybe this concept even exists somewhere in a different language, and
I am not aware of it.

I think it has some overlap with generics, https://wiki.php.net/rfc/generics.



I think I am not the first one to suggest allowing contravariance for
method parameters.
E.g. here, "PHP RFC: Parameter Type Widening"
https://wiki.php.net/rfc/parameter-no-type-variance

>From this RFC:
> Unfortunately “true” contravariance for class types isn't part of this RFC, 
> as implementing that is far more difficult, and would require additional 
> rules about autoloading and/or class compilation, which might only be 
> acceptable at a major release.

For anyone not familiar with the term:

interface I {
  function foo(J $arg);
}

interface J extends I {
  function foo(I $arg);
}

So: While return types in a child method should be either the same or
more narrow, the parameter types should be either the same or more
permissive.
Without this it would break Liskov substitution.

---

Now for my actual proposal: The "empty type".
We can think of a type (class/interface or primitive) as a set or a
constraint on the kind of values that it allows.
There is a special type, "mixed", which allows all values. We could
also think of it as the union of all types.

A natural extension of this concept, on the other end, would be a type
"nothing" or "empty", which would allow no values at all.
We could think of this as the intersection of all types.
In fact it is already sufficient to intersect just two distinct
primitive types to get this empty type:
"All values that are at the same time string and integer" clearly is
an empty type.

How would this ever be useful?
If we write a base class or interface for a category of interfaces
that have a similar signature.

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);
}

One could imagine a component that has a list of AbstractFruitEater
objects, and chooses one that is suitable for the given fruit, using
instanceof.
I think the correct term is "chain of responsibility".

function eatApple(array $fruitEaters, Apple $apple) {
  foreach ($fruitEaters as $eater) {
if ($eater instanceof AppleEater) {
  $eater->eat($apple);
  break;
}
  }
}



We can go one step further.

The natural parameter type to use for param $fruit in
AbstractFruitEater::foo() would not be the global EMPTY_TYPE, but
something more specific:
The projected intersection of all real and hypothetical children of
interface Fruit.
Obviously this does not and cannot exist as a class or interface.

Practically, for the values it allows, this is the same as the global
EMPTY_TYPE.
But unlike the EMPTY_TYPE, this would poses a restriction on the
parameter type in child interfaces.

What would be the syntax / notation for such a projected hypothetical subtype?
I don't know. Let's say INTERSECT_CHILDREN

So, would the following work?

interface Food {..}
interface Fruit extends Food {..}
interface Banana extends Fruit {..}

interface AbstractFoodEater {
  function eat(INTERSECT_CHILDREN $food);
}

interface AbstractFruitEater extends AbstractFoodEater {
  function eat(INTERSECT_CHILDREN $fruit);
}

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

I'm not sure.
Liskov would not care. Both AbstractFoodEater and AbstractFruitEater
are useless on their own.
Maybe there are other logical conflicts which I don't see.


--

Obviously with generics this base interface would no longer be relevant.
https://wiki.php.net/rfc/generics

interface FruitEater {
  function eat(FruitType $fruit);
}

// This is not really necessary.
interface BananaEater extends FruitEater {
  function eat(Banana $banana);
}

So, would the "empty type" become obsolete? Maybe.
I did not arrive at a final conclusion yet. It still seems too
interesting to let it go.

-- Andreas

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