Hello all, I’ve been thinking about extensible variants recently (having recently dived into OCaml), and I wondered if there’d be any interest in adding language-level support for them. Currently Nate Faubion’s excellent purescript-variant
library is available, but there are a few shortcomings that could only be solved in the compiler:
1. No normal pattern-matching
Optimally, one would like to be able to match on variants using the normal case
statement. This would allow us to do complicated pattern matching that combines extensible variants with normal ADT’s, such as
type Foo = Maybe (Variant (Foo :: Int, Bar :: Int))
processFoo = \case
Just (`Foo x) -> blah
Just (`Bar x) -> blah
Nothing -> blah
where `Foo and `Bar are variant type constructors (see below). And it would be nice to be able to simultaneously match on multiple values (i.e., case x, y, z of ...
) which are variants. Currently, I don’t think there’s a good way to do these things using purescript-variant
.
2. Better type inference
Consider this example from the purescript-variant
documentation:
allToString :: Variant (foo :: Int, bar :: Boolean, baz :: String) -> String
allToString =
case_
# on _foo show
# on _bar (if _ then "true" else "false")
# on _baz (\str -> str)
If you comment out the type signature, the same type is inferred. However, this is not quite optimal from a design perspective, since it wouldn’t allow us to plug in a value of type Variant (foo :: Int, bar :: Boolean)
; it would need to be coerced to a Variant (foo :: Int, bar :: Boolean, baz :: String)
value first. I’d argue that the optimal inferred type signature of this function should be:
allToString :: forall u u'. Union u u' (foo :: Int, bar :: Boolean, baz :: String)
=> Variant u
-> String
which, again, I’m not sure is possible in a library.
3. More potential opportunities for optimization
If you know statically that `A Int, `B Int and `C Int are the only variant types that occur in your program, you can secretly compile them under the hood to an ADT:
data SecretCompilerSumType = A Int | B Int | C Int
and then perform all the same optimizations you could for an ADT. This could speed up other libraries like purescript-run
, which IMO is the most important (but not only) use-case for extensible variants, and which uses purescript-variant
under the hood. But purescript-variant
's mock-pattern matches use function composition rather than real pattern matching, and I don’t think optimizations like this would be available.
(I do realize that a lot of PureScript is under-optimized currently anyway, so maybe this perk is so far-off anyway that it’s not a big consideration.)
4. Syntactic convenience
Currently, to create a variant in purescript-variants
, you have to write something like someFoo = inj (Proxy :: Proxy "foo") 42
. This is mildly annoying and discourages their use. It would be nice to be able to just write someFoo = `foo 42
or something like that, which could then be mirrored in first-class pattern matches as above.
There are other pieces of syntactic sugar you could theoretically add on top of this – for example, just as record types have their own special curly-brace syntax, we could give variants (the dual of records) their own special square-brace syntax like OCaml has. But this would be beyond the scope of my informal proposal here.
I realize that variants probably aren’t the biggest priority, and that the capabilities of purescript-variant
might suffice for most people’s use-cases, especially given limited compiler maintainer manpower. I’ve just been really impressed by their applicability in OCaml and thought it might be worth asking for you guys’ thoughts!