次のように、サーバント 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
すべての thar パーサーを生成したいので、特にパーサーは関数を生成する必要があります。API
API
Manager -> BaseUrl -> ClientM Int
それは可能ですか?
アップデート。
わかりました、質問は実際には2つで構成されています:
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))
シングルトンと 2 のタプルには 2 つのインスタンスがありますが、考えられるすべての次元のタプルに拡張するのは簡単です。
2 番目の質問はまだ不明です。異なる次元のタプルを生成するパーサーを生成する方法がまだわかりません。
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 型クラスと、Servant のクライアント関数と解析結果を type を持つものに変換する ConvertAPI 型クラスを定義して実装できます[Token] -> Manager -> BaseUrl -> ClientM a
。存在型にラップする必要があるa の型にはいくつかの複雑さがありますが、それでも可能です。これの実装はまだ完全ではありませんが、これは別の問題であり、型レベル プログラミングなどの最新の機能を使用して API を実装する方法です。
これを行う別の方法は、サーバントの API 定義から生成されたパーサーを使用して、値レベルで Servant.Common.Req に HTTP リクエストを構築することです。はるかに簡単ですが、多くのコードが必要であり、どういうわけか Servant の HasClient インスタンスを複製します。
率直に言って、最新の型レベルの機能を使用して API を設計するには、何らかのマニュアルが必要です。