Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forward declarations of choice types #3652

Open
zygoloid opened this issue Jan 25, 2024 · 5 comments
Open

Forward declarations of choice types #3652

zygoloid opened this issue Jan 25, 2024 · 5 comments
Labels
leads question A question for the leads team long term Issues expected to take over 90 days to resolve.

Comments

@zygoloid
Copy link
Contributor

Currently, there is no suggestion in the design that choice types support forward declarations, and the toolchain implementation consequently does not support forward declarations of choice types. So:

  • Should forward declarations be permitted?
  • Should a type forward declaration be required to specify whether the type is defined with class or choice?

I don't think there are any novel issues with choice type declarations that don't apply to classes. But the second question seems like it might deserve a little thought.

@geoffromer
Copy link
Contributor

Presumably the syntax will require the forward declaration to use class or choice, so the question is whether the keyword used has to match the definition.

If a sum-type class could be forward-declared with choice, that could make it easier to evolve a choice type by replacing it with a class implementation of the same API. However, I'm not sure how important that will be in practice. If I understand correctly, forward declarations will only be possible within a package, so if packages are sufficiently small it may be feasible to change all the forward declarations as part of the implementation change.

If we do allow using choice to forward-declare a class definition, I think it should be limited to classes that actually implement the sum type interface, because it would be really confusing if you could use choice to forward-declare arbitrary classes.

Even with that restriction, it seems a little messy to have cases where there's an arbitrary, formally meaningless choice between choice and class. I'm almost tempted to suggest that in general we require types to be forward-declared with class, regardless of the keyword used to define them. However, that seems liable to be a future FAQ entry ("Why can't I forward-declare my choice type?"), so it's probably better to avoid that.

@zygoloid
Copy link
Contributor Author

zygoloid commented Jan 26, 2024

One interesting consequence of changing the adapter syntax from adapter X for Y { ... } to class X { adapt Y; ... } is that we get to forward-declare adapters with class X;. I wonder if the same logic should be applied to choice types: if we want choice types to notionally be a particular way to define a class, then perhaps they should use syntax beginning with class X. Then the forward declaration question becomes pretty trivial -- you can forward-declare with class X;, because that's how you forward-declare any class type.

Syntactically, our approach up to now has been that the portion of the definition from the { onwards can be replaced by a ; to form a declaration, which would suggest something like:

class Optional(T:! type) {
  choice {
    Some(t: T),
    None
  }
}

... but given that Some and None are (among other things) factory functions, perhaps something more similar to a function declaration (eg, with choice replacing fn) would be better:

class Optional(T:! type) {
  choice None;
  choice Some(t: T);
}

This gives us immediate syntactic support for putting access specifiers on the alternatives, adding other member names including member functions to choice types, and so on. (Presumably we wouldn't permit a class to have both var fields and choice alternatives, just like we don't permit it to have both var fields and an adapt declaration.)

@jonmeow
Copy link
Contributor

jonmeow commented Jan 29, 2024

Regarding the latter syntax in particular, I tend to think of the union format that choice represents as more ordered across name than fn. For example, in C++ if I write enum class Boolean : uint8_t { False, True };, that is meaningfully distinct from enum class Boolean : uint8_t { True, False }; because I can also make use of the integer values underlying the enum. Would Carbon support a similar ordering semantic for using the discriminator values?

For comparison, reordering other declarations (e.g., fn, var) I don't perceive as changing the value in an equivalent way.

Also, depending on how much you try sharing syntax, while choice represents tagged unions, you might also want to consider how you might alter syntax for unnamed untagged unions (if they're even supported, e.g. for migrating multiple C++ unnamed unions on a single class type).

@geoffromer
Copy link
Contributor

class Optional(T:! type) {
  choice None;
  choice Some(t: T);
}

If we go that route, it might be nice to take it one step further, and have an evolution path where you can customize the representation while leaving the choice syntax more or less in place, so that the reader can understand the API of a sum type without having to care whether it has a custom implementation. For example, maybe you could treat the choice declarations as static function/variable declarations, so that you could customize that definition of Optional like so:

class Optional(T:! type) {
  choice None = { .has_value = false };
  choice Some(t: T) { return {.has_value = true, .value = t }; }

  private var has_value: bool;
  private var value: T;

  impl as Match { ... }
}

That's not great, but it's a gesture toward the kind of thing I mean.

If not for that consideration, I would much prefer your other suggested syntax:

class Optional(T:! type) {
  choice {
    Some(t: T),
    None
  }
}

That keeps all the alternatives grouped together, which I think will be helpful for readability if the definition can contain other things like member functions. But maybe we can get the best of both, somehow?

Would Carbon support a similar ordering semantic for using the discriminator values?

We don't have a design for making the discriminator visible in the API (short of doing a full custom implementation) but we'll likely want that at some point, for interop/migration purposes. However, I think it should require the type to explicitly define the discriminator values, rather than have them be inferred based on the order.

Also, depending on how much you try sharing syntax, while choice represents tagged unions, you might also want to consider how you might alter syntax for unnamed untagged unions (if they're even supported, e.g. for migrating multiple C++ unnamed unions on a single class type).

I think we'll very likely want untagged unions, but trying to share syntax between them seems like it will impose a lot of cost for little benefit. The problems they need to solve don't have much overlap.

Copy link

github-actions bot commented May 2, 2024

We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the inactive label. The long term label can also be added for issues which are expected to take time.

This issue is labeled inactive because the last activity was over 90 days ago.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label May 2, 2024
@jonmeow jonmeow added long term Issues expected to take over 90 days to resolve. leads question A question for the leads team and removed inactive Issues and PRs which have been inactive for at least 90 days. labels Jul 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
leads question A question for the leads team long term Issues expected to take over 90 days to resolve.
Projects
None yet
Development

No branches or pull requests

3 participants