dbe*_*ham 5 haskell existential-type typeclass data-kinds
首先简要概述我的一般问题可能更容易,然后显示我遇到的问题.
我想收到一些单独索引类型的JSON列表,其中索引类型也有一个关联的类型系列.在代码中:
data MyType = MyValue1 | MyValue2
type family MyFamily (mt :: MyType) where
MyFamily MyValue1 = Int
MyFamily MyValue2 = Double
data InputType (mt :: MyType) = InputNoFamily | InputWithFamily (MyFamily mt)
data OutputType (mt :: MyType) = OutputNoFamily | OutputWithFamily (MyFamily mt)
Run Code Online (Sandbox Code Playgroud)
通过存在量化,我应该能够隐藏变化的索引并且仍然能够获得值(具有一些类似延续的更高级别的类型函数 - 可能有更好的名称).我最终得到了我的程序
JSON -> [Some InputType] -> [Some OutputType] -> JSON
Run Code Online (Sandbox Code Playgroud)
其中Some是从exinst包,但低于还重新定义.在我不解析的情况下,我可以解析JSON MyFamily mt,但是我也无法找到从JSON中解析它的最佳方法.
我到目前为止的内容如下:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE RankNTypes #-}
module SO where
import Data.Aeson
import Data.Singletons.TH
import GHC.Generics
$(singletons [d|
data MyType
= MyValue1
| MyValue2
| MyValue3
deriving (Show, Eq, Generic)
|])
instance FromJSON MyType
type family MyFamily (mt :: MyType) :: * where
MyFamily 'MyValue1 = Double
MyFamily 'MyValue2 = Double
MyFamily 'MyValue3 = Int
-- stolen from exinst package
data Some (f :: k -> *) =
forall a. Some (Sing a) (f a)
some :: forall (f :: k -> *) a. SingI a => f a -> Some f
some = Some (sing :: Sing a)
withSome :: forall (f :: k -> *) (r :: *). Some f -> (forall a. SingI a => f a -> r) -> r
withSome (Some s x) g = withSingI s (g x)
data MyCompoundType (mt :: MyType)
= CompoundNoIndex
| CompoundWithIndex (MyFamily mt)
deriving instance (Show (SMyType mt), Show (MyFamily mt)) => Show (MyCompoundType mt)
-- instance with no parsing of `MyFamily`
instance
forall (mt :: MyType).
( SingKind (KindOf mt)
, FromJSON (DemoteRep (KindOf mt))
) => FromJSON (Some MyCompoundType) where
parseJSON = withObject "MyCompoundType" $ \o -> do
mt :: MyType <- o .: "myType"
case toSing mt of
SomeSing (smt :: SMyType mt') -> case smt of
SMyValue1 -> return $ some (CompoundNoIndex :: MyCompoundType mt')
SMyValue2 -> return $ some (CompoundNoIndex :: MyCompoundType mt')
SMyValue3 -> return $ some (CompoundNoIndex :: MyCompoundType mt')
Run Code Online (Sandbox Code Playgroud)
我显然需要添加一个FromJSON (MarketIndex mt)约束,但我还需要能够将它绑定到Some CompoundType我为其生成实例的那个.
简单添加一个FromJSON (MyFamily mt)constaint
instance
forall (mt :: MyType).
( SingKind (KindOf mt)
, FromJSON (DemoteRep (KindOf mt))
, FromJSON (MyFamily mt)
) => FromJSON (Some MyCompoundType) where
parseJSON = undefined
Run Code Online (Sandbox Code Playgroud)
给出了模糊类型的错误
Could not deduce (FromJSON (MyFamily mt0))
arising from the ambiguity check for an instance declaration
from the context (SingKind (KindOf mt),
FromJSON (DemoteRep (KindOf mt)),
FromJSON (MyFamily mt))
bound by an instance declaration:
(SingKind (KindOf mt), FromJSON (DemoteRep (KindOf mt)),
FromJSON (MyFamily mt)) =>
FromJSON (Some MyCompoundType)
at SO.hs:(57,3)-(61,39)
The type variable ‘mt0’ is ambiguous
In the ambiguity check for:
forall (mt :: MyType).
(SingKind (KindOf mt), FromJSON (DemoteRep (KindOf mt)),
FromJSON (MyFamily mt)) =>
FromJSON (Some MyCompoundType)
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In the instance declaration for ‘FromJSON (Some (MyCompoundType))’
Run Code Online (Sandbox Code Playgroud)
我可以看到typechecker谈论的mt0而不是mt一个大问题,但我不知道如何把它哄骗到mt约束右侧的类型.
(我也意识到我没有包括FromJSON (MyFamily mt)实例,但如果类型检查员无法弄清楚mt ~ mt0我认为当前不重要).
希望有一个解决办法吗?
我花了相当多的时间尝试不同的东西,但是有很多不同的事情发生(单身,存在等).我正慢慢地达到某种程度的熟练程度,但我没有足够的知识或经验来确定他们是如何(或不是)为这个问题做出贡献.
(我之前对您先前问题的回答很大程度上适用于此)。
您可以自由地解析您想要的任何类型,您只需证明特定类型具有实例即可FromJSON。在这种情况下,您应该解析 的具体结果类型MyFamily,因为它们都有适当的实例。
instance FromJSON (Some MyCompoundType) where
parseJSON = withObject "MyCompoundType" $ \o -> do
cons :: String <- o .: "constructor"
mt :: MyType <- o .: "myType"
case toSing mt of
SomeSing smt ->
case cons of
"CompoundNoIndex" -> pure $ Some smt CompoundNoIndex
"CompoundWithIndex" -> case smt of
SMyValue1 -> Some SMyValue1 . CompoundWithIndex <$> o .: "field"
SMyValue2 -> Some SMyValue2 . CompoundWithIndex <$> o .: "field"
SMyValue3 -> Some SMyValue3 . CompoundWithIndex <$> o .: "field"
Run Code Online (Sandbox Code Playgroud)
这里我假设有一些东西表明编码的构造函数。当然,有许多可供选择的编码和解码格式。
或者,我们可以将量化约束的近似值放在一起,并更多地利用从字段解析的单例标签"myType":
import Data.Constraint -- from "constraints"
import Data.Proxy
data MyFamilySym :: TyFun MyType * -> *
type instance Apply MyFamilySym a = MyFamily a
class ForallInst (f :: TyFun k * -> *) (c :: * -> Constraint) where
allInst :: Proxy '(f, c) -> Sing x -> Dict (c (f @@ x))
instance ForallInst MyFamilySym FromJSON where
allInst _ SMyValue1 = Dict
allInst _ SMyValue2 = Dict
allInst _ SMyValue3 = Dict
instance FromJSON (Some MyCompoundType) where
parseJSON = withObject "MyCompoundType" $ \o -> do
cons :: String <- o .: "constructor"
SomeSing smt <- toSing <$> o .: "myType"
case cons of
"CompoundNoIndex" -> pure (Some smt CompoundNoIndex)
"CompoundWithIndex" ->
case allInst (Proxy :: Proxy '(MyFamilySym, FromJSON)) smt of
Dict -> Some smt . CompoundWithIndex <$> o .: "field"
Run Code Online (Sandbox Code Playgroud)
这里的关键点是MyFamilySym和的去功能化Apply。它使我们能够有效地放入MyFamily实例头,否则这将被 GHC 禁止。请参阅此博客文章,了解有关singletons.
对于类型族上的量化实例,我们永远无法避免一件事:写出类型族的所有情况并为每个情况演示一个实例。该ForallInst解决方案也做到了这一点,但至少它要求我们只写一次案例。