How can I write this pattern synonym without ambiguous type errors?

245 Views Asked by At

Using ViewPatterns and Data.Typeable, I’ve managed to write a function that allows me to write something resembling case analysis on types. Observe:

{-# LANGUAGE GADTs, PatternSynonyms, RankNTypes, ScopedTypeVariables
           , TypeApplications, TypeOperators, ViewPatterns #-}

import Data.Typeable

viewEqT :: forall b a. (Typeable a, Typeable b) => a -> Maybe ((a :~: b), b)
viewEqT x = case eqT @a @b of
  Just Refl -> Just (Refl, x)
  Nothing -> Nothing

evilId :: Typeable a => a -> a
evilId (viewEqT @Int -> Just (Refl, n)) = n + 1
evilId (viewEqT @String -> Just (Refl, str)) = reverse str
evilId x = x

The above evilId function is very evil, indeed, since it uses Typeable to completely subvert parametricity:

ghci> evilId True
True
ghci> evilId "hello"
"olleh"

Since I love being evil, I am very pleased with this, but the above syntax is very noisy. I would love to be able to write the same code more clearly, so I decided to write a pattern synonym:

pattern EqT :: forall b a. (Typeable a, Typeable b) => (a ~ b) => b -> a
pattern EqT x <- (viewEqT @b -> Just (Refl, x))

I figured that I would be able to use this pattern synonym to make my evil case analysis much easier to read:

evilId :: Typeable a => a -> a
evilId (EqT (n :: Int)) = n + 1
evilId (EqT (str :: String)) = reverse str
evilId x = x

Sadly, this does not work at all. GHC does not seem to consult my type annotations before typechecking the pattern, so it believes b is an ambiguous variable in each pattern. Is there any way I can cleanly wrap these patterns with a pattern synonym, or will I be stuck with my longer view patterns?

1

There are 1 best solutions below

0
On

If the goal is to find some clean syntax to implement your evilId function, you can write it like this:

{-# Language ScopedTypeVariables, GADTs, TypeApplications #-}

module Demo where

import Data.Typeable

evilId :: forall a. Typeable a => a -> a
evilId x
  | Just Refl <- eqT @a @Int    = x+1
  | Just Refl <- eqT @a @String = reverse x
  | otherwise                   = x

This doesn't help with the ambiguities surrounding your pattern synonyms, unfortunately.