I recently found out that type holes combined with pattern matching on proofs provides a pretty nice Agda-like experience in Haskell. For example:

{-# LANGUAGE DataKinds, PolyKinds, TypeFamilies, UndecidableInstances, GADTs, TypeOperators #-} data (==) :: k -> k -> * where Refl :: x == x sym :: a == b -> b == a sym Refl = Refl data Nat = Zero | Succ Nat data SNat :: Nat -> * where SZero :: SNat Zero SSucc :: SNat n -> SNat (Succ n) type family a + b where Zero + b = b Succ a + b = Succ (a + b) addAssoc :: SNat a -> SNat b -> SNat c -> (a + (b + c)) == ((a + b) + c) addAssoc SZero b c = Refl addAssoc (SSucc a) b c = case addAssoc a b c of Refl -> Refl addComm :: SNat a -> SNat b -> (a + b) == (b + a) addComm SZero SZero = Refl addComm (SSucc a) SZero = case addComm a SZero of Refl -> Refl addComm SZero (SSucc b) = case addComm SZero b of Refl -> Refl addComm sa@(SSucc a) sb@(SSucc b) = case addComm a sb of Refl -> case addComm b sa of Refl -> case addComm a b of Refl -> Refl

The really nice thing is that I can replace the right-hand sides of the Refl -> exp constructions with a type hole, and my hole target types are updated with the proof, pretty much as with the rewrite form in Agda.

However, sometimes the hole just fails to update:

(+.) :: SNat a -> SNat b -> SNat (a + b) SZero +. b = b SSucc a +. b = SSucc (a +. b) infixl 5 +. type family a * b where Zero * b = Zero Succ a * b = b + (a * b) (*.) :: SNat a -> SNat b -> SNat (a * b) SZero *. b = SZero SSucc a *. b = b +. (a *. b) infixl 6 *. mulDistL :: SNat a -> SNat b -> SNat c -> (a * (b + c)) == ((a * b) + (a * c)) mulDistL SZero b c = Refl mulDistL (SSucc a) b c = case sym $ addAssoc b (a *. b) (c +. a *. c) of -- At this point the target type is -- ((b + c) + (n * (b + c))) == (b + ((n * b) + (c + (n * c)))) -- The next step would be to update the RHS of the equivalence: Refl -> case addAssoc (a *. b) c (a *. c) of Refl -> _ -- but the type of this hole remains unchanged...

Also, even though the target types do not necessarily line up inside the proof, if I paste in the whole thing from Agda it still checks fine:

mulDistL' :: SNat a -> SNat b -> SNat c -> (a * (b + c)) == ((a * b) + (a * c)) mulDistL' SZero b c = Refl mulDistL' (SSucc a) b c = case (sym $ addAssoc b (a *. b) (c +. a *. c), addAssoc (a *. b) c (a *. c), addComm (a *. b) c, sym $ addAssoc c (a *. b) (a *. c), addAssoc b c (a *. b +. a *. c), mulDistL' a b c ) of (Refl, Refl, Refl, Refl, Refl, Refl) -> Refl

Do you have any ideas why this happens (or how I could do proof rewriting in a robust way)?