6
$\begingroup$

In most languages, enum types intersect with number types, implicitly converting between them so you can do SomeEnum.VariantA | SomeEnum.VariantB to work with flags. In C# enums are numbers with custom methods, getters and setters and built-ins such as HasFlag(). In GDScript enums are numbers without custom methods etc. In Rust, IIRC there's a separate bitflags crate with type-safe flags.

How do other languages facilitate working with bitwise flags?

For example:

  • Is there a language that infers the type from where a variant comes (based on Swift (let x: Enum = .variant) or Haxe), a feature from which flags can be wrote faster? Of course as I know neither Swift nor Haxe support flags enums: Haxe uses enums for algebraic data types.
  • Is there a language that is type-safe, i.e., that does not allow to assign a number to a flags typed property without explicit conversion?
$\endgroup$
1
  • 2
    $\begingroup$ Apologies I have metad your question as I think it raises some interesting questions. $\endgroup$ Commented Jun 25, 2023 at 13:01

4 Answers 4

5
$\begingroup$

C++

Famously, C++ supports bit flags in a library, std::bitset. This is often used in conjunction with an enumerated type:

enum {
    FLAG_1,
    FLAG_2,
    FLAG_3,
    FLAG_COUNT // Not used as an actual flag
};

std::bitset<FLAG_COUNT> flags; // This is how you represent flags.

// This is how you use them.
flags[FLAG_3] = true;
if (flags[FLAG_2]) { /* ... */ }
$\endgroup$
3
$\begingroup$

Of course as I know neither Swift nor Haxe support flags enums: Haxe uses enums for algebraic data types.

Swift has OptionSet.

OptionSet, like the standard hash-based Set<T>, inherits from the SetAlgebra protocol. However, its element type is itself, and you get a lot of the conformances for free.

Consider this short program, which demonstrates most of the common operations:

// Let's define such a type.
struct MyOptions: OptionSet {
  var rawValue: Int

  // ...yep, that's it.

  // Presumably you also want something like these:
  static let foo = MyOptions(rawValue: 1 << 0)
  static let bar = MyOptions(rawValue: 1 << 1)
  static let baz = MyOptions(rawValue: 1 << 2)
}

// Create two values: one that's just foo, and one that's both foo and bar
let first: MyOptions = .foo
let second: MyOptions = [.foo, .bar]

// Check what flags they have in common
print(first.intersection(second)) // first & second in C
// Remove first from second
print(second.subtracting(first)) // second & ~first in C
// Test for membership
if second.contains(first) { // second & first == first in C
  print("\(first) is in \(second)!")
}
// Merge it with .baz
print(first.union(.baz)) // first | baz in C

For further reading, see the documentation at https://developer.apple.com/documentation/swift/optionset-swift.protocol.

$\endgroup$
1
  • $\begingroup$ Looking here it seems Swift is powerful. $\endgroup$
    – Hydroper
    Commented Jun 26, 2023 at 22:39
2
$\begingroup$

In Tuplet, enums are sum types, whereas the "combinable" flag-like options use flags. Here is an example:

FlagRegister /* 6502 "P" flags register */ = [flags(u8)] {
  carry, // least significant bit
  zero, // next bit
  disable_interrupts,
  decimal,
  b_flag,
  // bit 5 is always 1
  [private] bit5 always true, // a `FlagRegister` value must have bit5 == 1, or it fails at construction
  // alternatively, to just ignore it
  [private] bit5 = true,
  overflow,
  negative,
} // The compiler checks that there are 8 bits

a: FlagRegister = 2b0010_0100
b = FlagRegister(carry, decimal) // same as 2b0010_1001
c = a | b // carry, decimal, disable_interrupts(, bit5)
// + and | do the same thing
d = a + FlagRegister.b_flag // disable_interrupts, b_flag(, bit5)
e = a - FlagRegister.disable_interrupts // (bit5)
f = b :~ FlagRegister.disable_interrupts // toggle: carry, decimal, disable_interrupts(, bit5)
g = a :~ FlagRegister.disable_interrupts // (bit5)
h = b & FlagRegister(carry) // carry(, bit5)
i = a & FlagRegister.carry // (bit5)

core::assert(c.decimal) // compile time assertion
core::assert(!c.negative)
core::assert(std::reflect::compile { _: FlagRegister = 0 }.is_empty) // fails because bit5 is false

[runtime] y: u8 = 0
z = FlagRegister(d) // fails at runtime if the compiler can't statically know that bit5 is true

assuming Q is a type implementing Integer, T is a type implementing Bitwise(Q), F = [flags(T)] { a, b, ... }, q of type Q, and x, y of type F these are all the supported operations:

  • x.value and its alias @x gets the integer representation
  • x | y and its aliases x + y and x.or(y) is F(@x | @y)
  • x & y and its alias x.and(y) is F(@x & @y)
  • x - y and its alias x.without(y) is F(@x & ~@y)
  • x :~ y and its alias x.toggle(y) is F(@x ^ @y)
  • x.a, x.b, etc
  • x(q) and its alias x.at(q) gets the q-th bit (0 is the lowest bit)
  • ~x and its alias x.negate is F(~@x)

F will of course also inherit the operations from its typeclasses, most notably Show F, Read F, Formattable F, Integer F, Sequence(Bool) F, Bitwise(Q) F, FiniteBitwise(Q) T => FiniteBitwise(Q) F.

$\endgroup$
1
$\begingroup$

VioletScript

My language has a type-safe (i.e. not mixed with number types) numeric enum. You can work with flags by adding the reserved Flags decorator. It is not necessary to specify the value for each variant; you can omit the = ... part. It supports custom methods, getters and setters and enum variant inference.

Every variant has an unique number and an unique user-friendly string.

I'd say working with flags is safer and more efficient in VioletScript than working with flags in C#. I don't know of another language with the same characteristics (there is one someone mentioned me that has flags enums, but I forgot the name, though I still bet there are differences).

Enum Variant Inference

The language implicitly converts string literals, array initializers and object initializers to enums wherever expected. Even the undefined constant too (equivalent to {} or []).

You can think of this as similiar to variant inference in Swift or Haxe. I don't know Swift, but in Haxe enums (that keyword) are algebraic data types; in VioletScript they're rather the equivalent of C#, except they're type-safe and also associate an user-friendly string:

VioletScript example:

const modifiers: Modifiers = 'someModifier'
const modifiers: Modifiers = ['someModifier', 'anotherModifier']
const modifiers: Modifiers = { someModifier: true }

Variant Structure

This is about how the enum defines the variants.

  • Variant has an enum static property.
    • Variant has a number (valueOf()).
    • Variant has an user-friendly string (toString()).

What you assign at the right side of the variant is just a compile-time association:

  • If it's a string literal, associates an user-friendly string and let the compiler infer the number.
  • If it's a numeric literal, associates a number and let the compiler infer the string.
  • If it's a [str, num] or [num, str] array, associates the specified number and string.

If no initialiser, let the compiler infer the number and string.

Number and String Inference

  • The SCREAMING_SNAKE_CASE constant name is implicitly converted into an user-friendly camel-case string (screamingSnakeCase).
  • Like in all other languages, the number is inferred based on a counter. However, different from C#, it will assign correct flags: for flags the counter will multiply per 2 each increment.

Strictness

  • Duplicates are not allowed.
  • A number in a flags type must be either 1 or power of two.

Example

[Flags]
enum E {
    X
    Y = 32
    Z = 'zZ'
    W = ['k', 512]

    // custom method
    function f(): void {
        trace(this.valueOf())
    }

    // custom getter
    function get prop(): Number (this.valueOf() + 5)
}

const e: E = ['x', 'k']
assert('x' in e) // has flag 'x'

const e: E = { x: true }

// is not empty
assert(e != {})

const e = e.toggle('x')

To clarify:

  • E.X
    • E.X.valueOf() == 1
    • E.X.toString() == 'x'
  • E.Y
    • E.Y.valueOf() == 32
    • E.Y.toString() == 'y'
  • E.Z
    • E.Z.valueOf() == 64
    • E.Z.toString() == 'zZ'
  • E.W
    • E.W.valueOf() == 512
    • E.W.toString() == 'k'

Basic Syntax

'flag' in f
f = f.toggle('flag')
f = f.filter(other)
f = f.include(other)
f = f.exclude(other)

// specify custom number type
[Flags]
enum E wraps BigInt {
}
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .