Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:100277 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 95642 invoked from network); 22 Aug 2017 08:40:03 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 22 Aug 2017 08:40:03 -0000 Authentication-Results: pb1.pair.com smtp.mail=nikita.ppv@gmail.com; spf=pass; sender-id=pass Authentication-Results: pb1.pair.com header.from=nikita.ppv@gmail.com; sender-id=pass Received-SPF: pass (pb1.pair.com: domain gmail.com designates 209.85.214.42 as permitted sender) X-PHP-List-Original-Sender: nikita.ppv@gmail.com X-Host-Fingerprint: 209.85.214.42 mail-it0-f42.google.com Received: from [209.85.214.42] ([209.85.214.42:36751] helo=mail-it0-f42.google.com) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 44/C5-34801-2EDEB995 for ; Tue, 22 Aug 2017 04:40:02 -0400 Received: by mail-it0-f42.google.com with SMTP id 77so41473746itj.1 for ; Tue, 22 Aug 2017 01:40:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc; bh=dzBH6xKYyumBS85XKQ/FX14J5WNYor6Q2aHgc++Wm/Q=; b=vRA68JxvscBnN/HgoiJIGxh9PphJDA3jyzz7oVeZktPb1lS7xH1NLYbMysxxnt+CmA 9u+kHB6L9T3QQvu/7dTGG/yR4OI17DXdG/auN837WwbulUo3ge99NNdQ7Y/vu4DdTGXl ZXJbhteuSoZCzuopItrEg/KoxnDivnQXmNfSTPip6h5RmUtUuKRAn3SMhdOMz0hOIVwm opwP6+/I7+YfWshf2FCZNJzcGaDepgm96M+8QxnHIrO2/L9SJVHylHD1f8FhaFLl1bmz R4akVEKnpFtnwX/fCusw/YhAujyKXRpxZFRKI3/z79o+378WaavpHbv7zW8MV6554LvL IdJQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:in-reply-to:references:from:date :message-id:subject:to:cc; bh=dzBH6xKYyumBS85XKQ/FX14J5WNYor6Q2aHgc++Wm/Q=; b=rBeEwd6vA4u6IBsK97ggBJ6jy9XoUpNm99lwPuFAuerCXwKsmr9N5PzZEd2XednEpQ 0pW9hg6vBihBx/1iXzr2nofI481B1Kci10J+N5uJOlSmlANSt1b9Y33oQmoGEFsU0Vf0 s0eGT1sKpJRy+JfDbEPKc+Y5B5Pwza1VA/zH8VuXqlJYzmVSAdvCWMN/mUbUZcwOWT+k yiba8ESGZgCilN7McnEa05neLVDR0GBWW/qpQf2I1qiAQyO8A2it085q0RyevbTIzirz hRxaHeAaTAvfpN9ET+hVLj0yPCaC5/0VZE5vlCCMk4zZOMcG5YyUGhjrSECC1VyH9WQB KQmQ== X-Gm-Message-State: AHYfb5jsLg7UAQYrnS9hONzb9pHApsacZMowLXe7/0/3yHzK6dyFyJRa v2YJi9LwMBSkwPuP3ZvGBIIp/DjeEQ== X-Received: by 10.36.254.139 with SMTP id w133mr2475199ith.44.1503391200107; Tue, 22 Aug 2017 01:40:00 -0700 (PDT) MIME-Version: 1.0 Received: by 10.107.13.3 with HTTP; Tue, 22 Aug 2017 01:39:59 -0700 (PDT) In-Reply-To: References: Date: Tue, 22 Aug 2017 10:39:59 +0200 Message-ID: To: Andreas Hennings Cc: PHP internals Content-Type: multipart/alternative; boundary="94eb2c03564670e44d0557538aff" Subject: Re: [PHP-DEV] Contravariance and the "empty type" From: nikita.ppv@gmail.com (Nikita Popov) --94eb2c03564670e44d0557538aff Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable 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 =E2=80=9Ctrue=E2=80=9D 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 --94eb2c03564670e44d0557538aff--