约束封闭式家庭

dbe*_*ham 10 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 = ()
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

但编译器必须能够推断存在SynonymClass a每个的实例V1,V2并且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 
Run Code Online (Sandbox Code Playgroud)

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

And*_*ács 4

问题是我们不能放入Synonym一个实例头,因为它是一个类型族,而且我们不能编写一个“通用量化”约束,(forall x. Show (Synonym x)) => ...因为 Haskell 中没有这样的东西。

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

  • forall x. f x -> a相当于(exists x. f x) -> a
  • singletons无论如何,去功能化让我们可以将类型族放入实例头中。

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

data Some :: (TyFun k * -> *) -> * where
  Some :: Sing x -> f @@ x -> Some f
Run Code Online (Sandbox Code Playgroud)

我们还包括以下的去功能化符号LiftedType

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
Run Code Online (Sandbox Code Playgroud)

现在,我们可以使用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 
Run Code Online (Sandbox Code Playgroud)

这并不直接为我们提供Read (Synonym t)任何特定的选择t,尽管我们仍然可以读取Some SynonymS存在标记,然后进行模式匹配以检查我们是否获得了正确的类型(如果不正确则失败)。这几乎涵盖了read.

如果这还不够好,我们可以使用另一个包装器,并将Some f实例提升为“通用量化”实例。

data At :: (TyFun k * -> *) -> k -> * where
  At :: Sing x -> f @@ x -> At f x
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我们首先解析 a Some f,然后检查解析的类型索引是否等于我们要解析的索引。它是我上面提到的用于解析具有特定索引的类型的模式的抽象。At它更方便,因为无论我们有多少个索引,我们在模式匹配中都只有一个案例。请注意SDecide约束。它提供了%~方法,并singletons在我们将其包含deriving Eq在单例数据定义中时为我们派生它。使用它:

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 (At tag x)  -> "Synonym value: " ++ show (Some tag x :: Some SynonymS)
Run Code Online (Sandbox Code Playgroud)

At我们还可以使和之间的转换变得Some更容易一些:

curry' :: (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
Run Code Online (Sandbox Code Playgroud)