我是否可以说服编译器,封闭类型族中的类型同义词始终满足约束条件?通过有限的一组提升值来索引该家庭。

遵循以下原则

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实现的限制?]

最佳答案

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

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

  • forall x. f x -> a等效于(exists x. f x) -> a
  • singletons的反功能化使我们无论如何都将类型族放入实例头中。

  • 因此,我们定义了一个可用于singletons样式类型函数的存在性包装器:
    data Some :: (TyFun k * -> *) -> * where
      Some :: Sing x -> f @@ x -> Some f
    

    我们还包括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
    

    现在,我们可以使用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实例提升为“通用量化”实例。
    data At :: (TyFun k * -> *) -> k -> * where
      At :: Sing x -> f @@ x -> At f x
    
    Some fAt f x等效,但是我们可以为其编写实例。特别是,我们可以在此处编写一个明智的通用f @@ x实例。
    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
    

    我们首先解析Read,然后检查解析后的类型索引是否等于我们要解析的索引。这是我上面提到的用于解析具有特定索引的类型的模式的抽象。更加方便,因为无论有多少索引,我们在Some f上的模式匹配中只有一个大小写。注意At约束。它提供了SDecide方法,如果我们在单例数据定义中包括%~,则singletons会为我们派生它。使用它:
    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)
    

    我们还可以简化deriving EqAt之间的转换:
    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
    

    关于haskell - 约束封闭型族,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29697962/

    10-12 19:40