amaranth-lang / amaranth

A modern hardware definition language and toolchain based on Python
https://amaranth-lang.org/docs/amaranth/
BSD 2-Clause "Simplified" License
1.58k stars 174 forks source link

[subRFC] Discriminated Unions / Enums #699

Closed lachlansneff closed 1 year ago

lachlansneff commented 2 years ago

Discriminated Unions / Enums

This (sub)RFC builds on #697. As proposed in #697, first-class discriminated unions could be very useful.

Motivation

While discriminated/tagged unions can be built on top of data.Struct and data.Union, first-class support may improve readability and correctness of code.

Overview and Examples

from amaranth import *
from amaranth.lib import data

# Declare a discriminated union.
class Op(data.Enum):
    Add: unsigned(16)
    Mul: unsigned(16)

# Creating an `Op` signal can only be done through one of its variants.
op_a = Op.Add()
# Or
op_b = Op.Add(42)

# Enums can be accessed with `m.IfLet`.
with m.IfLet(op_a, Op.Add) as imm:
    m.d.sync += op_a.eq(Op.Mul(imm + 1))

# Enums can be equated as well.
with m.If(op_a == Op.Mul(43)):
    ...

# Or, it can be accessed with `m.Match`.
with m.Match(op_a):
    with m.Case(Op.Mul) as imm:
        m.d.sync += imm.eq(3)

# If the data associated with multiple variants is the same, you can match multiple.
with m.Match(op_a):
    with m.Case(Op.Mul | Op.Add) as imm:
        ...

# An enum with no annotations has no associated data and and the tag values can be specified
class Animal(data.Enum):
    Elephant = 0
    Cat = 1

Detailed Design

This subRFC proposes several language and library additions:

Alternatives

Open Questions

whitequark commented 1 year ago

Thank you for submitting this RFC, @lachlansneff! We have extensively discussed it during the 2023-02-20 weekly meeting; see the transcript.

The resolution was close, as there are several major concerns with this RFC:

  1. The "Detailed design" section unfortunately does not include the detailed design.
  2. It is not clear whether Op.Add(42) returns a view of a Signal with 42 included in the reset value, or a Const. If it is the latter, we do not currently have precedent for such polymorphism. (by @jfng)
  3. The lack of ability to perform nested matching with the proposed syntax greatly limits usefulness compared to e.g. Rust's match construct. The equivalent of e.g. match x { Foo { a: A(y), b: B(z) } => ... } in Rust would require three nested matches to bind all the variables, and six levels of indentation. Similarly, the RFC does not explain how constructing variants where the payload is a structure would work; if the answer to point (2) is that it returns a Const, then how is the type of the return value determined when the payload is a structure?
  4. It is not clear why the proposed mechanism should be restricted to discriminated unions with a layout specified by Amaranth. A general purpose matching mechanism could be useful, and may also handle cases where the dependencies between fields are determined by something other than a dedicated discriminant; for example, a data field could be valid only if length != 0. (by @rroohhh)

A follow-up RFC that proposes a general purpose pattern matching construct and addresses all of the concerns raised during the meeting could be useful. However, it is not clear that such a construct can be implemented within the constraints of Python.