Haskell初学者,尝试以安全的方式包装HTTP REST API并使用自动Aeson解码返回值。我开始为每个API调用使用Haskell函数。这有点模糊,但还可以。
我希望将每个API调用转换为自己的数据类型。例如,对于登录,我会将其建模为指定方法的Login
类型,方法参数的Credentials
类型和API调用结果的LoginReponse
。参数和响应类型当然具有相应的FromJSON和ToJSON实例。
对于两个API调用,它看起来像这样(使用GADT):
data Void = Void
data Credentials = Credentials {...}
data LoginResponse = LoginResponse {...}
data LogoutResponse = LogoutResponse {...}
data Command a where
Login :: Credentials -> Command LoginResponse
Logout :: Void -> Command LogoutResponse
execute :: FromJSON a => Command a -> IO a
execute cmd = do
manager <- newManager tlsManagerSettings
let request = buildHttpRequest cmd
result <- httpLbs request manager
let body = responseBody result
let parsed = fromJust $ decode body
return parsed
这对我的用例很有用 - 我可以在执行它们之前内省命令,我无法构造无效的API调用,而且Aeson知道如何解码返回值!
这种方法的唯一问题是我必须在单个数据声明下将所有命令保存在单个文件中。
我想将方法定义(在我的示例Login
和Logout
中)移到单独的模块中,但要使execute
函数保持相似,当然要保持类型安全和艾森解码。
我尝试使用类型类创建一些东西,但无处可去。
欢迎任何提示如何做到这一点!
答案 0 :(得分:1)
由于execute
中针对不同命令的唯一不同之处是调用buildHttpRequest
,我建议使用以下备用数据类型:
type Command a = Tagged a HttpRequest
(我不知道buildHttpRequest
的返回类型,所以我做了一些事情,并假设它返回了HttpRequest
。希望这个想法很清楚,即使我是这样的。我确定我错了这部分。)Tagged
类型来自tagged,并允许您将类型附加到值;它是urphantom类型。在我们的示例中,我们将使用附加类型来决定如何在execute
步骤中解码JSON。
execute
的类型需要稍加修改才能要求解码附加类型:
execute :: FromJSON a => Command a -> IO a
然而,其实施基本保持不变;只需将buildHttpRequest
替换为untag
即可。这些命令不方便内省,但您可以沿模块边界拆分; e.g。
module FancyApp.Login (Credentials(..), LoginResponse(..), login) where
import FancyApp.Types
data Credentials = Credentials
data LoginResponse = LoginResponse
login :: Credentials -> Command LoginResponse
login = {- the old implementation of buildHttpRequest for Login -}
module FancyApp.Logout (LogoutResponse(..), logout) where
import FancyApp.Types
data LogoutResponse = LogoutResponse
logout :: Command LogoutResponse
logout = {- the old implementation of buildHttpRequest for Logout -}