如何使用Haskell/Aeson中的类型函数解析多态值?

如何使用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=状态

为了提高我对Haskell的理解,我启动了一个个人项目,允许用户结合许多不同的预定义转换,这些转换依赖于多态的环境和状态

核心类型围绕着通过
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