[PHP-DEV] bikeshed: Typed Aliases

On Tue, Sep 10, 2024, at 10:00, Mike Schinkel wrote:

On Sep 9, 2024, at 5:35 PM, Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

On 09/09/2024 19:41, Mike Schinkel wrote:

In Go you cannot add or subtract on a typedef without casting to the 
underlying type.  I would definitely prefer that to be relaxed, but only
 if it is relaxed via an explicit opt-in, e.g. something maybe like 
this:

typedef UserId: int operations: +, -, *, /;
typedef UserName: string operations: .;

I think this would stray into some of the same complexity as operator overloads on objects, in terms of the types and values allowed. For instance:

I tend to agree that allowing operations may be too much for an initial scope given that it is unlike anything else in the current language and with no other languages offering an equivalent AFAIK.

I would however make the distinction that it is unlike operator overloading because the big concern was what constituted an operation for any given type could be too subjective. In your example of Metres it is pretty obvious, but not at all obvious for a User, for example. (BTW, thank you for not calling out my nonsensical example of operations on a UserId; when I wrote that I clear was not thinking about if they were relevant, doh!)

However give the suggestion regarding operations with a typedef, the only operations that I suggested would be valid would be the ones already defined on the underlying type, (when I mentioned other operations I was thinking of methods — see my the following example with round — not operators so that is not the same as operator overload.) For example:

/**

  • Currency is an int so for example in USD 1
  • unit of currency not a dollar but a cent.
    /
    typedef Currency: int operations: +,-,
    ,/,round;
    function CalcTotal(Currency $subTotal, Currency $shipping, float $tax):Currency {
    return round($subTotal*(1+$tax/100),0) + $shipping;
    }

This is very similar (behaviorally) to what I wanted to do with GMP. Overloading GMP would have given you int-like powers in your type. The biggest negative feedback I got was that people would abuse it still; so we need some way to prevent abuse. If you read Jordon’s operator overloads RFC, and my GMP overloading RFC, you can see that users basically need a way to define how to operate over even just an integer.

For example, Dollar(1) + Euro(3) is what? Or even Dollar(1) + 1? How does a developer prevent someone from doing something nonsensical? The language needs to enforce some rules and/or the developer needs to be able to define them. These rules need to be intuitive and well reasoned, IMHO.

typedef Metres: int;

assert( Metres(2) + Metres(1) === Metres(3) ); // most obvious

assert( Metres(2) + 1 === Metres(3) ); // seems pretty clear

Both of those are in line with what I was suggesting.

$_GET[‘input’] = ‘1’;

assert( Metres(2) + $_GET[‘input’] === Metres(3) ); // might be more controversial

I would not consider this appropriate as it has two levels of conversion and could thus end up with unintended edge cases. To do the above I think you would have to either convert or typecast:

assert( Metres(2) + intval($_GET[‘input’]) === Metres(3) );

assert( Metres(2) + (int)$_GET[‘input’] === Metres(3) );

typedef Feet: int;

assert( Metres(2) + Feet(1) === Metres(3) ); // almost certainly a bad idea

This would be operator overloading where knowledge of the conversion between meters and feet would be required, and that is not in any way in scope with what I was suggesting.

As an aside, I am against userland operator overloading as I have seen in other languages that operator overloading gets abused and results in code that is a nightmare to maintain. OTOH, I would support operator overloading in specific cases, e.g. a Measurement class in PHP core could allow adding meters to feet, assuming such a proposal were made and all other aspects of the RFC were of the nature to be voted in.

To reiterate on typedefs, what I was suggesting was that if an operation was explicitly allowed — e.g. + — then anything that would work with the underlying type — such as adding an int 1 would work without typecasting and yet still result in the typedef type, e.g. Meters(2) + 1 results in a value of type Meters. (note that I corrected your spelling of ‘Meters’ here. :wink:

But I agree, this is probably a bridge too far for a first RFC for typedefs.

type MyNewType: Foo
type MyAlias = Foo

I know this was only an example, but as a general point, I think we should avoid concise but cryptic differences like this. PHP is generally keyword-heavy, rather than punctuation-heavy, and I think that’s a valid style which we should keep to.

Here, I also tend to agree WRT PHP. Was just pointing out for sake of laying out other options that were implied not to exist.

-Mike

In other news, I’m highly considering refactoring the records RFC to be a typedef RFC; the infrastructure is there; we just need more restrictions.

— Rob

Hi Rob,

On Sep 10, 2024, at 4:31 AM, Rob Landers <rob@bottled.codes> wrote:

This is very similar (behaviorally) to what I wanted to do with GMP. Overloading GMP would have given you int-like powers in your type. The biggest negative feedback I got was that people would abuse it still; so we need some way to prevent abuse. If you read Jordon’s operator overloads RFC, and my GMP overloading RFC, you can see that users basically need a way to define how to operate over even just an integer.

For example, Dollar(1) + Euro(3) is what? Or even Dollar(1) + 1? How does a developer prevent someone from doing something nonsensical? The language needs to enforce some rules and/or the developer needs to be able to define them. These rules need to be intuitive and well reasoned, IMHO.

My arguments were that the use-cases where it is clear and objective what each operator would mean are few and far between[caveat, see below] and so rather than provide general operator overloading PHP should build those use-cases into a core extension.

For example, to add Dollar + Euro, having a Money or Currency extension as part of PHP core would make huge sense to me, but not giving everyone the ability to overload operators for every type.

I did not follow the GMP discussions as I have never needed to use that extension in any of my own PHP development so I am not familiar with the arguments made and may be mischaracterizing your proposal; correct me if I do.

BTW, where is your GMP RFC? I searched for it but could not find it. Did you propose operator overloading for GMP in core, or in userland?

For example, Dollar(1) + Euro(3) is what? Or even Dollar(1) + 1? How does a developer prevent someone from doing something nonsensical?

If the rules are built into core, then the compiler and/or runtime stops them from doing something nonsensical, right?

The language needs to enforce some rules and/or the developer needs to be able to define them.

My argument is that the language should enforce those rules as allowing the userland developer to overload operators will result in every developer defining different rules for their own classes, leading to a tower of babble. And the chances that many developers will do things nonsensical with their operators approaches 100%.

I do acknowledge not everyone agrees with me on this, and if so that is their right. If enough people disagree with me then PHP may eventually have general operator overloading. My hope is that there are not enough people who disagree.

These rules need to be intuitive and well reasoned, IMHO."

I hope anything that passes an RFC is intuitive and well reasoned because otherwise our governance model is flawed and maybe we need to address that first, if so?

-Mike

[caveat] — I am aware there are likely numerous use-cases in financial, scientific and/or related fields that could benefit from operator overloading but that would likely never be mainstream enough to make it into PHP core. For this I think making it easier to implement operator overloading in an extension would be a reasonable solution (if that is possible.) Yes, it would require people learn to develop extensions, but working groups for those use-cases could form, and if it could be done in a way that supports Zephir, then writing an extension should not be too hard. https://zephir-lang.com

That, or come up with some way to limit operator overloading to only those things where overloads are objectively obvious, but I really have no idea what kind of limits could do that and I doubt it is even possible.

And if none of those things then those communities can get by with what they have always been able to do; use methods and parameters. One thing is clear to me, PHP is never going to overtake Python (or Julia, et. al.) in those communities, so why appeal to a base that is not really interested except on the periphery? OTOH, Python is not likely to overtake PHP for general web development no matter how “popular” it becomes.

BTW, why has nobody ever mentioned Zephir on this list (that I am aware of?)

On 10 September 2024 19:32:19 BST, Mike Schinkel <mike@newclarity.net> wrote:

BTW, why has nobody ever mentioned Zephir on this list (that I am aware of?)

Zephir is an interesting idea that has never quite fulfilled its aims. The Phalcon developers hoped that creating a more PHP-like language would allow more people to work on extensions such as their framework, but it doesn't seem to have worked out that way.

The worse problem was that Zephir itself had very few contributors. A few years ago, the project came close to shutting down as there was nobody left to maintain it; Phalcon was to be rewritten in PHP. <https://blog.phalcon.io/post/the-future-of-phalcon&gt; Since then, somebody has stepped up, but Phalcon work is still focussed on the PHP rewrite, with the intention of a smaller, optional, extension providing performance-critical components. <https://blog.phalcon.io/post/phalcon-roadmap&gt;

Meanwhile, PHP 7 and 8 have massively increased both the performance and the capability of code written in PHP, and even FFI to bridge to existing native binaries (although I gather there's a lot that could be improved to make that more useful).

The overall trend is to have only what's absolutely necessary in an extension, and there have even been suggestions that some built-in functions would be better off implemented in PHP itself, if the right low-level features were included.

All of which is drifting a long way off topic, except to say that I think we should be aiming to reduce the difference between what can be done in extensions and what in PHP code, rather than planning any new such differences.

Regards,
Rowan Tommins
[IMSoP]

On Sep 10, 2024, at 5:35 PM, Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:
All of which is drifting a long way off topic,

Yes, very true.

I will start another thread with the title "Zephir and other tangents" to reply to your comments so as not to hijack this thread on those tangents, and so others can ignore it unless they really want to follow it.

-Mike

On Mon, Sep 9, 2024, at 4:35 PM, Rowan Tommins [IMSoP] wrote:

On 09/09/2024 19:41, Mike Schinkel wrote:

Referencing prior art (e.g. Go) PHP could allow int literals — e.g. `1`, `47`, etc. — to be passed to typedefs derived from ints but require int variables to be typecast to the required type. Same for string literals.

That's an interesting compromise, worth considering.

I have concerns about this. Mainly, it depends on what we would want a typeef to *do*. Eg, if it's just an alternate name, then maybe. If, however, typedefs allow other functionality -- such as validation, additional methods, etc. -- then primitive -> typeef is not a guaranteed total function. Eg;

typedef UserId: string is /\s{3}-\s{4}/ {
  public function groupId(): string {
    return substr($this, 3);
  }
}

Maybe that particular functionality makes sense to do, maybe not, that's a separate discussion. My point for now is just that there are typedef approaches where auto-up-casting would be frequently invalid, and probably also designs where auto-down-casting would be invalid (or possibly valid). For now, we should keep all options on the table until we decide which options we want to make impossible.

In Go you cannot add or subtract on a typedef without casting to the
underlying type. I would definitely prefer that to be relaxed, but only
if it is relaxed via an explicit opt-in, e.g. something maybe like
this:

typedef UserId: int operations: +, -, *, /;
typedef UserName: string operations: .;

Not to go further down this rabbit hole than is necessary, but I would much rather see operator overloads adopted, along the lines of Jordan's previous RFC, and let typedefs implement that if them if sensible.

There's probably yet another research project to do here. I'd volunteer, but I now have a newborn taking up most of my time. :slight_smile:

--Larry Garfield