Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:125600 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id 66EF31A00BD for ; Tue, 17 Sep 2024 19:25:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1726601274; bh=ETMxG4t6JiN5l5GLqWMQ2jDJ3KPCS7nQsLizMwrZ7CY=; h=Date:Subject:To:References:From:In-Reply-To:From; b=Ntf7HIKufo86XydTIRqME4B79gpg8tqfRV1Ss39Nk228twmeOiwAHGQ7ATZ5bUk2/ 2+D0jJnfMhEco5CCw9QVdEFXL55nD/5qMH1SP9/3J4W1jmIlsYpiso7H0xphoNIiPy A9vwrkmHRFrXv1f6UCUbfzA6C6vACXV+Nx51aDjJiFyIy5m9bCEk2spvhjXOMXcAXE gT7CiB1+K6ZFo4LqzELY3pbS+LHBKVS0GFaZc9EJxIDYKd7U0+OJF/hpmVHKXT0sOD mjV2g9cv17/Nnm2Bp7ePjL/8aRp+9YXrx72nh8M+q744ZLMyXOzPgR9xPz/rnGBUtu 3NBfDcEHMo75A== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 1126A180054 for ; Tue, 17 Sep 2024 19:27:53 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fhigh5-smtp.messagingengine.com (fhigh5-smtp.messagingengine.com [103.168.172.156]) (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 ; Tue, 17 Sep 2024 19:27:52 +0000 (UTC) Received: from phl-compute-11.internal (phl-compute-11.phl.internal [10.202.2.51]) by mailfhigh.phl.internal (Postfix) with ESMTP id BC9E911401DA for ; Tue, 17 Sep 2024 15:25:46 -0400 (EDT) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-11.internal (MEProxy); Tue, 17 Sep 2024 15:25:46 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rwec.co.uk; h=cc :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=fm1; t=1726601146; x=1726687546; bh=wUSXVQ2uQx ax1C0wxFS6XDFbfzxehg0QgS2GyStlUm0=; b=DL35qgQNLhepiqHen/S7/3y5sE QMgZuOEvhvMoZdwvVV9x449kii1qV0zj3Jpk+rvhgwA3ZB3oUb01ZqZLFsbF+Ga/ +GLWPWY4n5Ahm8dAm6yxOF5ayiIbKOyTqs089NaMw49KsjD6pxzS3P0EqVkox7Sj Sjv0Xx0IxfNwBxMRbB8G0750/xb79SV6BWVPykCbxXqlnaBhBooOkewpd0QUcNoa JbG+RGRxiX1hogcEiBFTqTFnCEYbZZj6djgvLONsp6znkXK2S/DQUO7klaF5CK/8 4u6hEtnQaAdW5DY7uFy1XdKdJqM76Diy+/Ac42n03txJiU6/D1+Ub9DDNA4A== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc: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= fm1; t=1726601146; x=1726687546; bh=wUSXVQ2uQxax1C0wxFS6XDFbfzxe hg0QgS2GyStlUm0=; b=CUk36TsmIG0GSU+qEfIdPcdnEnhhxe99dBxnWQc1gWCL MThTsWsBZI2ecshp8B3z6HF/EY/BH13sTm1jT5iOqmTstYHMjnqSMYoF0cIRhfT/ 1+C0hzWnFAoP9KxSBNe1wifEFuiAEmJDP+vPKx9+nfJnluw3GRF4lotCrLGahZOp 3ix6MnIhnP5AE3c1RcocbTiL5GsqsGd6O9e6sZ+YhR7TL2BaXESTh4FRaRitkx59 BlHfWrfIWOd+oEETYXtwl+LKvkyGdIdYFItjK/Ec3/XRKj/EoyNIGu8xds8zP2wD 2yIyq6DrgCJ35F1lM50zQgLEBAu4Rhlt2iN+nl1Uwg== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeftddrudekjedgudeflecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp uffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecunecujfgurheptgfkff ggfgfuvfhfhfgjsegrtderredtvdejnecuhfhrohhmpedftfhofigrnhcuvfhomhhmihhn shculgfkoffuohfrngdfuceoihhmshhophdrphhhphesrhifvggtrdgtohdruhhkqeenuc ggtffrrghtthgvrhhnpeehteelieeigfeuudeiueeiffdvveehudeufeekjeeugffffedt iedtgeettdelteenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfh hrohhmpehimhhsohhprdhphhhpsehrfigvtgdrtghordhukhdpnhgspghrtghpthhtohep uddpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepihhnthgvrhhnrghlsheslhhish htshdrphhhphdrnhgvth X-ME-Proxy: Feedback-ID: id5114917:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA for ; Tue, 17 Sep 2024 15:25:46 -0400 (EDT) Content-Type: multipart/alternative; boundary="------------UoRLhg2uzwzXU6Zpc6nRHVZz" Message-ID: <0f0444eb-8fc5-4c56-8528-5aa528988e73@rwec.co.uk> Date: Tue, 17 Sep 2024 20:25:43 +0100 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PHP-DEV] [Pre-RFC Discussion] User Defined Operator Overloads (again) To: internals@lists.php.net References: <2551c06a-ec1f-4870-a590-aeb5752fc944@rwec.co.uk> Content-Language: en-GB In-Reply-To: From: imsop.php@rwec.co.uk ("Rowan Tommins [IMSoP]") This is a multi-part message in MIME format. --------------UoRLhg2uzwzXU6Zpc6nRHVZz Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit On 17/09/2024 18:15, Jordan LeDoux wrote: > > 1. Are we over-riding *operators* or *operations*? That is, is the > user > saying "this is what happens when you put a + symbol between two Foo > objects", or "this is what happens when you add two Foo objects > together"? > > > If we allow developers to define arbitrary code which is executed as a > result of an operator, we will always end up allowing the first one. I don't think that's really true. Take the behaviour of comparisons in your previous RFC: if that RFC had been accepted, the user would have had no way to make $a < $b and $a > $b have different behaviour, because the same overload would be called, with the same parameters, in both cases. Slightly less strict is requiring groups of operators: the Haskell "num" typeclass (roughly similar to an interface) requires definitions for all of "+", "*", "abs", "signum", "fromInteger", and either unary or binary "-". It also defines the type signatures for each. If this was the only way to overload the "+" operator, users would have to really go out of their way to use it to mean something unrelated addition. As it happens, Haskell *does* allow arbitrary operator overloads, and in fact goes to the other extreme and allows entirely new operators to be invented. The same is true in PostgreSQL - you can implement the <>  operator if you want to. I think it's absolutely possible - and desirable - to choose a philosophical position on that spectrum, and use it to drive design decisions. The choice of "__add" vs "operator+" is one such decision. > The approach I plan to use for this question has a name: Polymorphic > Handler Resolution. The overload that is executed will be decided by > the following series of decisions: > > 1. Are both of the operands objects? If not, use the overload on the > one that is. (NOTE: if neither are objects, the new code will be > bypassed entirely, so I do not need to handle this case) > 2. If they are both objects, are they both instances of the same > class? If they are, use the overload of the one on the left. > 3. If they are not objects of the same class, is one of them a direct > descendant of the other? If so, use the overload of the descendant. > 4. If neither of them are direct descendants of the other, use the > overload of the object on the left. Does it produce a type error > because it does not accept objects of the type in the other position? > Return the error and abort instead of re-trying by using the overload > on the right. This is option (g) in my list, with the additional "prefer sub-classes" rule (step 3), which I agree would be a good addition. As noted, it doesn't provide symmetry, because step 4 depends on the order in the source code. Option (c) is the same algorithm without step 4, so guarantees that $a + $b and $b + $a will always call the same method. Options (d), (e), and (f) each add an extra step: one operand can signal "I don't know" and the other operand gets a chance to answer. They're essentially ways to "partially implement" an operator. Options (a) and (b) perform the same kind of polymorphic resolution on *both* operands, which is how many languages work for functions and/or methods already. Reading the C# spec, if there is more than one candidate overload which is equally specific, an error is raised. I guess you could do the same even with one implementation per class, by replacing step 4 in your algorithm: > 4. If neither of them are direct descendants of the other, and only one implements the operator, use it. > 5. If neither of them are direct descendants of the other, and both implement the operator, throw an error. Let's call that option (h) :) By the way, searching online for the phrase "Polymorphic Handler Resolution" finds no results other than you saying it is the name for this algorithm. > This is similar to what I originally designed, and I actually moved to > an enum based on feedback. The argument was something like > `$isReversed` or `$left` or so on is somewhat ambiguous, while the > enum makes it extremely explicit. Ah, fair enough. Explicitness vs conciseness is always a trade-off. My thinking was that the "reversed" form would be far more rarely called than the "normal" form; but that depends a lot on which resolution algorithm is used. Regards, -- Rowan Tommins [IMSoP] --------------UoRLhg2uzwzXU6Zpc6nRHVZz Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit
On 17/09/2024 18:15, Jordan LeDoux wrote:

1. Are we over-riding *operators* or *operations*? That is, is the user
saying "this is what happens when you put a + symbol between two Foo
objects", or "this is what happens when you add two Foo objects together"?

If we allow developers to define arbitrary code which is executed as a result of an operator, we will always end up allowing the first one.


I don't think that's really true. Take the behaviour of comparisons in your previous RFC: if that RFC had been accepted, the user would have had no way to make $a < $b and $a > $b have different behaviour, because the same overload would be called, with the same parameters, in both cases.

Slightly less strict is requiring groups of operators: the Haskell "num" typeclass (roughly similar to an interface) requires definitions for all of "+", "*", "abs", "signum", "fromInteger", and either unary or binary "-". It also defines the type signatures for each. If this was the only way to overload the "+" operator, users would have to really go out of their way to use it to mean something unrelated addition.

As it happens, Haskell *does* allow arbitrary operator overloads, and in fact goes to the other extreme and allows entirely new operators to be invented. The same is true in PostgreSQL - you can implement the <<//-^+^-//>>  operator if you want to.

I think it's absolutely possible - and desirable - to choose a philosophical position on that spectrum, and use it to drive design decisions. The choice of "__add" vs "operator+" is one such decision.


 
The approach I plan to use for this question has a name: Polymorphic Handler Resolution. The overload that is executed will be decided by the following series of decisions:

1. Are both of the operands objects? If not, use the overload on the one that is. (NOTE: if neither are objects, the new code will be bypassed entirely, so I do not need to handle this case)
2. If they are both objects, are they both instances of the same class? If they are, use the overload of the one on the left.
3. If they are not objects of the same class, is one of them a direct descendant of the other? If so, use the overload of the descendant.
4. If neither of them are direct descendants of the other, use the overload of the object on the left. Does it produce a type error because it does not accept objects of the type in the other position? Return the error and abort instead of re-trying by using the overload on the right.


This is option (g) in my list, with the additional "prefer sub-classes" rule (step 3), which I agree would be a good addition.

As noted, it doesn't provide symmetry, because step 4 depends on the order in the source code. Option (c) is the same algorithm without step 4, so guarantees that $a + $b and $b + $a will always call the same method.

Options (d), (e), and (f) each add an extra step: one operand can signal "I don't know" and the other operand gets a chance to answer. They're essentially ways to "partially implement" an operator.

Options (a) and (b) perform the same kind of polymorphic resolution on *both* operands, which is how many languages work for functions and/or methods already. 


Reading the C# spec, if there is more than one candidate overload which is equally specific, an error is raised. I guess you could do the same even with one implementation per class, by replacing step 4 in your algorithm:

> 4. If neither of them are direct descendants of the other, and only one implements the operator, use it.
> 5. If neither of them are direct descendants of the other, and both implement the operator, throw an error.

Let's call that option (h) :)


By the way, searching online for the phrase "Polymorphic Handler Resolution" finds no results other than you saying it is the name for this algorithm.


This is similar to what I originally designed, and I actually moved to an enum based on feedback. The argument was something like `$isReversed` or `$left` or so on is somewhat ambiguous, while the enum makes it extremely explicit.


Ah, fair enough. Explicitness vs conciseness is always a trade-off. My thinking was that the "reversed" form would be far more rarely called than the "normal" form; but that depends a lot on which resolution algorithm is used.


Regards,

-- 
Rowan Tommins
[IMSoP]
--------------UoRLhg2uzwzXU6Zpc6nRHVZz--