th-desugar
th-desugar copied to clipboard
Pattern guards are not a sufficient replacement for view patterns
The implementation for desugaring view patterns is dsPat (ViewP _ _) = dsPat (ViewP _ _) = fail "View patterns are not supported in th-desugar. Use pattern guards instead."
It's not possible to use pattern guards in pattern synonyms, so desugaring the following will fail:
x :: [DDec] <-
desugar
[ PatSynD
(mkName "pat")
(RecordPatSyn [])
(ExplBidir [])
( ViewP
(VarE (mkName "a"))
(TupP [])
)
]
And of course it's not possible to construct such pattern synonyms!
There's definitely a complexity/usefulness tradeoff to be struck; although I'd like to be able to use view patterns with th-desugar I would understand if this is closed with something like "not worth the complexity".
This is most likely a consequence of two historical coincidences:
- This code was first written before pattern synonyms were a thing. At the time, any use of view patterns could be straightforwardly converted to pattern guards (which then can be desugared to
case
expressions), so view patterns were excluded fromth-desugar
's AST to keep things simpler. -
th-desugar
was originally forked from thesingletons
library. There is no reasonable way to make view patterns work in the context ofsingletons
, so that was another reason to keep view patterns out of theth-desugar
AST.
Now that pattern synonyms are a thing (and that we have th-desugar
users besides singletons
), we should revisit these assumptions. I agree that view patterns are quite essential for defining many kinds of pattern synonyms, and there really isn't a viable alternative to them—at least, not in this particular spot. Some possible ways to handle this:
-
Add a
DViewP :: DExp -> DPat -> DPat
data constructor toDPat
. This would allow defining view patterns, but now all downstream consumers ofth-desugar
will be forced to reckon with view patterns in any place whereDPat
s can occur. -
Leave
DPat
alone, and instead add a newDPatSynPat
data type:data DPatSynPat = DPatSynViewPat DExp DPatSynViewPat | DPatSynPat DPat
Then change the
DPat
field inDPatSynD
to aDPatSynPat
, leaving all other uses ofDPat
alone. This would limit the scope of the change to just pattern synonym declarations. The downside is that this would only be able to express non-"prenex" uses of view patterns. For instance, this would permitpattern P1 x <- (f -> g -> x)
, but notpattern P2 x y = (f -> x, g -> y)
. -
Introduce a simpler variant of
DPatSynPat
:data DPatSynPat = DPatSynViewPat DExp DPat | DPatSynPat DPat
If we encounter nested uses of view patterns, introduce a fresh auxiliary function that collapses all the view patterns into a single one. For instance, desugar
pattern P2 x y = (f -> x, g -> y)
into something like this:pattern P2 x y <- (_aux -> (x, y)) _aux (x', y') = (f x', g y')
The downside to this approach is that we would need to pollute the top-level namespace with these
_aux
functions.
Perhaps there are other solutions I haven't thought of as well. What are your thoughts on this, @goldfirere?
Why couldn't option 3 just define _aux
inline?
Ah, great point! For some reason, I had it in my mind that the thing to the left of the ->
had to be a bare function name, but we could just as well use an arbitrary expression, e.g.,
pattern P2 x y <- ((\(x', y') -> (f x', g y')) -> (x, y))
That should work pretty nicely, I think. I like it.
It occurs to me that this approach could also resolve another restriction that th-desugar
imposes on pattern synonyms. Currently, you can't desugar a pattern synonym where the right-hand side contains an as-pattern, e.g., pattern P x y z <- x@(y, z)
. (This is a GHC feature that was added after this proposal.) If we added support for "top-level" view patterns as described above, however, then this could be desugared as:
pattern P x y z <- (\x' -> case x' of (y', z') -> (x', y', z')) (x, y, z)
It occurs to me that this approach could also resolve another restriction that
th-desugar
imposes on pattern synonyms. Currently, you can't desugar a pattern synonym where the right-hand side contains an as-pattern...
That's easy to implement using dsPatX
however that leads to some uncomfortable unpacking/repacking...
pattern P x a b <- (\arg_2_0 -> case arg_2_0 of
Foo a b -> GHC.Tuple.(,,) a b (Foo a b) -> GHC.Tuple.(,,) a b x)
ok, there's a (unneatened) PR for this.
aside: It's a shame that th-desugar doesn't use lambdacase to replace DCaseE
and DLamE
; lambdacase seems like the more "distilled" construct. I guess the ship has sailed on this though..
It's a shame that th-desugar doesn't use lambdacase to replace
DCaseE
andDLamE
; lambdacase seems like the more "distilled" construct. I guess the ship has sailed on this though..
For what it's worth, I encountered another situation in which it would be nice to use \cases
rather than DCaseE
/DLamE
, so I've opened #210 to track the using \cases
within th-desugar
. This would require a fair bit of work to do, but the end result is that we may be able to avoid some of the unsightly tuple unpacking/packing seen in https://github.com/goldfirere/th-desugar/issues/174#issuecomment-1382674047.