这里的总菜鸟。我已经看到了使用do
块执行HTTP Get请求的示例,但我想通过合成来完成。
像这样:
get url = getResponseBody . simpleHTTP $ getRequest url
这是正确的吗?这个函数的正确类型注释是什么?
这个没问题:
get :: String -> IO String
get url = getResponseBody =<< simpleHTTP (getRequest url)
但我想写作而不是束缚。什么是更好/正确的方式?
答案 0 :(得分:5)
如果我们看一下类型:
getRequest :: String -> Request_String
simpleHTTP :: Request ty -> IO (Result (Response ty))
getResponseBody :: Result (Response ty) -> IO ty
type Request_String = Request String
因此,出于这个问题的目的,我们可以将类型专门化为
simpleHTTP :: Request_String -> IO (Result (Response String))
getResponseBody :: Result (Response String) -> IO String
很明显,我们可以做到
simpleHTTP (getRequest url) :: IO (Result (Response String))
但是,为了用getResponseBody
组合,我们需要使用monadic组合器。这有各种各样的数学原因,但为了简单起见,只需将其视为功能组合,不足以组合动作,只有功能。为了构成动作,引入了monad,并且它们自带了自己的合成运算符。比较这两个组合运算符的类型:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c) -- Imported from Control.Monad
他们非常相似,不是吗?唯一的区别是m
添加了Monad
约束。这是monads的 组合运算符,它比普通.
运算符更具限制性和强大功能,这意味着它可以执行更多操作,例如排序和执行IO操作,但它也意味着它适用于较少的类型。它可以用作
get = simpleHTTP <=< simpleHTTP . getRequest
但是,Control.Category
中定义的一个更通用的组合版本可以处理两者:
import Prelude hiding ((.), id) -- Hide the less general versions in Prelude
import Control.Category
import Control.Arrow
get = runKleisli (Kleisli getResponseBody . Kleisli simpleHTTP) . getRequest
但是这需要你将monadic函数包装在Kleisli
newtype包装器中并用runKleisli
打开它。您也可以跳过Control.Category
并使用Control.Arrow
:
get = runKleisli (Kleisli getResponseBody <<< Kleisli simpleHTTP) <<< getRequest
你甚至可以交换它
get = getRequest >>> runKleisli (Kleisli simpleHTTP >>> Kleisli getResponseBody)
如果你想让它从左到右而不是从右到左阅读。
最后,有几种方法可以编写等效的代码,但对大多数人来说最熟悉和最易读,如果你坚持使用无点样式(没有必要是惯用的),那就是使用通常的.
和<=<
,因为它是最短的实现,并使用更多可识别的运算符。构建自定义内容时Control.Category
和Control.Arrow
非常有用,但对于内置类型,通常已经定义了更有意义的运算符。