rampion suggested 模式同义词,我同意,但无可否认,弄清楚如何正确构建它们的签名并不是一件容易的事。因此我想我会写一个正确的答案来给出完整的代码。
首先,通常的样板:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE Trustworthy #-}
module FakeFin (Nat (..), Fin (FZ, FS), FinView (..), viewFin) where
import Numeric.Natural
import Unsafe.Coerce
现在是基本类型:
data Nat = Z | S Nat
-- Fin *must* be exported abstractly (or placed in an Unsafe
-- module). Users can use its constructor to implement
-- unsafeCoerce!
newtype Fin (n :: Nat) = Fin Natural
deriving instance Show (Fin n)
通过视图类型而不是直接工作要容易得多,所以让我们定义一个:
data FinView n where
VZ :: FinView ('S n)
VS :: !(Fin n) -> FinView ('S n)
deriving instance Show (FinView n)
重要的是要注意,我们可以使用显式等式约束来定义FinView,因为我们必须用这些术语来思考才能给出正确的模式签名:
data FinView n where
VZ :: n ~ 'S m => FinView n
VS :: n ~ 'S m => !(Fin m) -> FinView n
现在实际的视图函数:
viewFin :: Fin n -> FinView n
viewFin (Fin 0) = unsafeCoerce VZ
viewFin (Fin n) = unsafeCoerce (VS (Fin (n - 1)))
模式签名精确地反映了FinView 构造函数的签名。
pattern FZ :: () => n ~ 'S m => Fin n
pattern FZ <- (viewFin -> VZ) where
FZ = Fin 0
pattern FS :: () => n ~ 'S m => Fin m -> Fin n
pattern FS m <- (viewFin -> VS m) where
FS (Fin m) = Fin (1 + m)
-- Let GHC know that users need only match on `FZ` and `FS`.
-- This pragma only works for GHC 8.2 (and presumably future
-- versions).
{-# COMPLETE FZ, FS #-}
为了完整起见(因为我写这个比我预期的要花更多的精力),如果这个模块不小心导出了Fin 数据构造函数,这里有一种写unsafeCoerce 的方法。我想可能有更简单的方法。
import Data.Type.Equality
type family YahF n a b where
YahF 'Z a _ = a
YahF _ _ b = b
newtype Yah n a b = Yah (YahF n a b)
{-# NOINLINE finZBad #-}
finZBad :: 'Z :~: n -> Fin n -> a -> b
finZBad pf q =
case q of
FZ -> blah (trans pf Refl)
FS _ -> blah (trans pf Refl)
where
blah :: forall a b m. 'Z :~: 'S m -> a -> b
blah pf2 a = getB pf2 (Yah a)
{-# NOINLINE getB #-}
getB :: n :~: 'S m -> Yah n a b -> b
getB Refl (Yah b) = b
myUnsafeCoerce :: a -> b
myUnsafeCoerce = finZBad Refl (Fin 0)
finZBad 是所有动作发生的地方,但它并没有做任何不恰当的事情!如果有人真的给了我们一个 Fin 'Z 类型的非底部值,那么就已经出现了严重错误。这里明确的类型相等性证据是必要的,因为如果 GHC 看到代码需要'Z ~ 'S m,它会直接拒绝它; GHC 并不真正喜欢约束中的假设推理。 NOINLINE 注释是必要的,因为 GHC 的简化器本身使用类型信息;处理它非常了解的事物的证据是不可能的,这会使它非常困惑,并产生极其武断的结果。所以我们把它屏蔽了,成功实现了The Evil Function。