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:
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
Consider this example from the
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.
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.)
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!