如何在纯函数中跳过不必要的IOs?

How to skip unnecessary IOs in pure functions?

更新

本题有额外限制, 也就是尽量避免IO.

这个限制原来放在我问题的最后, 这似乎很难被注意到。

我实际上知道如何在 Haskell 中实现我的目标, 就像我知道如何在其他命令式编程语言中那样做 - 命令式方式。

但我没有使用任何其他命令式编程,对吗? 我正在使用 Haskell。 我想要一种 Haskell 方式,一种纯粹的方式。

我重新组织了我的问题,将额外的限制重新放置在一个相对显眼的地方。 那是我的错。 非常感谢您的快速回复。

原问题

main :: IO ()
main =
    putStrLn =<< cachedOrFetched
        <$> getLine
        <*> getLine

cachedOrFetched :: String -> String -> String
cachedOrFetched cached fetched =
    if not $ null cached
    then cached
    else fetched

以上代码执行了两次IO。 但期望的行为是跳过第二个 IO 当第一个 IO 的结果不为空时。

我知道我可以通过使用 dowhen 来实现。 鉴于使用太多 do 违背了我使用 Haskell 的初衷, 我可能会和 when 一起生活。

或者有更好的方法吗?更纯粹的方法?

这是全部内容

我大约两周前开始学习 Haskell。 我不期望从中得到一份工作, 但只是被编程语言本身所吸引。 因为它是我所知道的“最纯粹”的

起初,一切似乎都如我所料。 但是后来我发现我必须在我的纯代码中写 IOs 。 我花了很长时间才找到一种方法来控制不纯污染。 Applicative Functor 似乎是救星。

有了它,我可以将不纯的IOs“咖喱”到我的纯函数中, 这节省了大量 do<- 和明确的 IO 符号。 但是,我遇到了这个问题 - 我无法在纯函数中跳过不必要的 IOs。

同样,大量的搜索和阅读。 遗憾的是,至今没有满意的答案。

参考资料

要跳过 IO,您必须告诉 cachedOrFetched 您正在执行 IO,而不是将两个字符串传递给它 读取两行之后。只有这样,它才能有条件地 运行 第二个 getLine IO 动作。当然,cachedOrFetched不一定需要处理IO,它可以对any monad:

起作用
main :: IO ()
main = putStrLn =<< cachedOrFetched getLine getLine

cachedOrFetched :: Monad m => m String -> m String -> m String
cachedOrFetched first second = do
    cached <- first
    if not $ null cached
        then return cached
        else second

我可能会写

main :: IO ()
main = getLine >>= ifNullDo getLine >>= putStrLn

ifNullDo :: Applicative f => f String -> String -> f String
ifNullDo fetch cached = 
    if not $ null cached
        then pure cached
        else fetch

I am not using any other imperative programming, right?

是的,你是。如果您正在编写 main 函数或使用 IO 类型,那么您正在编写一个命令式程序,一个接一个的 IO 操作。 (纯)程序需要控制 运行 putStrLngetLine 命令的时间、是否以及以何种顺序。

Haskell 只是使命令式编程显式化,并允许很容易地从中抽象出来,例如运行通过换出 monad 来模拟而不是实际的 IO。

I'd like a Haskell way, a pure way.

Haskell方式是将纯应用逻辑从不纯的边界中分离出来,对外开放;很像ports and adapters architecture。当然,这只能到此为止,对于像 cachedOrFetched 这样的玩具示例,很难看出将边界放在哪里。但是在较大的应用程序中,您通常会在业务逻辑中提出自己的“操作”抽象,并且只处理它们而不是直接 IO

这是您的代码在 do notation 中编写时的样子:

main :: IO ()
main = do
  cached <- getLine
  fetched <- getLine
  putStrLn $ cachedOrFetched cached fetched

您可以看到两个 IO 操作都在调用 cachedOrFetched 之前完成。您可以像这样在 main 中完成所有操作:

main :: IO ()
main = do
  cached <- getLine
  if not $ null cached
    then putStrLn cached
    else do
      fetched <- getLine
      putStrLn fetched

原因是因为 cachedOrFetched 必须能够访问两个值(它们是其调用的参数!)才能决定选择哪个.