解析json时如何从多个GADT构造值?

问题描述

我有以下数据结构:

data Operator :: * -> * where
  StringEquals     :: Operator String
  StringNotEquals  :: Operator String
  NumericEquals    :: Operator Integer
  NumericNotEquals :: Operator Integer

data Variable :: * -> * where
  UserName     :: Variable String
  RequestPath  :: Variable String
  BodyLength   :: Variable Integer

data Value :: * -> * where
  StringValue     :: String -> Value String
  NumericValue    :: Integer -> Value Integer

data Condition a = Condition (Operator a) (Variable a) (Value a)

将在以下函数中使用:

extractvariable :: Variable a -> HttpRequest -> a
evaluate :: Operator a -> Value a -> a -> Bool

我想从 JSON 文件中解析 Condition。我可以单独解析这些部分,用 Some 包裹,但不确定如何从中构造 Condition。 以下工作并显示了我想要做的事情,但显然无法编写:

import Data.some

parseCondition :: Some Operator -> Some Variable -> Some Value -> Parser (Some Condition)
parseCondition (Some op@StringEquals) (Some var@UserName) (Some val@(StringValue _)) = return $ mkSome $ Condition op var val
parseCondition (Some op@StringNotEquals) (Some var@UserName) (Some val@(StringValue _)) = return $ mkSome $ Condition op var val
parseCondition (Some op@StringEquals) (Some var@RequestPath) (Some val@(StringValue _)) = return $ mkSome $ Condition op var val
parseCondition (Some op@StringNotEquals) (Some var@RequestPath) (Some val@(StringValue _)) = return $ mkSome $ Condition op var val
parseCondition (Some op@NumericEquals) (Some var@BodyLength) (Some val@(NumericValue _)) = return $ mkSome $ Condition op var val
parseCondition _ _ _ = parseFail "incompatible types"

在 haskell 中执行此操作的最佳方法是什么?

解决方法

给定一个 Operator a(分别为 Variable aValue a),您确切地知道 a 是什么。使用表示类型 a 的完整知识的标准类型是 TypeRep a,在代码中表达该知识。

import Type.Reflection -- NOT the other TypeRep

operatorType :: Operator a -> TypeRep a
operatorType StringEquals = typeRep
operatorType StringNotEquals = typeRep
operatorType NumericEquals = typeRep
operatorType NumericNotEquals = typeRep

variableType :: Variable a -> TypeRep a
variableType UserName = typeRep
variableType RequestPath = typeRep
variableType BodyLength = typeRep

valueType :: Value a -> TypeRep a
valueType (StringValue _) = typeRep
valueType (NumericValue _) = typeRep

那是很多样板文件,但我们的想法是现在每个构造函数扩展为一行,而不是每个可能的三个构造函数组合一行。此外,它看起来可以自动化,但我不知道有任何“预制”解决方案。

现在只需对 TypeRep 进行相等测试。

import Data.Type.Equality
-- data a :~: b where Refl :: a :~: a
-- testEquality :: TypeRep a -> TypeRep b -> Maybe (a :~: b)

-- data Some f = forall a. Some (f a) -- I guess?
makeCondition :: Some Operator -> Some Variable -> Some Value -> Maybe (Some Condition)
makeCondition (Some op) (Some v) (Some x) = do -- I generally prefer guards for this "proofwork" but in this case a do happens to work
  Refl <- operatorType op `testEquality` variableType v
  Refl <- variableType v `testEquality` valueType x
  return $ Some $ Condition op v x