如何在纯函数中跳过不必要的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 的结果不为空时。
我知道我可以通过使用 do
或 when
来实现。
鉴于使用太多 do
违背了我使用 Haskell 的初衷,
我可能会和 when
一起生活。
或者有更好的方法吗?更纯粹的方法?
这是全部内容
我大约两周前开始学习 Haskell。
我不期望从中得到一份工作,
但只是被编程语言本身所吸引。
因为它是我所知道的“最纯粹”的
起初,一切似乎都如我所料。
但是后来我发现我必须在我的纯代码中写 IO
s 。
我花了很长时间才找到一种方法来控制不纯污染。
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 操作。 (纯)程序需要控制 运行 putStrLn
和 getLine
命令的时间、是否以及以何种顺序。
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
必须能够访问两个值(它们是其调用的参数!)才能决定选择哪个.
更新
本题有额外限制,
也就是尽量避免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 的结果不为空时。
我知道我可以通过使用 do
或 when
来实现。
鉴于使用太多 do
违背了我使用 Haskell 的初衷,
我可能会和 when
一起生活。
或者有更好的方法吗?更纯粹的方法?
这是全部内容
我大约两周前开始学习 Haskell。 我不期望从中得到一份工作, 但只是被编程语言本身所吸引。 因为它是我所知道的“最纯粹”的
起初,一切似乎都如我所料。
但是后来我发现我必须在我的纯代码中写 IO
s 。
我花了很长时间才找到一种方法来控制不纯污染。
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 操作。 (纯)程序需要控制 运行 putStrLn
和 getLine
命令的时间、是否以及以何种顺序。
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
必须能够访问两个值(它们是其调用的参数!)才能决定选择哪个.