使用幻像类型时避免冗余约束

Avoiding redundant constraints when working with phantom types

这是我正在尝试编写的一个简化的、可能很愚蠢的示例(它更复杂并且涉及列表长度的编译时编码)。

鉴于以下情况:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}

data D (a :: Bool) where
  D :: Bool -> D a

我想要以下功能g:

g :: D a -> Bool
g (D x) = x == a

当然这不会编译,因为 a 是一个类型,而不是一个值。

这是一个可能的解决方案:

class C (a :: Bool) where
  f :: D a -> Bool

instance C True where
  f (D x) = x == True

instance C False where
  f (D x) = x == False

g :: (C a) => D a -> Bool
g = f

但是我必须向 g 添加一个约束,这似乎是多余的 a :: Bool,而且我已经为 Bool 的所有情况提供了实例。

有没有我可以写 g 的签名:

g :: D a -> Bool 

即不需要额外的约束?

不,这是不可能的,因为我可以给你一个完美的 D Any 类型的值,其中定义了 Any

type family Any :: k where {}

你能做的就是写一个更普遍有用的class:

data SBool a where
  SFalse :: SBool 'False
  STrue :: SBool 'True

sBoolToBool :: SBool a -> Bool
sBoolToBool SFalse = False
sBoolToBool STrue = True

class KnownBool a where
  knownBool :: SBool a
instance KnownBool 'False where
  knownBool = SFalse
instance KnownBool 'True where
  knownBool = STrue

当然,如果您不将这些类型用于其他任何用途,那么所有这些机制就真的有点过分了。