Haskell中的函数组合和类型注释

时间:2015-04-10 15:09:03

标签: haskell

这里的总菜鸟。我已经看到了使用do块执行HTTP Get请求的示例,但我想通过合成来完成。

像这样:

get url = getResponseBody . simpleHTTP $ getRequest url

这是正确的吗?这个函数的正确类型注释是什么?

这个没问题:

get :: String -> IO String
get url = getResponseBody =<< simpleHTTP (getRequest url)

但我想写作而不是束缚。什么是更好/正确的方式?

1 个答案:

答案 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.CategoryControl.Arrow非常有用,但对于内置类型,通常已经定义了更有意义的运算符。