Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:116678 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 2761 invoked from network); 17 Dec 2021 19:23:49 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 17 Dec 2021 19:23:49 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 76FC11804C3 for ; Fri, 17 Dec 2021 12:26:35 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,HTML_MESSAGE, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Virus: No X-Envelope-From: Received: from mail-lj1-f179.google.com (mail-lj1-f179.google.com [209.85.208.179]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Fri, 17 Dec 2021 12:26:34 -0800 (PST) Received: by mail-lj1-f179.google.com with SMTP id z8so5116496ljz.9 for ; Fri, 17 Dec 2021 12:26:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=PYcmtupU18nCG4jvaVG//5KACnNAiSioYhjlDWfgklc=; b=H2n6VOL3fncCOF5FRdIVQ8xNFTBK+Nx4lIo3RkYwqCtfT5t8GndjqSZQGNtEbGhQlK 2v821KVRCGIfS8G3yf4rNB9s4j1vIEsZMnjNyjr3DvQWoi9Dc4gnKw+ct2CG7dIfiZKA sZECdlChOKDas711NCSwNKVmEftKtw2uvUzjm0QGf53bRZF7iPFNZl5dw3Emr+iQVc8+ f9H58StEuyHwc4bL1tAJQFQ5vk7EbYIcvYBLXrQsBJqUkzuLHCwoHLqJ/6ZmLwyVa/+0 fiTd59FK3uizz1EC3HExX4f6q9TMOylUuvqYL5fZB4kA9ZJTy6i6Fd1FZvPUzBpmRnY2 399g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=PYcmtupU18nCG4jvaVG//5KACnNAiSioYhjlDWfgklc=; b=NNchk+s1PD2TpeCNdF7j7aUlAv8ffJn97gIXJisASvbtwKNkrYf9kpPdwQP45jiRR2 b6zkHh7xzTH4nvXv2Ealco4DAMrql5YpZXblcopQMt0BRbyMMvTY49lel9yGX/Lno0HS vH1Jn9FQVUA/4ZNRBd9wSLsCXWWWyjD+50iLBWDAuAEKBNWH24fuUi/M4uxZ15Q3StD3 bVwHqUGxRJPcO0HHaWihewxN8HlMo0Bm9lqdQAgWgRAbY0ybC1/JnNpW21y9KIsLYoLX 00/tTUo3VsPr63uFfwUBzEB0hrApjfBqGdQNpIKlLlwgqqDM9TDfNWXtebw9Hi6rPab2 ZpaA== X-Gm-Message-State: AOAM531B5WLjWtleg+466O7M83RX4rbFqIJ9/OExD1KIPIRpNYRW9wzC zNXn4p0tsU5PnkunupgXcPN0f+6TUe8xFNUz1ybr7/pckMU= X-Google-Smtp-Source: ABdhPJz2Y03ek4NKohrloDaSQeTY3nPjBl0ktqGS7lJiOKRyr3zz+bJPnVDACz5b6AvRmHoLLJRyWtecO4YiuTiNYbo= X-Received: by 2002:a2e:a786:: with SMTP id c6mr4188042ljf.161.1639772792955; Fri, 17 Dec 2021 12:26:32 -0800 (PST) MIME-Version: 1.0 References: <44b3fb4b-4693-1639-c8c0-5e17296c196e@gmail.com> <4b58c011-ed87-ba87-201d-0cf8e4116c6f@processus.org> In-Reply-To: Date: Fri, 17 Dec 2021 12:26:06 -0800 Message-ID: To: Jordan LeDoux Cc: PHP internals Content-Type: multipart/alternative; boundary="000000000000d6259d05d35d5ab1" Subject: Re: [PHP-DEV] [RFC] User Defined Operator Overloads (v0.6) From: matthewfonda@gmail.com (Matt Fonda) --000000000000d6259d05d35d5ab1 Content-Type: text/plain; charset="UTF-8" On Fri, Dec 17, 2021 at 10:37 AM Jordan LeDoux wrote: > On Fri, Dec 17, 2021 at 9:43 AM Matt Fonda wrote: > >> Hi Jordan, >> >> Thanks for the RFC. I have a couple questions: >> >> Suppose I have classes Foo and Bar, and I want to support the following >> operations: >> >> - Foo * Bar (returns Foo) >> - Bar * Foo (returns Foo) >> >> If I understand correctly, there are three possible ways I could >> implement this: >> >> a) Implement the * operator in Foo, accepting a Foo|Bar, and use the >> OperandPosition to determine if I am doing Foo * Bar or Bar * Foo and >> implement the necessary logic accordingly. >> b) Implement the * operator in Bar, accepting a Foo|Bar, and use the >> OperandPosition to determine if I am doing Foo * Bar or Bar * Foo and >> implement the necessary logic accordingly. >> c) Implement the * operator in Foo, accepting a Bar (handles Foo * Bar >> side); Implement the * operator in Bar, accepting a Foo (handles Bar * Foo >> side) >> >> Is this understanding correct? If so, which is the preferred approach and >> why? If not, can you clarify the best way to accomplish this? >> > > You are correct in your understanding. All three of these would accomplish > what you want, but would have varying levels of maintainability. Which you > choose would depend on the specifics of the Foo and Bar class. For > instance, if the Bar class was one that you didn't ever expect to use on > its own with operators, only in combination with Foo, then it would make > sense to use option 1. The inverse would be true if Bar was the only one > you ever expected to use with operators on its own. > > The better way, in general, would be for Foo and Bar to extend a common > class that implements the overload in the *same* way for both. In most > circumstances, (but not all), if you have two different objects used with > each other with operators, they should probably share a parent class or be > instances of the same class. Like I said, this isn't always true, but for > the majority of use cases I would expect it is. > > >> Next, suppose I also want to support int * Foo (returns int). To do this, >> I must implement * in Foo, which would look like one of the following >> (depending on which approach above) >> >> public operator *(Foo|int $other, OperandPos $pos): Foo|int { ... } >> public operator *(Foo|Bar|int $other, OperandPos $pos): Foo|int { ... } >> >> Now, suppose I have an operation like `42 * $foo`, which as described >> above, should return int. It seems it is not possible to enforce this via >> typing, is that correct? i.e. every time I use this, I am forced to do: >> >> $result = 42 * $foo; >> if (is_int($result)) { >> // can't just assume it's an int because * returns Foo|int >> } >> > > In general I would say that returning a union from an operator overload is > a recipe for problems. I would either always return an int, or always > return an instance of the calling class. Mostly, this is because any scalar > can be easily represented with a class as well. > > Jordan > Hi Jordan, Thanks for the info. I share Stas's unease with having many different places we must look in order to understand what $foo * $bar actually executes. I'm also uneasy with the requirement of union typing in order for an operator to support multiple types. This will lead to implementations which are essentially many methods packed into one: one "method" for each type in the union, and potentially one "method" for each LHS vs. RHS. When combined, these two issues will make readability difficult. It will be difficult to know what $foo * $bar actually executes, and once we find it, the implementation may be messy. I agree that returning a union is a recipe for a problem, but the fact that the input parameter must be a union can imply that the return value must also be a union. For example, Num * Num may return Num, but Num * Vector3 may return Vector3, or Vector3 * Vector3 may represent dot product and return Num. But let's not get hung up on specific scenarios; it's a problem that exists in the general sense, and I believe that if PHP is to offer operator overloading, it should do so in a way that is type safe and unambiguous. Method overloading could address both issues (LHS always "owns" the implementation, and has a separate implementation for each type allowed on the RHS). But I see this as a non-starter because it would not allow scalar types on the LHS. It's difficult to think of a solution that addresses both of these issues without introducing more. One could imagine something like the following: register_operator(*, function (Foo $lhs, Bar $rhs): Foo { ...}); register_operator(*, function (Bar $lhs, Foo $rhs): Foo { ...}); register_operator(*, function (int $lhs, Foo $rhs): int { ...}); But this just brings a new set of problems, including visibility issues (i.e. can't use private fields in the implementation), and the fact that this requires executing a function at runtime rather than being defined at compile time. I don't have any ideas that address all of these issues, but I do think they deserve further thought. Thanks, --Matt --000000000000d6259d05d35d5ab1--