在将逻辑保持为纯功能的同时,可以对IO操作进行排序吗? [英] Can IO actions be sequenced while keeping the logic in a pure function?
问题描述
我有以下代码,可从分页的API端点获取两页数据.我想修改query
函数以使其不断获取页面,直到发现更多数据为止(因此,将下面代码中的take 2
替换为查看API响应的内容).
I have the following code which grabs two pages of data from a paginated API endpoint. I'd like to modify query
function to keep getting pages until it finds no more data (so replace take 2
in the code below with something which looks at the API response).
我的问题是,是否可以在不将query
函数更改为IO
函数的情况下实现此目标.如果是这样,我将如何处理.如果没有,是否可以在不编写递归函数的情况下做到这一点?
My question is wether it is possible to achieve this without changing query
function to an IO
function. And if so, how would I go about it. If not, is there a way of doing this without writing recursive function?
这是代码:
#!/usr/bin/env stack
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
import Servant.Client
import Network.HTTP.Client (newManager, defaultManagerSettings)
import Data.Proxy
import Servant.API
import Data.Aeson
import GHC.Generics
-- data type
data BlogPost = BlogPost
{ id :: Integer
, title :: String
} deriving (Show, Generic)
instance FromJSON BlogPost
-- api client
type API = "posts" :> QueryParam "_page" Integer :> Get '[JSON] [BlogPost]
api :: Proxy API
api = Proxy
posts :: Maybe Integer -> ClientM [BlogPost]
posts = client api
-- query by page
query :: ClientM [[BlogPost]]
query = sequence $ take 2 $ map posts pages
where
pages = [Just p | p <- [1..]]
-- main
main :: IO ()
main = do
manager' <- newManager defaultManagerSettings
let url = ClientEnv manager' (BaseUrl Http "jsonplaceholder.typicode.com" 80 "")
posts' <- runClientM query url
print posts'
我尝试使用takeWhileM
来执行此操作,最终使查询成为IO
函数并将url
传递给它.它开始看起来非常可怕,而且我无法找到匹配的类型(我觉得我需要更多类似于(a -> m Bool) -> m [a] -> m [a]
而不是(a -> m Bool) -> [a] -> m [a]
的东西,而takeWhileM
就是这种东西-仍然觉得这个奇怪,因为我知道此功能用作过滤器,但输入列表和输出列表不同(一个在其周围有单子,而另一个没有).
I've tried to use takeWhileM
to do this and ended up making query an IO
function and passing url
into it. It was starting to look pretty horrible and I couldn't get the types to match up (I felt like I needed something more like (a -> m Bool) -> m [a] -> m [a]
rather than (a -> m Bool) -> [a] -> m [a]
which is what takeWhileM
is - still find this strange because I see this function as a filter, yet the input list and output list are different (one has monad around it and the other doesn't)).
推荐答案
For these cases of monadic iteration I usually turn to the streaming library. Its interface is reminiscent to that of pure lists, while still allowing effects:
import Streaming
import qualified Streaming.Prelude as S
repeatAndCollect :: Monad m => m (Either a r) -> m [a]
repeatAndCollect = S.toList_ . Control.Monad.void . S.untilRight
repeatAndCollectLimited :: Monad m => Int -> m (Either a r) -> m [a]
repeatAndCollectLimited len = S.toList_ . S.take len . S.untilRight
使用 untilRight
, take
和 toList_
函数.
Using the untilRight
, take
and toList_
functions.
当仅需要第一个成功的结果时,我们可以使用ExceptT
转换器与
When only the first successful result is needed, we can use the Alternative
instance of the ExceptT
transformer in combination with asum
from Data.Foldable
to execute a list of fallible actions until one of them succeeds.
IO
本身具有一个Alternative
实例,该实例返回第一个成功",其中失败"表示抛出IOException
.
IO
itself has an Alternative
instance that returns the first "success", where "failure" means throwing a IOException
.
这篇关于在将逻辑保持为纯功能的同时,可以对IO操作进行排序吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!