Currently, creating an object variant type like this:
type
FooKind = enum a, b, c
Foo = object
case kind: FooKind
of a:
bar, a: int
of b:
bar, b: int
of c:
c: int
results in an error:
Error: attempt to redefine: 'bar'
because the different branches of the variant cannot share fields. There's an open issue about that but since it's over 3 years old and still unresolved, I'm not holding my breath.
I see a few possible alternatives:
type
FooKind = enum a, b, c
Foo = object
bar: int
case kind: FooKind
of a:
a: int
of b:
b: int
of c:
c: int
type
FooKind = enum a, b, c
Foo[K: static[FooKind]] = object
when K == a:
bar, a: int
elif K == b:
bar, b: int
elif K == c:
c: int
type
FooKind = enum a, b, c
FooBase = object
kind: FooKind
FooA = object of FooBase
bar, a: int
FooB = object of FooBase
bar, b: int
FooC = object of FooBase
c: int
Are there other alternatives to getting around this issue?
Making the "shared" fields common to all variants, which would pollute some variants with unnecessary fields
Is the way to go. And also "Using regular object inheritance" wouldn't accomplish the same at all.
Consider:
type
Actions = enum
Accuracy, Maturity, Id, Phase, Skip, Status
Action = object
case kind: Actions
of Accuracy: acc: range[1..9]
of Maturity: maturity: range[1..9]
of Id: id: int
of Phase:
phasenote: string
phase: range[1..5]
of Skip:
skipnote: string
skip: int
of Status:
statusnote: string
status: int
And say I don't like those foo-note fields. They're just notes; it's not important that they're notes for a status vs. some other kind of action.
Alternative #1 is to add a note: string field to the Action object, already stated.
Alternative #2:
type
Actions = enum
Accuracy, Maturity, Id, Phase, Skip, Status
Action = object
case kind: Actions
of Accuracy: acc: range[1..9]
of Maturity: maturity: range[1..9]
of Phase: phase: range[1..5]
of Id, Skip, Status:
note: string
id, skip, status: int
This moves the bloat to only three variants, instead of to all of them. Whether this is desirable or not vs. alternative #1 depends on your use.
I would like to discuss this one as well. I have hit a couple of cases including in Nim compiler itself when this features would have bern rather helpful. So it is potentially good addition to Nim as RFC.
Obviously, the following should not compile, because bar is out of position:
type
FooKind = enum a, b, c
Foo = object
case kind: FooKind
of a:
bar, a: int
of b:
b, bar: int
of c:
c: int
While the following can:
type
FooKind = enum a, b, c
Foo = object
case kind: FooKind
of a:
bar, a: int
of b:
bar, b: int
of c:
c: int
Codegen should not be a problem. IsFieldAccessible check would become more involved though.
@Araq, what do you think? Is it worth an RFC?
Everything I said is still my opinion and I would write your example like:
type
FooKind = enum a, b, c
Foo = object
case kind: FooKind
of a, b: bar: int
of a:
a: int
of b:
b: int
of c:
c: int
Just hit this problem, solution proposed to @araq looks wrong. The point of type safety is to validate code, and having a field on the object that shouldn't be there is not contributing to correct code.
Some events have el, some don't. It makes no sense to force all events to have el attribute.
type EventType* = enum location, click, change
type Event* = object
case kind*: EventType
of location:
location*: Url
of click:
el*: seq[int]
click*: ClickEvent
of change:
el*: seq[int]
change*: ChangeEvent
It's better to fix language problems,, not mark it as "features" and forget.
PRs are welcome, talking is cheap. And no matter how you slice it, object variants in their current imperfect form are already quite useful. And you will never catch all your invariants within a traditional type system either, so every solution has these imperfections. It's just that you are not aware because the "invariant" keyword has not yet been added to TypeScript.