On 4 September 2025 15:50:08 BST, Rob Landers <rob@bottled.codes> wrote:
>I think this is a fair 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 === 15 (0x1111) even if you are completely new to 
>the language. If you just use some magical cardinal order, it is impossible to 
>reserve ranges, handle communications with external systems, etc.


A set does not need elements 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 serialised in various ways, based on the available serialisations of 
its elements (comma-separated list, integer bitmask, binary string bitmask, 
etc). That's the strongly typed model.

The weakly typed model is to keep the values permanently in their serialised 
form, and 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 advantage 
of being simple and efficient, at the cost of safety and easy tool affordance.

What you're suggesting sounds like somewhere between the two: the individual 
flags are of a specific type, rather than raw integers, but the set 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 value 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.

Indeed, enforcing "this integer must be a power of 2" is 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 limiting, and would much prefer Java-style case properties.

enum FooFlag: object {
    case None(0, '');
    case ThrowOnError(0b1, 'T');
    // etc.
    case PrettyPrint(0b1000, 'P');
    // etc.

    public function __construct(public int $flagForBinaryApi, public string 
$flagForTextApi){}
}

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, using 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 currently have with 
single-value backed enums).


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

$foo = FooFlag::ThrowOnError | FooFlag::PrettyPrint;
$foo->serialiseForBinaryApi(); // 0b1001
$foo->serialiseForTextApi(); // 'TP'


Rowan Tommins
[IMSoP]

Reply via email to