haskell - 在 IO monad 中进行递归
问题描述
我一直试图弄清楚如何在 IO monad 中进行递归。我熟悉使用纯函数进行递归,但无法将这些知识转移到 IO monads。
使用纯函数递归
我很乐意使用纯函数进行递归,例如foo
下面的函数。
foo (x:y:ys) = foo' x y ++ foo ys
具有 IO [String] 输出
的函数我创建了一个如下goo
所示的函数,它可以满足我的需要并具有 IO 输出。
goo :: String -> String -> IO [String]
goo xs ys = goo' xs ys
尝试在 IO monad
中进行递归 当我尝试在 IO monad 中进行递归时(例如,“main”函数),我做不到。我查找了liftM
,replicateM
和 undo-the-IO<-
运算符或函数。我想要一个类似hoo
or的 IO monad hoo'
(为接下来的胡言乱语道歉)。
hoo :: [String] -> IO [String]
hoo (xs:ys:yss) = do
let rs = goo xs ys ++ hoo yss
return rs
或者
hoo' :: [String] -> IO [String]
hoo' (xs:ys:yss) = do
rs <- goo xs ys
let gs = rs ++ hoo' yss
return gs
(顺便说一句,如果你想知道我的项目是什么,我正在为一门课程从头开始编写遗传算法程序。我的函数需要两个父母并繁殖两个后代,因为使用随机数生成器goo
,它们作为 IO 返回。goo
我需要做的是使用递归hoo
函数goo
从 20 个父母的列表中繁殖 20 个后代。我的想法是取列表中的前两个父母,繁殖两个后代,带上接下来的两个父母名单,繁殖另一对后代,依此类推。)
解决方案
如果您发现do
符号令人困惑,我的建议是根本不要使用它。您可以使用>>=
. 假装它的类型是
(>>=) :: IO a -> (a -> IO b) -> IO b
也就是说,让我们看看你的代码。
let
在一个do
块中为某个值命名。这与它在 之外所做的事情是一样的do
,所以这在这里没有帮助(它没有给你额外的权力)。
<-
更有趣的是:它充当“从本地 IO 中提取值”的构造(如果你眯着眼睛看的话)。
hoo :: [String] -> IO [String]
hoo (xs:ys:yss) = do
-- The right-hand side (goo xs ys) has type IO [String], ...
rs <- goo xs ys
-- ... so rs :: [String].
-- We can apply the same construct to our recursive call:
hs <- hoo yss
-- hoo yss :: IO [String], so hs :: [String].
let gs = rs ++ hs
return gs
如上所述,let
只是将名称绑定到一个值,所以我们在这里并不需要它:
hoo :: [String] -> IO [String]
hoo (xs:ys:yss) = do
rs <- goo xs ys
hs <- hoo yss
return (rs ++ hs)
或者,没有do
符号,<-
我们将按如下方式进行。
(>>=) :: IO a -> (a -> IO b) -> IO b
>>=
接受一个值和一个回调函数,并在“展开”值 ( )IO
上运行该函数。a
这意味着在函数中,只要整个事情的结果IO b
再次出现(对于某些任意类型b
),我们就可以本地访问该值。
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys -- :: IO [String]
...
我们有一个IO [String]
,我们需要做一些事情[String]
,所以我们使用>>=
:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= (\rs -> ...)
如果看>>=
's 的类型签名,这里 ( )a
起到的作用也是 is (因为整体需要 return )。[String]
rs :: [String]
b
[String]
hoo
IO [String]
那么我们在这...
部分做什么呢?我们需要对 进行递归调用hoo
,这又会产生一个IO [String]
值,因此我们>>=
再次使用:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= (\rs -> hoo yss >>= (\hs -> ...))
同样,hs :: [String]
最好...
有类型IO [String]
来使整个事情进行类型检查。
现在我们有了rs :: [String]
and hs :: [String]
,我们可以简单地连接它们:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= (\rs -> hoo yss >>= (\hs -> rs ++ hs)) -- !
这是一个类型错误。rs ++ hs :: [String]
, 但上下文需要IO [String]
. 幸运的是,有一个函数可以帮助我们:
return :: a -> IO a
现在它进行类型检查:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= (\rs -> hoo yss >>= (\hs -> return (rs ++ hs)))
由于 Haskell 语法的工作方式(函数体尽可能向右延伸),这里的大多数括号实际上是可选的:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= \rs -> hoo yss >>= \hs -> return (rs ++ hs)
并且通过一些重新格式化,可以使整个事情看起来很有启发性:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= \rs ->
hoo yss >>= \hs ->
return (rs ++ hs)
推荐阅读
- java - 如何解决eclipse自动转换的问题;输入不需要的方法?
- sql-server - SQL Server 上每个数据库的最大连接数
- ubuntu - 已安装 Fluentd 插件,但无法正常工作
- nginx - Certbot Https - Nginx - Docker:端口冲突
- nginx - NGINX RTMP 记录始终开启
- javascript - NodeJS - 正则表达式没有按预期工作。\0 被解释为 \u0000
- python - 你如何解决“MatplotlibDeprecationWarning: pie() 的非一维输入当前是挤压()d”?
- linux - 为什么这不起作用?脚本、目录
- javascript - 为什么 React 在直接改变状态时会更新?
- javascript - jQuery - 用于 src 和背景图像的图像上传器