Replace `DLamE`/`DCaseE` with lambda cases (`\cases`)
RyanGlScott opened this issue · comments
Currently, the th-desugar
AST has a DLamE
construct for binding variables in expressions, and a DCaseE
construct for scrutinizing expressions. I propose that we remove both of these in favor of a single DLamCasesE
construct, as proposed in #174 (comment):
data DExp
= ...
| DLamCasesE [DClause]
Advantages
-
Doing so would make the
th-desugar
AST more minimal, as we can desugar lambda expressions,case
expressions,\case
expressions, and\cases
expressions to a single language construct (DLamCasesE
). (See #204 (comment) for examples of how each of these desugarings would work.) We could also get rid ofDMatch
in favor ofDClause
. -
Doing so would avoid the need to desugar expressions like this:
\(Foo x) (Bar y) -> f x y
Into this:
\fooX barY -> case (# fooX, barY #) of (# Foo x, Bar y #) -> f x y
That is, it would avoid the need to "pack" arguments into an unboxed tuple just for the sake of pattern-matching on them in a single clause. Instead, we can avoid all of this tuple-packing business by simply using
\cases
' built-in pattern-matching capabilities. -
By not performing tuple-packing, we would make it much, much simpler to desugar expressions that bind embedded type patterns or invisible type patterns (#204).
Potential downsides
- This would be a pretty big breaking API change, even by
th-desugar
standards. It may not be entirely straightforward to migrate all existingth-desugar
clients over to theDLamCasesE
approach, so we may even want to consider a deprecation period for one release before removingDLamE
andDCaseE
entirely. - I haven't attempted to port
singletons-th
(by far the most sophisticatedth-desugar
client I know of) over to this new approach. We should make sure that this is viable before committing to this design.
One obstacle to making this work is that \cases
(as well as its template-haskell
counterpart, LamCasesE
) has only been around since GHC 9.4. When sweetening a DLamCasesE
expression, this means we have to think carefully about what to do on pre-9.4 versions of GHC, as we can't simply convert DLamCasesE
to LamCasesE
. In many cases, we can convert simple DLamCasesE
expressions to LamE
(when there is only a single \cases
clause) or LamCaseE
(when each \cases
clause only has a single pattern), which provides at least partial backwards compatibility. This wouldn't be quite as straightforward for \cases
expressions like this this one, however:
\cases
True (Just x) -> x
False Nothing -> 2
_ _ -> 3
Note that you wouldn't actually be able to write such an expression directly with a pre-9.4 version of GHC—you'd only be able to construct one by splicing in a DLamCasesE
. Still, it's conceivable that a user might try this, so we should have a story for how to handle it. Some options:
-
Throw an error. We can say that sweetening
\cases
expressions like the one above are simply unsupported on pre-9.4 versions of GHC, and you'd need to upgrade your GHC if you want to do this. -
We could cleverly sweeten this
\cases
expression to something like:\case True -> \case Just x -> x _ -> 3 False -> \case Nothing -> 2 _ -> 3
Note how we have inlined the
3
case in certain spots. While we could do this, this would require a significant amount of additional complexity in the sweetening pass. This feels a bit wrong, asth-desugar
's usual approach is to put all of the complicated logic in desugaring, which then makes sweetening back to thetemplate-haskell
AST nearly trivial. -
We could take a page from how
\cases
expressions are currently desugared and sweeten the expression above to something like this:\arg1 arg2 -> case (arg1, arg2) of (True, Just x) -> x (False, Nothing) -> 2 (_, _) -> 3
This is still a bit involved, but not nearly as complicated as option (2).
This option comes with a more severe drawback, however. In order to ensure that
arg1
andarg2
don't shadow anything currently in scope (and run the risk of emitting-Wname-shadowing
warnings), we need to usenewName
to ensure thatarg1
andarg2
are fresh names.newName
is a monadic operation, however, and sweetening is a completely pure operation. This means that if we wanted to callnewName
during sweetening, we would need to change the type signatures of all sweetening-related operations to be monadic. This is doable, but this it at odds withth-desugar
's current approach of making sweetening as simple as possible.
For now, I am inclined to pick option (1). We can revisit if someone specifically asks for the ability to sweeten DLamCasesE
expressions with pre-9.4 versions of GHC.
Reading the options, I was more excited about (3). But actually I agree that (1) is most expedient.
See #218 for the changes on the th-desugar
side, as well as goldfirere/singletons#595 for the changes on the singletons-th
side. Happily, migrating singletons-th
over to DLamCasesE
proved extremely straightforward, and it even simplified some tricky parts of how singling works.