在将逻辑保持为纯功能的同时,可以对IO操作进行排序吗? [英] Can IO actions be sequenced while keeping the logic in a pure function?

查看:84
本文介绍了在将逻辑保持为纯功能的同时,可以对IO操作进行排序吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码,可从分页的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.

当仅需要第一个成功的结果时,我们可以使用Alternative实例-Trans-Except.html#t:ExceptT"rel =" noreferrer> 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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆