约束封闭式家庭

时间:2015-04-17 11:29:27

标签: haskell type-families

我可以说服编译器封闭类型系列中的类型同义词总是满足约束吗?该系列由一组有限的推广值编制索引。

的内容
data NoShow = NoShow
data LiftedType = V1 | V2 | V3

type family (Show (Synonym (a :: LiftedType)) => Synonym (a :: LiftedType)) where
  Synonym V1 = Int
  Synonym V2 = NoShow -- no Show instance => compilation error
  Synonym V3 = ()

我可以对开放类型系列强制执行约束:

class (Show (Synonym a)) => SynonymClass (a :: LiftedType) where
  type Synonym a
  type Synonym a = ()

instance SynonymClass Int where
  type Synonym V1 = Int

-- the compiler complains here
instance SynonymClass V2 where
  type Synonym V2 = NoShow

instance SynonymClass V3

但是编译器必须能够推断出SynonymClass aV1V2中的每一个都存在V3的实例?但无论如何,我宁愿不使用开放式家庭。

我要求这样做的动机是我想让编译器相信我的代码中所有闭包类型的实例都有Show / Read实例。一个简单的例子是:

parseLTandSynonym :: LiftedType -> String -> String
parseLTandSynonym lt x =
  case (toSing lt) of
    SomeSing (slt :: SLiftedType lt') -> parseSynonym slt x

parseSynonym :: forall lt. SLiftedType lt -> String -> String
parseSynonym slt flv =
      case (readEither flv :: Either String (Synonym lt)) of
        Left err -> "Can't parse synonym: " ++ err
        Right x  -> "Synonym value: " ++ show x 

[有人在评论中提到这是不可能的 - 这是因为它在技术上是不可能的(如果是这样,为什么)或只是限制GHC的实施?]

2 个答案:

答案 0 :(得分:4)

问题在于我们无法将Synonym放在实例头中,因为它是一个类型系列,而且我们无法编写一个"普遍量化的"像(forall x. Show (Synonym x)) => ...这样的约束,因为在Haskell中没有这样的东西。

但是,我们可以使用两件事:

  • forall x. f x -> a相当于(exists x. f x) -> a
  • singletons的defunctionalization让我们无论如何都要将类型系列放入实例头中。

因此,我们定义了一个适用于singletons - 样式类型函数的存在包装器:

data Some :: (TyFun k * -> *) -> * where
  Some :: Sing x -> f @@ x -> Some f

我们还包括LiftedType

的defunctionalization符号
import Data.Singletons.TH
import Text.Read
import Control.Applicative

$(singletons [d| data LiftedType = V1 | V2 | V3 deriving (Eq, Show) |])

type family Synonym t where
  Synonym V1 = Int
  Synonym V2 = ()
  Synonym V3 = Char

data SynonymS :: TyFun LiftedType * -> * -- the symbol for Synonym
type instance Apply SynonymS t = Synonym t

现在,我们可以使用Some SynonymS -> a代替forall x. Synonym x -> a,也可以在实例中使用此表单。

instance Show (Some SynonymS) where
  show (Some SV1 x) = show x
  show (Some SV2 x) = show x
  show (Some SV3 x) = show x

instance Read (Some SynonymS) where
  readPrec = undefined -- I don't bother with this now...

parseLTandSynonym :: LiftedType -> String -> String
parseLTandSynonym lt x =
  case (toSing lt) of
    SomeSing (slt :: SLiftedType lt') -> parseSynonym slt x

parseSynonym :: forall lt. SLiftedType lt -> String -> String
parseSynonym slt flv =
      case (readEither flv :: Either String (Some SynonymS)) of
        Left err -> "Can't parse synonym: " ++ err
        Right x  -> "Synonym value: " ++ show x 

虽然我们仍然可以阅读Read (Synonym t)然后在存在标记上进行模式匹配以检查我们是否存在t,但我们无法直接向我们提供Some SynonymS read得到了正确的类型(如果它不对,则失败)。这几乎涵盖了Some f的所有用例。

如果这还不够好,我们可以使用另一个包装器,并将data At :: (TyFun k * -> *) -> k -> * where At :: Sing x -> f @@ x -> At f x 实例提升到"普遍量化"实例。

At f x

f @@ x相当于Read,但我们可以为它编写实例。特别是,我们可以在这里编写一个合理的通用instance (Read (Some f), SDecide (KindOf x), SingKind (KindOf x), SingI x) => Read (At f x) where readPrec = do Some tag x <- readPrec :: ReadPrec (Some f) case tag %~ (sing :: Sing x) of Proved Refl -> pure (At tag x) Disproved _ -> empty 实例。

Some f

我们首先解析At,然后检查解析的类型索引是否等于我们要解析的索引。它是我上面提到的用于解析具有特定索引的类型的模式的抽象。它更方便,因为我们在SDecide的模式匹配中只有一个案例,无论我们有多少个索引。请注意%~约束。它提供了singletons方法,如果我们在单例数据定义中包含deriving EqparseSynonym :: forall lt. SLiftedType lt -> String -> String parseSynonym slt flv = withSingI slt $ case (readEither flv :: Either String (At SynonymS lt)) of Left err -> "Can't parse synonym: " ++ err Right (At tag x) -> "Synonym value: " ++ show (Some tag x :: Some SynonymS) 会为我们推导出来。将其用于:

At

我们还可以更轻松地在Somecurry' :: (forall x. At f x -> a) -> Some f -> a curry' f (Some tag x) = f (At tag x) uncurry' :: (Some f -> a) -> At f x -> a uncurry' f (At tag x) = f (Some tag x) parseSynonym :: forall lt. SLiftedType lt -> String -> String parseSynonym slt flv = withSingI slt $ case (readEither flv :: Either String (At SynonymS lt)) of Left err -> "Can't parse synonym: " ++ err Right atx -> "Synonym value: " ++ uncurry' show atx 之间进行转换:

{{1}}

答案 1 :(得分:0)

如果我理解你想做什么,这是不可能的。如果是,您可以轻松地构建一个类型为Proxy t -> Bool的非常量函数,沿

data YesNo = Yes | No
class Foo (yn :: YesNo) where foo :: Proxy yn -> Bool
type family (Foo (T t) => T t) where
    T X = Yes
    T y = No

f :: forall t. Proxy t -> Bool
f _ = foo (Proxy (T t))

但是你不能构建这样一个函数,即使所涉及的所有类型都是关闭的(这当然是GHC的一个特征或限制,取决于你的观点)。