更新
这个问题有一个额外的限制,
这是为了避免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
s违背了我使用Haskell的初衷,
我可能会和 when
一起生活.
或者有更好的方法吗?更纯粹的方式?
这就是全部内容
大约两周前我开始学习 Haskell。 我不期待它的工作, 但只是被编程语言本身所吸引。 因为据我所知,它是“最纯粹的”。
起初,一切似乎都和我预期的一样好。
但后来我发现我必须写 IO
s 在我的纯代码中。
我花了很长时间才找到一种方法来控制不纯污染。
Applicative Functor 似乎是救星。
有了它,我可以将不纯的 IO “curry” 到我的纯函数中,
节省了很多 do
, <-
和明确的IO
符号。
但是,我遇到了这个问题 -
我无法跳过纯函数中不必要的 IO。
再次,大量搜索和阅读。 不幸的是,到目前为止还没有令人满意的答案。
引用文献
最佳答案
要跳过 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 Action 接一个接一个。 (纯)程序需要控制何时、是否以及以什么顺序运行 putStrLn
和 getLine
命令。
Haskell 只是明确了命令式编程,并允许非常容易地从中抽象出来,例如通过换出 monad 来运行模拟而不是实际的 IO。
I'd like a Haskell way, a pure way.
Haskell 方式是将纯应用逻辑与不纯边界分离到外界;非常喜欢ports and adapters architecture .当然,这只能到此为止,对于 cachedOrFetched
之类的玩具示例,很难看出在哪里放置边界。但在较大的应用程序中,您通常会为业务逻辑中的“ Action ”提出自己的抽象,并且只处理它们而不是直接处理 IO
。
关于haskell - 如何在纯函数中跳过不必要的 IO?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70226458/