Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:122331 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 79129 invoked from network); 7 Feb 2024 15:31:01 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 7 Feb 2024 15:31:01 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1707319914; bh=Q8a6BGesFLwbQ+bkE/TIJssfuykp4MVGKOHodJNkthQ=; h=In-Reply-To:References:Date:From:To:Subject:From; b=PzjaIvXYY8KdT5dmGm50ml3MKL4VoUsm6Y9sX8aPKVbPV8GflgofApVcvPOKXDu/E t9yCXiWCde6SSXF61T9hLUsbKZwVIqJeDRkp/OnsC+jQvPGkq61pLH8qMwew4r9TZy zfRepQSj9ClGvvvcpEW0n1jBEiZTsiYV1zyGKWSv0FKHLtL/PfRnRHs8L77A9pYpsH 94a8oSstp4zYyiAM1zOSezfeLEva4iCZ0hkMeRIh1SFUcd7QOAZGug205rQ/vUhEUR eBCgz1JH/7iYASR7b/oJHHuCk7GpbTfd/wbeqXLB7B4HT6tMzeUSATqJrLTsib78kd s1sJhlLpyLGfA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 63A59180042 for ; Wed, 7 Feb 2024 07:31:53 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-3.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H5,RCVD_IN_MSPIKE_WL,SPF_HELO_PASS,SPF_NONE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from wout4-smtp.messagingengine.com (wout4-smtp.messagingengine.com [64.147.123.20]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 7 Feb 2024 07:31:52 -0800 (PST) Received: from compute2.internal (compute2.nyi.internal [10.202.2.46]) by mailout.west.internal (Postfix) with ESMTP id 19C5F3200A1F for ; Wed, 7 Feb 2024 10:30:59 -0500 (EST) Received: from imap50 ([10.202.2.100]) by compute2.internal (MEProxy); Wed, 07 Feb 2024 10:30:59 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; h=cc:content-transfer-encoding:content-type :content-type:date:date:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to; s=fm3; t=1707319858; x=1707406258; bh=bOcQ/bF3GT9LGGXpXF6iD rXpM4BWXgYntBkrIz1SXXs=; b=xuUG7b8KMa71st3hwPgb5lH+VkDfjhnDgWqA1 aP7pjf4jPO+nBVsrQGdhBTCLUmW6TM4pJgAtOrn4PeBynmspNhJMHlbAi/X2/4yN SeqS1xkq2hml4Mt95urhf7kPYJXiDT6VLfH+zLeJVWoynBVEIk9Q4gw6DeRiJQdI J2ow+tcnS54FZ3XVKrkWCD2Op1Owsy+ZgBYUh2pBqRX41WaP5QlLQWttQap3tQw7 0MunNWmlYbNSN8fEpSdSkHgriO7vwNIArpmFM1EDRYe5uBEF95wvvS/qmMFcB391 5VljDeV8jLat0fm+iIQc+4EiBkpMbiC2GWu1DKYZ8tP93x4Lg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding:content-type :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=1707319858; x= 1707406258; bh=bOcQ/bF3GT9LGGXpXF6iDrXpM4BWXgYntBkrIz1SXXs=; b=y dJ8w0dnLr3En0bxwsBLaiYVE7/hsvXfRXCS5DABBbsV324xwBN8z9fgWyV1WAQAa ehK7jXM9FqiqXBvKl6a4/mcBQBCXPbBMx6PBbAZZnCx5qcZobLN0TdMLqYto6Jgs +KlmVIvVg9DAR8KKB/ZiJsWAEV2rZfdz6yhph4H4ct04OAh1dqmwtkUixc6DmIS9 i7t+uuyr7YbOHxfb6HmiSklVA83DVMec4ZsfuWLciYm2LmqBB1ge7tCnWYHm6jfQ FIxpeji9A8nXn4SgN6giBiYpQNOhNKBwE6v4wY6Bi5+pcaZQYgT6AKdiwmX/LL4u 8qE3OmLC2uso39r6NphJA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrtddvgdejhecutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenuc fjughrpefofgggkfgjfhffhffvufgtgfesthhqredtreerjeenucfhrhhomhepfdfnrghr rhihucfirghrfhhivghlugdfuceolhgrrhhrhiesghgrrhhfihgvlhguthgvtghhrdgtoh hmqeenucggtffrrghtthgvrhhnpefhkedtfffghfekieduhedvheefgfefheeugfdvleet teektefgteejieeltdelkeenucffohhmrghinhepghhithhhuhgsrdgtohhmnecuvehluh hsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheplhgrrhhrhiesghgr rhhfihgvlhguthgvtghhrdgtohhm X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id BD8881700097; Wed, 7 Feb 2024 10:30:57 -0500 (EST) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.11.0-alpha0-144-ge5821d614e-fm-20240125.002-ge5821d61 MIME-Version: 1.0 Message-ID: <1cd8fa7b-ab62-4563-bf13-bb7801f1ce8d@app.fastmail.com> In-Reply-To: References: <742f202d-7990-4f51-b903-7a15e3fd33c2@app.fastmail.com> Date: Wed, 07 Feb 2024 15:30:36 +0000 To: "php internals" Content-Type: text/plain;charset=utf-8 Content-Transfer-Encoding: quoted-printable Subject: Re: [PHP-DEV] Feature request: https://github.com/php/php-src/issues/13301 From: larry@garfieldtech.com ("Larry Garfield") On Wed, Feb 7, 2024, at 12:55 AM, Alex Wells wrote: > On Tue, Feb 6, 2024 at 7:14=E2=80=AFPM Larry Garfield > wrote: > >> These two samples *are logically identical*, and even have mostly the= same >> performance characteristics, and both expose useful data to static >> analyzers. They're just spelled differently. The advantage of the s= econd >> is that it could be implemented without generics. (ADTs would be an >> optional nice-to-have.) And if the caller doesn't handle DivByZero, = it >> would try to pass it up to its caller, but being checked it would req= uire >> the caller to also declare that it can raise DivByZero. >> > > Let's assume that the developer knows the divisor isn't 0 - through an > assertion or an `if` clause above the call to `divide(5, $divisor)`. In > this case, DivByZero error cannot ever be thrown (or risen), but the > developer would still have to either handle the error (which will never > happen) or declare it as raisable, which in turn may require also mark= ing > 10+ function/method calls as "raises DivByZero". Both options aren't g= reat. > > And even if there was no assertion about the divisor, maybe the develo= per's > intent is exactly to ignore that case as an "implicit assertion" - mea= ning > instead of explicitly asserting the divisor value themselves (through > `assert($divisor !=3D=3D 0)`), they rely on `divide(5, $divisor)` doin= g that > implicitly for them. If the `assert()` fails, then nobody is expected = to > really handle that assertion error; it usually bubbles up to the global > exception handler which takes care of it. If the `divide()` fails on t= he > other hand, checked exceptions would require all the callers to actual= ly > "check" it by catching or declaring the caller function as `raises > DivByZero`, but this doesn't bring any benefit to the developer in this > case. > > So I assume this is why Java developers hate checked exceptions and why > Kotlin doesn't have them. I'm not aware of other implementations of ch= ecked > exceptions; there may be other, better versions of them. If you have a= ny in > mind that overcome the issues above, I'd be interested to look into th= em :) Re assertions: The problem with assertions is they can be disabled. The= y're really *only* useful as an extra "extended type check", and then on= ly in dev. That makes them unreliable, so using them for flow control i= s right out. And in practice they just turn into exceptions anyway (or = Throwables at least), so there's really no benefit over just using a Thr= owable if you're going to insist they aren't disabled for the code to wo= rk. The Joe Duffy article I linked above describes the issues with Java's ex= ception design. Mainly, it's only mostly-checked. It forces you to dec= lare your throwables... but certain types of throwables don't need to be= declared, which means as a consumer of a function, you have no guarante= e that a function that has no declared throws will actually never throw.= So you get all the pain, none of the gain. (This is admittedly a chal= lenge for introducing them to PHP as well, which is why I am proposing a= separate syntax from exceptions since they would serve a different purp= ose.) As discussed in the article, Midori (the experimental language for which= Duffy was tech lead) had checked exceptions that worked essentially as = I have proposed here. What made them work is * They were very lightweight. * They were firmly and strictly checked, without any "holes" in the desi= gn like Java. * They were used locally, as an unwrapped Either monad, rather than for = up-the-stack communication. * Midori had a much more robust type checker than Java, so more errors c= ould be moved to the type system and eliminated entirely. * The built-in type hierarchy was more sensible than Java's. * Midori has guards, which eliminate 99% of cases. It's essentially pro= moting assertion-esque type checking into the function signature. That = is, DivByZero wouldn't even be an exception, it would be a runtime enfor= ced type error. I'd love to have these, too, but that's not the topic r= ight now. :-) Guards would look something like this (using Midori-inspired syntax): function divide(float $a, float $b): float require $b !=3D=3D 0 ensures = return !=3D INF {=20 // ... } (In Midori, those could either be materialized into code or compiled awa= y if the compiler could guarantee they held true. In PHP I don't think = we could compile them away so they'd have to be materialized, but it wou= ld make them more apparent to static analyzers as well as better communi= cate intent to other developers.) The article goes into much more detail, and I really do encourage readin= g it. To your specific question about prior knowledge (eg, non-zero), there's = a couple of ways, conceptually, to address that. 1. A more robust type system can handle things like non-zero-int or unsi= gned-int as a type. (I believe Midori has this, but honestly it's unlik= ely for PHP.) 2. Guard clauses. 3. Better syntax making handling "no op errors" easier. For the third, just to spitball: function divide(float $a, float $b): int raises DivByZero { if ($b =3D=3D=3D 0) raise new DivByZero(); return new $a/$b; } $result =3D try divide(5, 0) on DivByZero null; // Equivalent to: try { $result =3D divide(5, 0); } catch (DivByZero) { $result =3D null; } But the basic point is that DivByZero is probably a bad use case example= as that should be an Abandonment case (ie, type failure), just the easi= est one I came up with on the spot. :-) To use the more practical example,=20 findProduct(int $id): Product raises ProductNotFound { } function mycontroller(int $id) { $product =3D try findProduct($id) on ProductNotFound return view('no= t_found'); return view('product', $product); } If you're 100% certain the ID is valid, you could do "on ProductNotFound= null" as a no-op case. However, I suspect in practice that is a minori= ty case. Routing would probably be more like this: function findRoute(Request $request): Route raises RoutingError try $route =3D findRoute($request); catch (RouteNotFound $r) { // Do stuff. } catch (MethodNotAllowed $r) { // Do stuff. } // ... (Though in fairness, I'd probably use a proper monad for routing instead= anyway.) --Larry Garfield