如果我在仆人 DSL 中描述了一些仆人的 API,例如:
type API = "some" :> QueryParam "a" Int :> QueryParam "b" Double:> Get '[JSON] Int
它有 HasClient 实例,所以我有一个客户端:
λ> let theClient = client (Proxy :: Proxy API)
λ> :t theClient
theClient
:: Maybe Int
-> Maybe Double
-> http-client-0.4.31:Network.HTTP.Client.Types.Manager
-> BaseUrl
-> ClientM Int
λ>
如果我要手动调用所有这些 API,那就太好了。但是,如果我需要从文本文件生成 HTTP 请求怎么办?
API
我可以从使用(我自己的)类型类
生成简单的解析器,HasParser
它解析来自文本文件的所有查询参数,如下所示:
get some a=1234 b=0.1234
...
但是如何将解析的结果与客户端的类型联系起来呢?
当然,我可以为每个API
方法在值级别编写所有解析器,但是我想从API
类型级别生成所有 thar 解析器,因此特别API
是解析器应该生成一个函数:
Manager -> BaseUrl -> ClientM Int
甚至可能吗?
UPD。
好的,问题实际上包括两个:
1)如何生成生成通用客户端
2)如何生成解析器,实际上使用什么类型来保持解析结果。
(1)虽然很笨重,但很简单:
type API = QueryParam "a" Int :> Get '[JSON] Int
type family FinalType k where
FinalType (a :> b) = FinalType b
FinalType (Verb a b c d) = Client (Verb a b c d)
class ConvertAPI api input where
convert :: Proxy api -> input -> FinalType api
instance ( HasClient api
, Client api ~ (Maybe a -> FinalType api)
, Read a
)
=> ConvertAPI api String where
convert (Proxy :: Proxy api) i = (client (Proxy :: Proxy api)) (Just (read i))
instance ( HasClient api
, Client api ~ (Maybe a -> Maybe b -> FinalType api)
, Read a
, Read b
)
=> ConvertAPI api (String,String) where
convert (Proxy :: Proxy api) (i,ii) = (client (Proxy :: Proxy api)) (Just (read i)) (Just (read i))
单例和二元组有两个实例,但很容易扩展所有可能维度的元组。
第二个问题还不清楚,我还不知道如何生成会产生不同维度元组的解析器。
UPD2。
使用嵌套元组的最小实现在这里: https ://gist.github.com/voidlizard/ef67a7ae486834d591d7b45c493bec1d
it gives:
λ> parse (Proxy :: Proxy API) ["42"]
Just ("42",())
λ> parse (Proxy :: Proxy API2) ["some","42"]
Just ("42",())
λ> parse (Proxy :: Proxy API2) ["42"]
Nothing
所以基本上这就是我想要的,但实现非常难看。是否有可能让它变得更好 - 使用一些 HLists 或 Vinyl 或其他东西来摆脱嵌套的元组?
UPD3 和结论。
好吧,好消息是很有可能的。我们可以定义和实现 HasParser 类型类,它定义了所有解析函数和类型或数据族来解析结果,以及 ConvertAPI 类型类,它将把 Servant 的客户端函数和解析结果转换为具有 type 的东西[Token] -> Manager -> BaseUrl -> ClientM a
。应该将a的类型包装成存在类型有一些复杂性,但它仍然是可能的。实现这一点仍然不完美,但这是另一个问题,如何实现具有现代特性(如类型级编程)的 API。
另一种方法是使用从servant 的API 定义生成的解析器,在Servant.Common.Req 上在值级别上构造HTTP 请求。更容易,但需要大量代码,并且以某种方式复制了 Servant 的 HasClient 实例。
坦率地说,我们都需要某种手册,如何使用现代类型级功能设计 API。