capy-language / capy

🍊 A statically typed, compiled programming language, largely inspired by Jai, Odin, and Zig.
Apache License 2.0
64 stars 4 forks source link

Enums #39

Open NotAFlyingGoose opened 4 months ago

NotAFlyingGoose commented 4 months ago

Problem

Enums (and tagged unions) are an incredibly useful construct for dealing with varying state, and representing optional or error values.

Proposed Solution

The four requirements for enums and tagged unions are as follows:

  1. Manual discriminants (for FFI)
  2. Manual backing type (for FFI)
  3. A unique type (or lack of type) for each variant
  4. Must fit in with the rest of the language's syntax
Result :: enum {
    ok: u64,
    err: str
}

Complex :: enum(i32) { // custom backing type for the discriminant
    foo,
    // custom discriminant
    bar      | 42,
    baz: str | 43,
    qux: i32 | 100,
}

This strikes the majority of the boxes without looking too cursed, although this syntax could be reworked in the future. The literals could look like this:

Complex.foo
Complex.baz("hello")
.foo
.baz("hello")

These would definitely require a rework of how functions calls are type checked in hir_ty. Unfortunately hir::Exprs are immutable after creation, so this would also require a lot of additional checks in codegen.

Tbf I really don't like this literal syntax. It masquerades itself as being a function call when it really isn't. The only thing it has going for it is that it looks cleaner than alternatives like .baz.("hello") or .baz."hello".

Saying that, the literal syntax could be .baz."hello" at first (for it's relative ease of implementation) while a better syntax is thought out.

In languages with Algebraic Data Types, pattern matching is used for actually doing something with the value of an enum. Here's a simple syntax for pattern matching that could be implemented along with all this:

match my_complex_data {
    foo => {
        core.println("it was foo!");
    },
    bar => {
        core.println("it was bar!");
        return 1;
    },
    baz => {
        core.print("baz = ");
        core.println(baz);
        // type_of(baz) == str
    },
    qux => {
        core.print("qux = ");
        core.println(qux);
        // type_of(qux) == i32
    },
}

This works well for breaking down enums. However, if this was extended to be more like a switch statement, it would cause issues because refactoring between match blocks and if .. else chains is difficult to say the least.

Notes

The majority of the example syntaxes might not be the same down the road. It's all just something simple that could be implemented easily compared to something more thought out.

I'd love to hear ideas on a better syntax for all of these things (type declarations, enum literals, and pattern matching). Especially something that allows for quick refactoring and copy-pasting of code.