我是否可以说服编译器,封闭类型族中的类型同义词始终满足约束条件?通过有限的一组提升值来索引该家庭。
遵循以下原则
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 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
[有人在评论中提到这是不可能的–是因为这在技术上是不可能的(如果这样,为什么),或者仅仅是对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 f
与At 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 Eq
和At
之间的转换: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/