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

Rom*_*kov 1 haskell

总菜鸟在这里.我已经看到了使用do块执行HTTP Get请求的示例,但我希望通过组合来实现.

像这样:

get url = getResponseBody . simpleHTTP $ getRequest url
Run Code Online (Sandbox Code Playgroud)

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

这个没关系:

get :: String -> IO String
get url = getResponseBody =<< simpleHTTP (getRequest url)
Run Code Online (Sandbox Code Playgroud)

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

bhe*_*ilr 5

如果我们看一下类型:

getRequest :: String -> Request_String
simpleHTTP :: Request ty -> IO (Result (Response ty))
getResponseBody :: Result (Response ty) -> IO ty
type Request_String = Request String
Run Code Online (Sandbox Code Playgroud)

因此,出于这个问题的目的,我们可以将类型专门化

simpleHTTP :: Request_String -> IO (Result (Response String))
getResponseBody :: Result (Response String) -> IO String
Run Code Online (Sandbox Code Playgroud)

很明显,我们可以做到

simpleHTTP (getRequest url) :: IO (Result (Response String))
Run Code Online (Sandbox Code Playgroud)

但是,为了组成这个,getResponseBody我们需要使用monadic组合器.这有各种各样的数学原因,但为了简单起见,只需将其视为功能组合,不足以组合动作,只有功能.为了构成动作,引入了monad,并且它们自带了自己的合成运算符.比较这两个组合运算符的类型:

(.)   ::            (b ->   c) -> (a ->   b) -> (a ->   c)
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)  -- Imported from Control.Monad
Run Code Online (Sandbox Code Playgroud)

它们非常相似,不是吗?唯一的区别是添加mMonad约束.这是monad 组合运算符,它比普通.运算符更具限制性和强大功能,这意味着它可以执行更多操作,例如排序和执行IO操作,但这也意味着它可以使用更少的类型.它可以用作

get = simpleHTTP <=< simpleHTTP . getRequest
Run Code Online (Sandbox Code Playgroud)

但是,有一个更通用的组合定义版本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
Run Code Online (Sandbox Code Playgroud)

但是这需要你将monadic函数Kleisli包装在newtype包装器中并用它打开它runKleisli.您也可以跳过Control.Category并使用Control.Arrow:

get = runKleisli (Kleisli getResponseBody <<< Kleisli simpleHTTP) <<< getRequest
Run Code Online (Sandbox Code Playgroud)

你甚至可以交换它

get = getRequest >>> runKleisli (Kleisli simpleHTTP >>> Kleisli getResponseBody)
Run Code Online (Sandbox Code Playgroud)

如果你想让它从左到右而不是从右到左阅读.

最后,有几种方法可以编写等效的代码,但对于大多数人来说最熟悉和最易读,如果你坚持使用无点样式(没有必要是惯用的),那就是使用通常的.,<=<因为它是最短的实现,并使用更多可识别的运算符. Control.Category并且Control.Arrow在构建自定义内容时非常有用,但对于内置类型,通常已经定义了更有意义的运算符.