Arrow notation, existentials, and TcType demotion

Alexis King lexi.lambda at
Mon Oct 4 19:19:27 UTC 2021


As I mentioned in a previous email
<>, GHC
seems currently somewhat uncertain about whether or not matching on GADTs
is permitted in arrow notation. Sam Derbyshire suggested on Twitter
<> that we
probably do, ideally speaking, want to support them. Unfortunately, I am
confident that the existing implementation is *not* up to this task, as I
have now demonstrated in issues #20469
<> and #20470

The latter of those two issues is particularly challenging to solve, as it
highlights the sorts of tricky interactions that can arise when arrow
notation is mixed with existential quantification. To give an example,
suppose we have the following datatypes:

    data A where
      A :: a -> B a -> A
    data B a where
      B :: B ()

And suppose we have the following proc expression:

    proc a -> case a of
      A x B -> id -< x

The match on the A constructor introduces a locally-scoped skolem, and even
though the use of id appears on the RHS, it is *not* actually within the
match’s scope—rather, its scope is that of the overall proc expression.

This makes it tricky to maintain typechecker invariants, as introduced
metavariables must not accidentally leak into the outer scope via these
strangely-scoped expressions. For example, when typechecking the above
expression, we’ll increment tcl_tclevel before typechecking the id -< x
command, and we may introduce fresh metavariables while doing so. This
means we may end up with an expected type for id that looks like this:

    a1[tau:1] -> a2[tau:1]

However, when we actually check id against that type, we must do so in a
context where tcl_tclevel is 0, not 1. This violates WantedInv from Note
[TcLevel invariants] in GHC.Tc.Utils.TcType. This means we need to do a
three-step process to properly check id’s type:


   Synthesize a new metavariable a3[tau:0].

   Emit [W] a3[tau:0] ~ (a1[tau:1] -> a2[tau:1]) within the arrow scope,
   i.e. where tcl_tclevel = 1.

   Check id against a3[tau:0].

Discerning readers may note that this looks *awfully* similar to the
process by which we promote a type via promoteTcType, as described in Note
[Promoting a type] in GHC.Tc.Utils.TcMType. However, in this case, we
aren’t really promoting a type, but *demoting* it—we ultimately want to
decrease its level, not increase it. However, it seems to me that the
process of doing this demotion is in fact handled perfectly fine by
promoteTcType. So my question is twofold:


   Is my reasoning about what to do here correct?

   Is there any harm in using promoteTcType to do this demotion?

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <>

More information about the ghc-devs mailing list