Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128646 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 lists.php.net (Postfix) with ESMTPS id 045A21A00BC for ; Sat, 6 Sep 2025 00:48:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1757119592; bh=ady9pY5l/HZ136kxpLB3USZQvfiQp20xEfS5dyXWp6c=; h=Date:From:To:In-Reply-To:References:Subject:From; b=Xvfl3mUycacOrD+LjPcjxasn9xEZVyhWQ39OQzKwCwqbUHkPT+k7m51Ccjf3I/9c+ kmfalVv36xce6ltvn86MoUnIT+JkVDCK+TDY/NBAa6lbmaTIIwNBejuhn+VEXCCxyF dzTPEF3ykqLHEEGOXzlDB22hI8BgPNiWMUvGoxgB2DoIYEbg5P12k1Bk+Za9l2FjWL V0ItnGqw6+rFqEs77r2ej+SHv3kjVzRPzj/0/Hlz3ScCUSguDOnSvukJHKTjo6ywaZ 9XlgGHXf+zH3gLA8Y5NPhaSqv+EQbY0RoTEj60rwfUbGfbDwJ1ocV06MlQAOcwDDwG vwfvZSRMLLJLw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id D100B180054 for ; Sat, 6 Sep 2025 00:46:30 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,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.1 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-b5-smtp.messagingengine.com (fhigh-b5-smtp.messagingengine.com [202.12.124.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 ; Sat, 6 Sep 2025 00:46:30 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.internal [10.202.2.45]) by mailfhigh.stl.internal (Postfix) with ESMTP id D87107A0376 for ; Fri, 5 Sep 2025 20:47:57 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Fri, 05 Sep 2025 20:47:57 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; 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=fm2; t=1757119677; x=1757206077; bh=vPwQQJuof/ ySBuz7tR1Jk++yS2c7hJxM5tQxBlaRtMc=; b=L7EUF2rvEO2QsLlekwa8avxGlE vvzMYpaCveOArCsZnIMs865GQmhk6jrxRYwcoLxVnj5xV5Jy5Mcao4L2dr8dbsaT VnMLcaKiVTKFBW6EWHYaUxnZe4h7mjWOV0nCunV/YkNzyWgAgtIIaxMCWJqPmHye b1fON3HyiWQ1udF4vzoP4GDm7jJTeuHoNPRJkD+2hoy1n6TU8xW3AqVonAvjYnBe sHSdlaYkLi0xMCoVfUXCd3Sd+z0HMVx6pyoBN/RQYvl0X3qF4ulXH9vUjI6prNEd krIw+I8dXlzP3NX6QF2tfcU2IIxtt9A/BvWWek2OiVJazlPf3RuNozXAA9Lw== 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-sender:x-me-sender:x-sasl-enc; s=fm1; t= 1757119677; x=1757206077; bh=vPwQQJuof/ySBuz7tR1Jk++yS2c7hJxM5tQ xBlaRtMc=; b=Osuu5UMHo+mEjphcD79cWjsJYtqURwOto8BJcH93bilgMiaO/b8 puPiiA9/ZMhxITtIkkHaXy7nqNKPp8ZgSHSlqmw7kmOj1+BiXoGwX72p7VEyQM2E eYSvwNK8K8FrJceFjmxgcx4wRR2DScFqCC/83cNJBhLRlERLjllYWq8NMsPUMWXV uyPW8BJUfszuuSHa+B+rDmBAxtkmFcoJ6bHDO05h3R5z3E6UEKeGxtsYlZ1icfAT lBPaHnhtyyfkFostLEjdHp44GSzsqCF3OoLMbqGdCNaiXEKZzyZNxridGCApW4RG 6iQb38YVMptqHEXBMBSTU0Q9wcZFG0Ke8FQ== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddutdefjecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr ihhlohhuthemuceftddtnecunecujfgurhepofggfffhvffkjghfufgtsegrtderreertd ejnecuhfhrohhmpedftfhosgcunfgrnhguvghrshdfuceorhhosgessghothhtlhgvugdr tghouggvsheqnecuggftrfgrthhtvghrnheptdeujedttefhueelhfdtleeiudetlefftd duleehffegtdeihefhleeijefgveegnecuvehluhhsthgvrhfuihiivgeptdenucfrrghr rghmpehmrghilhhfrhhomheprhhosgessghothhtlhgvugdrtghouggvshdpnhgspghrtg hpthhtohepuddpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepihhnthgvrhhnrghl sheslhhishhtshdrphhhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 2F9E0182007A; Fri, 5 Sep 2025 20:47:57 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: list list-help: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: AFxHlD4AvJc3 Date: Sat, 06 Sep 2025 02:47:35 +0200 To: internals@lists.php.net Message-ID: <366fba57-cbef-4201-9b72-dbafcec0c521@app.fastmail.com> In-Reply-To: <7B9311D1-E433-4DBF-A85D-A7DEA94FCC4C@rwec.co.uk> References: <9c875e83-ddc8-4c5c-a368-747bef46e4a2@app.fastmail.com> <25e79d21-7b7f-4d7f-b5cb-1e9cfd0aa657@varteg.nz> <7B9311D1-E433-4DBF-A85D-A7DEA94FCC4C@rwec.co.uk> Subject: Re: [PHP-DEV] enum flag backed enum Content-Type: multipart/alternative; boundary=46fb96a0f1ec4c6ab5af9f981134e8eb From: rob@bottled.codes ("Rob Landers") --46fb96a0f1ec4c6ab5af9f981134e8eb Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Fri, Sep 5, 2025, at 22:01, Rowan Tommins [IMSoP] wrote: > On 4 September 2025 15:50:08 BST, Rob Landers wrot= e: > >I think this is a fair observation and a fair question; but I think i= t is important not to have "magic". The power-of-two rule is to make it = possible to work back how $enum->value =3D=3D=3D 15 (0x1111) even if you= are completely new to the language. If you just use some magical cardin= al order, it is impossible to reserve ranges, handle communications with= external systems, etc. >=20 >=20 > A set does not need elements to have a defined order, only a defined i= dentity; that is available on any enum, even one with no backing at all.= That pure set could be serialised in various ways, based on the availab= le serialisations of its elements (comma-separated list, integer bitmask= , binary string bitmask, etc). That's the strongly typed model. >=20 > The weakly typed model is to keep the values permanently in their seri= alised form, and manipulate that directly. That is, you have a set of in= tegers for the flags, and construct a new integer for the set of flags. = That has the advantage of being simple and efficient, at the cost of saf= ety and easy tool affordance. >=20 > What you're suggesting sounds like somewhere between the two: the indi= vidual flags are of a specific type, rather than raw integers, but the s= et itself is just an integer composed of their "backing values". The big= downside I see is that you can't natively label a parameter or return v= alue as being a "set of flags from this enum". You could probably make a= docblock type annotation work with an external static analyser, but in = that case, you might as well use that tool to enforce the powers of 2 on= your enum or Plain Old Constants. Interesting... but we don=E2=80=99t really have a way to say that a para= meter or return value must satisfy *any* constraints except that it be o= f a certain type. Hmmm, I guess we have "true" and "false" as values tha= t can be returned/accepted by functions =E2=80=94 but I think they=E2=80= =99re the only ones. A form of generics could solve *some* of this... an enum is a set, so wh= at we really want to say is: "map this set of PHP identifiers to this ot= her set of values". Which we already have via backed enums. What we lack= is some set of operators to allow us to select subsets of the set and r= epresent them. I was thinking of bitwise operators, because it seems the= most natural. In fact, my extension approach was to just inject some op= erators on the class entry and call it a day. But with your response and my implementation that needed to deal with st= ring-backed enums... The bitwise operators with enums would be a bit wei= rd. Because | is a type union and saying Foo::PrettyPrint | Foo::ThrowOn= Error also kind of a union, but really, we=E2=80=99re constructing a new= subset. So, maybe | isn=E2=80=99t the best operator. Then, when you wou= ld check, you use & (Foo::PrettyPrint & $val =3D=3D=3D 0)? It is not tha= t elegant looking... idiomatic in languages like C, sure, but PHP ... no= t so much. If I were to step back, I=E2=80=99d probably end up with something like = this: // use array-ish syntax to say that it is a subset of Foo $foo =3D Foo::[PrettyPrint, ThrowOnError]; // have a function accept any Foo but returns only specific subsets of F= oo. function doFoo(Foo $anyFoo): Foo::[PrettyPrint, ThrowOnError]|Foo::[Pret= tyPrint]|null // check if a value of Foo intersects with another set of Foo return enum_intersects($anyFoo, Foo::[PrettyPrint]); In this case, backing values don=E2=80=99t matter at all. Serialization = would still be an issue, but then again, it is already. >=20 > Indeed, enforcing "this integer must be a power of 2" is not really an= ything to do with enums, it would be useful on *any* type declaration. >=20 >=20 > Personally, I find the concept of enum cases having a single backing v= alue unnecessarily limiting, and would much prefer Java-style case prope= rties. >=20 > enum FooFlag: object { > case None(0, ''); > case ThrowOnError(0b1, 'T'); > // etc. > case PrettyPrint(0b1000, 'P'); > // etc. >=20 > public function __construct(public int $flagForBinaryApi, public s= tring $flagForTextApi){} > } >=20 > FooFlag::ThrowOnError->flagForBinaryApi; // 0b1 > FooFlag:: PrettyPrint->flagForTextApi; // 'P' >=20 >=20 > Ideally we would declare that $flagForBinaryApi must be a power of 2, = and that $flagForTextApi must be a single character, using some general-= purpose feature of the type system.=20 >=20 > The only thing that might be enum-specific is a way to say whether the= values of a property must be unique (something we don't currently have = with single-value backed enums). >=20 >=20 > Then the ideal would be an EnumSet customised to work with any propert= ies or methods it wanted: >=20 > $foo =3D FooFlag::ThrowOnError | FooFlag::PrettyPrint; > $foo->serialiseForBinaryApi(); // 0b1001 > $foo->serialiseForTextApi(); // 'TP' >=20 >=20 > Rowan Tommins > [IMSoP] >=20 =E2=80=94 Rob --46fb96a0f1ec4c6ab5af9f981134e8eb Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable


On Fri, Sep 5, 2025, at 22:01, Rowan Tommins [IMSoP] w= rote:
On 4 Sept= ember 2025 15:50:08 BST, Rob Landers <rob@bottled.codes> wrote:
>I think this is a f= air observation and a fair question; but I think it is important not to = have "magic". The power-of-two rule is to make it possible to work back = how $enum->value =3D=3D=3D 15 (0x1111) even if you are completely new= to the language. If you just use some magical cardinal order, it is imp= ossible to reserve ranges, handle communications with external systems, = etc.


A set does not need element= s to have a defined order, only a defined identity; that is available on= any enum, even one with no backing at all. That pure set could be seria= lised in various ways, based on the available serialisations of its elem= ents (comma-separated list, integer bitmask, binary string bitmask, etc)= . That's the strongly typed model.

The weakly t= yped model is to keep the values permanently in their serialised form, a= nd manipulate that directly. That is, you have a set of integers for the= flags, and construct a new integer for the set of flags. That has the a= dvantage of being simple and efficient, at the cost of safety and easy t= ool affordance.

What you're suggesting sounds l= ike somewhere between the two: the individual flags are of a specific ty= pe, rather than raw integers, but the set itself is just an integer comp= osed of their "backing values". The big downside I see is that you can't= natively label a parameter or return value as being a "set of flags fro= m this enum". You could probably make a docblock type annotation work wi= th an external static analyser, but in that case, you might as well use = that tool to enforce the powers of 2 on your enum or Plain Old Constants= .

Interesting... but we don=E2=80=99= t really have a way to say that a parameter or return value must satisfy=  any constraints except that it be of a certain type. H= mmm, I guess we have "true" and "false" as values that can be returned/a= ccepted by functions =E2=80=94 but I think they=E2=80=99re the only ones= .

A form of generics could solve some of this... an enum is a set, so what we really want to say is: "map t= his set of PHP identifiers to this other set of values". Which we alread= y have via backed enums. What we lack is some set of operators to allow = us to select subsets of the set and represent them. I was thinking of bi= twise operators, because it seems the most natural. In fact, my extensio= n approach was to just inject some operators on the class entry and call= it a day.

But with your response and my implem= entation that needed to deal with string-backed enums... The bitwise ope= rators with enums would be a bit weird. Because | is a type union and sa= ying Foo::PrettyPrint | Foo::ThrowOnError also kind of a union, but real= ly, we=E2=80=99re constructing a new subset. So, maybe | isn=E2=80=99t t= he best operator. Then, when you would check, you use & (Foo::Pretty= Print & $val =3D=3D=3D 0)? It is not that elegant looking... idiomat= ic in languages like C, sure, but PHP ... not so much.

If I were to step back, I=E2=80=99d probably end up with somethi= ng like this:

// use array-ish syntax to say th= at it is a subset of Foo
$foo =3D Foo::[PrettyPrint, ThrowOnEr= ror];

// have a function accept any Foo but ret= urns only specific subsets of Foo.
function doFoo(Foo $anyFoo)= : Foo::[PrettyPrint, ThrowOnError]|Foo::[PrettyPrint]|null
// check if a value of Foo intersects with another set of Fo= o
return enum_intersects($anyFoo, Foo::[PrettyPrint]);

In this case, backing values don=E2=80=99t matter at a= ll. Serialization would still be an issue, but then again, it is already= .


Indeed, enforcing "this integer must be a power of 2" i= s not really anything to do with enums, it would be useful on *any* type= declaration.


Personally, I find= the concept of enum cases having a single backing value unnecessarily l= imiting, and would much prefer Java-style case properties.
enum FooFlag: object {
    case Non= e(0, '');
    case ThrowOnError(0b1, 'T');
    // etc.
    case Prett= yPrint(0b1000, 'P');
    // etc.

=
    public function __construct(public int $fl= agForBinaryApi, public string $flagForTextApi){}
}
<= br>
FooFlag::ThrowOnError->flagForBinaryApi; // 0b1
FooFlag:: PrettyPrint->flagForTextApi; // 'P'

=

Ideally we would declare that $flagForBinaryApi must= be a power of 2, and that $flagForTextApi must be a single character, u= sing some general-purpose feature of the type system. 
The only thing that might be enum-specific is a way to say = whether the values of a property must be unique (something we don't curr= ently have with single-value backed enums).

Then the ideal would be an EnumSet customised to work with a= ny properties or methods it wanted:

$foo =3D Fo= oFlag::ThrowOnError | FooFlag::PrettyPrint;
$foo->serialise= ForBinaryApi(); // 0b1001
$foo->serialiseForTextApi(); // '= TP'


Rowan Tommins
[IMS= oP]


=E2=80=94 Rob
--46fb96a0f1ec4c6ab5af9f981134e8eb--