使用ViewPatterns
和Data.Typeable
,我设法编写了一个函数,该函数允许我编写类似于类型案例分析的内容。观察:
{-# 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
上面的evilId
函数确实非常邪恶,因为它使用Typeable
完全颠覆参数化:
ghci> evilId True
True
ghci> evilId "hello"
"olleh"
由于我喜欢作恶,所以我对此感到非常满意,但上面的语法非常嘈杂。我希望能够更清楚地编写相同的代码,所以我决定编写一个模式同义词:
pattern EqT :: forall b a. (Typeable a, Typeable b) => (a ~ b) => b -> a
pattern EqT x <- (viewEqT @b -> Just (Refl, x))
我想我将能够使用这个模式同义词来使我的邪恶案例分析更容易阅读:
evilId :: Typeable a => a -> a
evilId (EqT (n :: Int)) = n + 1
evilId (EqT (str :: String)) = reverse str
evilId x = x
可悲的是,这根本不起作用。GHC 在类型检查模式之前似乎没有参考我的类型注释,因此它认为b
是每个模式中的一个模棱两可的变量。有什么方法可以用模式同义词干净地包装这些模式,还是我会坚持我的长视图模式?
如果目标是找到一些干净的语法来实现你的evilId
函数,你可以这样写:
{-# 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
不幸的是,这无助于解决围绕模式同义词的歧义。