某些REST服务具有返回JSON的变量,例如某些字段可能会根据请求的参数出现或消失,结构本身可能会更改,嵌套等。 因此,这导致类型数量的雪崩类型增长(以及FromJSON实例)。选项包括:
Maybe
下创建很多字段(但这对结构的可变性无济于事) 1。的缺点是,如果使用某些固定参数进行的调用始终返回已知字段,则您也必须处理Nothing
种情况,代码会变得更加复杂。 2.和3.很累。
在Haskell中处理这种可变性的最简单/便捷的方法是什么(如果您使用Aeson,请确保另一种选择是避免使用Aeson)?
答案 0 :(得分:1)
使用类型级计算解决现有/不存在字段问题的可能解决方案。
一些必需的扩展名和导入:
{-# LANGUAGE DeriveGeneric, ScopedTypeVariables, DataKinds, KindSignatures,
TypeApplications, TypeFamilies, TypeOperators, FlexibleContexts #-}
import Data.Aeson
import Data.Proxy
import GHC.Generics
import GHC.TypeLits
这是一种数据类型(要推广使用),它指示是否缺少某些字段或存在。也是将不存在的类型映射到()
的类型族:
data Presence = Present
| Absent
type family Encode p v :: * where
Encode Present v = v
Encode Absent v = ()
现在,我们可以定义一个包含所有可能字段的参数化记录,如下所示:
data Foo (a :: Presence)
(b :: Presence)
(c :: Presence) = Foo {
field1 :: Encode a Int,
field2 :: Encode b Bool,
field3 :: Encode c Char
} deriving Generic
instance (FromJSON (Encode a Int),
FromJSON (Encode b Bool),
FromJSON (Encode c Char)) => FromJSON (Foo a b c)
一个问题:为每次出现/不出现的组合编写完整类型将很繁琐,尤其是如果每次仅出现几个字段时。但是也许我们可以定义一个辅助类型同义词FooWith
,让我们仅提及存在的那些字段:
type family Mentioned (ns :: [Symbol]) (n :: Symbol) :: Presence where
Mentioned '[] _ = Absent
Mentioned (n ': _) n = Present
Mentioned (_ ': ns) n = Mentioned ns n
-- the field names are repeated as symbols, how to avoid this?
type FooWith (ns :: [Symbol]) = Foo (Mentioned ns "field1")
(Mentioned ns "field2")
(Mentioned ns "field3")
使用示例:
ghci> :kind! FooWith '["field2","field3"]
FooWith '["field2","field3"] :: * = Foo 'Absent 'Present 'Present
另一个问题:对于每个请求,我们必须重复必填字段列表两次:一次在URL中(“ fields = a,b,c ...”),另一次在期望的类型中。最好有一个单一的真理来源。
通过使用辅助类型类Demote
,我们可以从字段的类型级别列表中推断出要添加到URL的术语级别的字段列表:
class Demote (ns :: [Symbol]) where
demote :: Proxy ns -> [String]
instance Demote '[] where
demote _ = []
instance (KnownSymbol n, Demote ns) => Demote (n ': ns) where
demote _ = symbolVal (Proxy @n) : demote (Proxy @ns)
例如:
ghci> demote (Proxy @["field2","field3"])
["field2","field3"]