如何使用Haskell/Aeson中的类型函数解析多态值?
为了提高我对Haskell的理解,我启动了一个个人项目,允许用户结合许多不同的预定义转换,这些转换依赖于多态的环境和状态 核心类型围绕着通过如何使用Haskell/Aeson中的类型函数解析多态值?,haskell,polymorphism,typeclass,aeson,Haskell,Polymorphism,Typeclass,Aeson,为了提高我对Haskell的理解,我启动了一个个人项目,允许用户结合许多不同的预定义转换,这些转换依赖于多态的环境和状态 核心类型围绕着通过c参数化的环境、通过结果类型a参数化的状态、通过c参数化的类型类Base组织,并确定a的类型,以及一个typeclass步骤,该步骤提供了一个界面,用户可通过该界面在RWSmonad中定义可选择的变换,并通过c和a进行参数化: 类型计划c a=Control.Monad.RWS.RWS(环境c)日志(状态a) 数据环境c=环境c(设置条件) 数据状态a=状态
c
参数化的环境、通过结果类型a
参数化的状态、通过c
参数化的类型类Base
组织,并确定a
的类型,以及一个typeclass步骤
,该步骤提供了一个界面,用户可通过该界面在RWS
monad中定义可选择的变换,并通过c
和a
进行参数化:
类型计划c a=Control.Monad.RWS.RWS(环境c)日志(状态a)
数据环境c=环境c(设置条件)
数据状态a=状态a(设置约束)
类基c b a | b->a其中
execBase::Env c->b->(状态a,日志)
类步骤c a s,其中
定义步骤:s->c计划a()
--^计划通过>>
execPlan::(基本c b a)=>环境c->b->c计划a()->(a,日志)
代码库的其余部分主要定义了两种不同的数据类型,用户可以将这些数据类型插入到Env
的c
部分,一些数据类型可以是a
的结果,还有一些数据类型的SCAD,这些数据类型的存在是为了保存一个或两个参数,并且是base
或步骤的实例。问题在于,我不知道如何从用户提供的JSON文档中解析这些内容。我从以下几点开始:
data Request ca=Request(Env c)(WrappedBase c a)[WrappedStep c a]
数据包装数据库c a,其中
包装库::(基本c b a,等式b,显示b,可键入a,可键入b)
=>b->WrappedBase c a
数据包装步骤c a,其中
总结步骤::(步骤c a s、等式s、显示s、可打印s)
=>s->WrappedStep c a
但我不知道如何说服GHC让我为请求CA
创建一个Data.Aeson.FromJSON
实例。编写一个数据类型SomeC
非常简单,它是c
所有可能情况下的求和类型,为SomeC
编写一个解析器以及一个函数::c->data.Aeson.Value->data.Aeson.parser(Env c)
,几乎同样简单,但是我如何将它转换成Env c
的解析器,这样我就可以将c
与Request
中的其他c
统一起来呢
(我还尝试将解析器转换为延续传递样式,但一旦转换完毕,我就意识到我根本没有解决这个问题。)
更深层的谜团是,我如何让GHC在值级别执行类型函数b->a
,以便我可以将请求中的a
设置为a
,由b
的基实例指示,或者,向用户返回一条消息,让他们知道他们选择的b
没有为他们指定的c
定义
感觉上我想要的是使用类型平等见证,但是使用类型类而不是类型:*
,但是我在GHC的扩展丛中搜索,没有找到允许这样做的东西。如果您能够为请求CA
编写解析器,这意味着解析JSON的结果在c
和a
中是多态的,因此调用者可以将结果用作Request Int Double
,然后用作请求字符串Bool
,这两者都有意义。这可能不是你想要的
我在这里猜测,您有一组环境类型Env c
、基本类型b
、步骤类型s
,每个类型都有一个独立的FromJSON
实例,可以解析它,而不必考虑任何其他类型。(因此,例如,一个特定的基类型MyBase
可以被解析为一个MyBase
值,而不知道它将与之一起使用的环境c
或状态a
类型。)
显然,一个具体的请求只涉及一种环境类型Env c
和一种基本类型b
。我有点不清楚步骤列表是打算都是相同类型的步骤,还是不同类型的步骤的异构列表,但我将假设后者。如果是这样,您的解析的最终结果将是嵌套的存在请求类型:
data SomeRequest where
SomeRequest :: (Base c b a) => Env c -> b -> [SomeStep c a] -> SomeRequest
data SomeStep c a where
SomeStep :: (Step c a s) => s -> SomeStep c a
当您运行这样一个请求时,它会产生一个最终结果(即,最终状态a
),它本身必须是存在的。除非引入一些约束,否则该值将对您毫无用处。为了简单起见,我们将使用Show
,不过如果您计划将结果发送回请求者,那么ToJSON
可能是一个不错的选择。我们还需要将此约束添加到SomeRequest
类型:
data SomeRequest where
SomeRequest :: (Show a, Base c b a) => Env c -> b -> [SomeStep c a] -> SomeRequest
data SomeResult where
SomeResult :: (Show a) => a -> SomeResult
要运行存在主义请求以获得存在主义结果,可以使用以下方法:
runRequest :: SomeRequest -> SomeResult
runRequest (SomeRequest e b ss) = SomeResult $ execPlan e b (mapM_ runStep ss)
runStep :: SomeStep c a -> RWS (Env c) Log (State a) ()
runStep (SomeStep s) = defineStep s
execPlan :: (Base c b a) => Env c -> b -> Plan c a () -> a
execPlan e b p = case execRWS p e (execBase e b) of (State a, _) -> a
您可以像这样使用runRequest
:
main = do
let r = parseRequest "<some JSON input>"
result = runRequest r
case result of SomeResult r -> print r
parseRequest :: String -> SomeRequest
parseRequest inp
= case (parseC inp, parseB inp) of
-- instance Base C1 B1 A1
(C1_ c1, B1_ b1) -> makeRequest c1 b1
-- instance Base C2 B1 A1
(C2_ c2, B1_ b1) -> makeRequest c2 b1
-- instance Base C1 B2 A2
(C1_ c1, B2_ b2) -> makeRequest c1 b2
(_, _) -> error "incompatible environment/base combination"
where makeRequest :: (Show a, Base c b a, ToSomeStep c a) => c -> b -> SomeRequest
makeRequest c b = SomeRequest (Env c) b (someStep <$> parseSList inp)
就我所见,这并不是一个真正的Aeson问题,尝试使其成为一个会使基本原理复杂化的问题,因此让我们忽略实际的解析,并对解析结果执行类型级编程
假设我们有以下环境、基本、步骤和状态/结果类型以及有效实例:
-- environments (c)
data C1 = C1 Int
data C2 = C2 String
-- bases (b)
data B1 = B1 Double
data B2 = B2 ()
-- steps (s)
data S1 = S1 Double
data S2 = S2 (Maybe Double)
data S3 = S3 ()
-- results (a)
data A1 = A1 Char deriving (Show)
data A2 = A2 Double deriving (Show)
-- valid base instances
instance Base C1 B1 A1
instance Base C2 B1 A1
instance Base C1 B2 A2
-- valid step instances
instance Step C1 A1 S1
instance Step C1 A1 S2
instance Step C2 A1 S2
instance Step C1 A2 S3
这里我假设您可以将环境、基础和步骤解析为sum类型。我知道你有很多基础和步骤,但我看不出有任何方法可以避免列举它们。毕竟,您需要为Aeson提供完整的有效基集和完整的有效步骤集,因此您还可以使用sum类型来驱动解析,并充当基和步骤的集中枚举
-- parse environment
data SomeC = C1_ C1 | C2_ C2
parseC :: String -> SomeC
parseC = undefined
-- parse base
data SomeB = B1_ B1 | B2_ B2
parseB :: String -> SomeB
parseB = undefined
-- parse list of steps
data SomeS = S1_ S1 | S2_ S2 | S3_ S3
parseSList :: String -> [SomeS]
parseSList = undefined
我们需要
parseRequest :: String -> SomeRequest
parseRequest inp
= case (parseC inp, parseB inp) of
-- instance Base C1 B1 A1
(C1_ c1, B1_ b1) -> SomeRequest (Env c1) b1 []
-- instance Base C2 B1 A1
(C2_ c2, B1_ b1) -> SomeRequest (Env c2) b1 []
-- instance Base C1 B2 A2
(C1_ c1, B2_ b2) -> SomeRequest (Env c1) b2 []
(_, _) -> error "incompatible environment/base combination"
parseRequest inp
= case (parseC inp, parseB inp, parseS inp) of
-- instance Base C1 B1 A1
(C1_ c1, B1_ b1, ss) -> SomeRequest (Env c1) b1 ??
parseRequest inp
= case (parseC inp, parseB inp, parseSList inp) of
-- instance Base C1 B1 A1
(C1_ c1, B1_ b1, ss) -> SomeRequest (Env c1) b1 $
map (\s -> case s of
-- instance Step C1 A1 S1
S1_ s1 -> SomeStep s1
-- instance Step C1 A1 S2
S2_ s2 -> SomeStep s2)
ss
...
someStepC1A1 :: SomeS -> SomeStep C1 A1
-- instance Step C1 A1 S1
someStepC1A1 (S1_ s) = SomeStep s
-- instance Step C1 A1 S2
someStepC1A1 (S2_ s) = SomeStep s
someStepC1A1 _ = error "bad step for C1/A1 combination"
someStepC2A1 :: SomeS -> SomeStep C2 A1
-- instance Step C2 A1 S2
someStepC2A1 (S2_ s) = SomeStep s
someStepC2A1 _ = error "bad step for C2/A1 combination"
someStepC1A2 :: SomeS -> SomeStep C1 A2
-- instance Step C1 A2 S3
someStepC1A2 (S3_ s) = SomeStep s
someStepC1A2 _ = error "bad step for C1/A2 combination"
parseRequest :: String -> SomeRequest
parseRequest inp
= case (parseC inp, parseB inp, parseSList inp) of
-- instance Base C1 B1 A1
(C1_ c1, B1_ b1, ss) -> SomeRequest (Env c1) b1 (someStepC1A1 <$> ss)
-- instance Base C2 B1 A1
(C2_ c2, B1_ b1, ss) -> SomeRequest (Env c2) b1 (someStepC2A1 <$> ss)
-- instance Base C1 B2 A2
(C1_ c1, B2_ b2, ss) -> SomeRequest (Env c1) b2 (someStepC1A2 <$> ss)
(_, _, _) -> error "incompatible environment/base combination"
class ToSomeStep c a where
someStep :: SomeS -> SomeStep c a
instance ToSomeStep C1 A1 where
-- instance Step C1 A1 S1
someStep (S1_ s) = SomeStep s
-- instance Step C1 A1 S2
someStep (S2_ s) = SomeStep s
someStep _ = error "bad step for C1/A1 combination"
instance ToSomeStep C2 A1 where
-- instance Step C2 A1 S2
someStep (S2_ s) = SomeStep s
someStep _ = error "bad step for C2/A1 combination"
instance ToSomeStep C1 A2 where
-- instance Step C1 A2 S3
someStep (S3_ s) = SomeStep s
someStep _ = error "bad step for C1/A2 combination"
parseRequest :: String -> SomeRequest
parseRequest inp
= case (parseC inp, parseB inp) of
-- instance Base C1 B1 A1
(C1_ c1, B1_ b1) -> makeRequest c1 b1
-- instance Base C2 B1 A1
(C2_ c2, B1_ b1) -> makeRequest c2 b1
-- instance Base C1 B2 A2
(C1_ c1, B2_ b2) -> makeRequest c1 b2
(_, _) -> error "incompatible environment/base combination"
where makeRequest :: (Show a, Base c b a, ToSomeStep c a) => c -> b -> SomeRequest
makeRequest c b = SomeRequest (Env c) b (someStep <$> parseSList inp)
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
import Control.Monad.RWS
type Log = ()
type Plan c a = RWS (Env c) Log (State a)
newtype Env c = Env c deriving (Show)
newtype State a = State a
class Base c b a | b -> a where
execBase :: Env c -> b -> State a
class Step c a s where
defineStep :: s -> Plan c a ()
data SomeRequest where
SomeRequest :: (Show a, Base c b a) => Env c -> b -> [SomeStep c a] -> SomeRequest
data SomeStep c a where
SomeStep :: (Step c a s) => s -> SomeStep c a
data SomeResult where
SomeResult :: (Show a) => a -> SomeResult
runRequest :: SomeRequest -> SomeResult
runRequest (SomeRequest e b ss) = SomeResult $ execPlan e b (mapM_ runStep ss)
runStep :: SomeStep c a -> RWS (Env c) Log (State a) ()
runStep (SomeStep s) = defineStep s
execPlan :: (Base c b a) => Env c -> b -> Plan c a () -> a
execPlan e b p = case execRWS p e (execBase e b) of (State a, _) -> a
-- environments (c)
data C1 = C1 Int
data C2 = C2 String
-- bases (b)
data B1 = B1 Double
data B2 = B2 ()
-- steps (s)
data S1 = S1 Double
data S2 = S2 (Maybe Double)
data S3 = S3 ()
-- results (a)
data A1 = A1 Char deriving (Show)
data A2 = A2 Double deriving (Show)
-- valid base instances
instance Base C1 B1 A1
instance Base C2 B1 A1
instance Base C1 B2 A2
-- valid step instances
instance Step C1 A1 S1
instance Step C1 A1 S2
instance Step C2 A1 S2
instance Step C1 A2 S3
-- parse environment
data SomeC = C1_ C1 | C2_ C2
parseC :: String -> SomeC
parseC = undefined
-- parse base
data SomeB = B1_ B1 | B2_ B2
parseB :: String -> SomeB
parseB = undefined
-- parse list of steps
data SomeS = S1_ S1 | S2_ S2 | S3_ S3
parseSList :: String -> [SomeS]
parseSList = undefined
class ToSomeStep c a where
someStep :: SomeS -> SomeStep c a
instance ToSomeStep C1 A1 where
-- instance Step C1 A1 S1
someStep (S1_ s) = SomeStep s
-- instance Step C1 A1 S2
someStep (S2_ s) = SomeStep s
someStep _ = error "bad step for C1/A1 combination"
instance ToSomeStep C2 A1 where
-- instance Step C2 A1 S2
someStep (S2_ s) = SomeStep s
someStep _ = error "bad step for C2/A1 combination"
instance ToSomeStep C1 A2 where
-- instance Step C1 A2 S3
someStep (S3_ s) = SomeStep s
someStep _ = error "bad step for C1/A2 combination"
parseRequest :: String -> SomeRequest
parseRequest inp
= case (parseC inp, parseB inp) of
-- instance Base C1 B1 A1
(C1_ c1, B1_ b1) -> makeRequest c1 b1
-- instance Base C2 B1 A1
(C2_ c2, B1_ b1) -> makeRequest c2 b1
-- instance Base C1 B2 A2
(C1_ c1, B2_ b2) -> makeRequest c1 b2
(_, _) -> error "incompatible environment/base combination"
where makeRequest :: (Show a, Base c b a, ToSomeStep c a) => c -> b -> SomeRequest
makeRequest c b = SomeRequest (Env c) b (someStep <$> parseSList inp)
main = do
let r = parseRequest "<some JSON input>"
result = runRequest r
case result of SomeResult r -> print r