在IO monad中进行递归

Eri*_*ten 4 recursion haskell io-monad

我一直试图弄清楚如何在IO monad中进行递归.我熟悉使用纯函数进行递归,但是无法将这些知识传递给IO monad.

使用纯函数递归
我很喜欢用纯函数进行递归,例如foo下面的函数.

foo (x:y:ys) = foo' x y ++ foo ys
Run Code Online (Sandbox Code Playgroud)

一个带有IO [String]输出
的函数我做了一个像goo下面这样的函数,它可以满足我的需求并具有IO输出.

goo :: String -> String -> IO [String]
goo xs ys = goo' xs ys 
Run Code Online (Sandbox Code Playgroud)

试图在IO monad中获取递归
当我尝试在IO monad中进行递归时(例如,"主"函数)我不能.我抬头一看liftM,replicateM和撤消最IO <-操作或功能.我想要一个IO monad hoo或者hoo'(为随后的乱码道歉).

hoo :: [String] -> IO [String]
hoo (xs:ys:yss) = do
                  let rs = goo xs ys ++ hoo yss
                  return rs
Run Code Online (Sandbox Code Playgroud)

要么

hoo' :: [String] -> IO [String]
hoo' (xs:ys:yss) = do
                  rs <- goo xs ys 
                  let gs = rs ++ hoo' yss
                  return gs
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,如果你想知道我的项目是什么,我正在编写一个遗传算法程序从头开始.我的goo函数需要两个父母并繁殖两个后代,它们作为IO返回,因为goo使用随机数生成器.我需要做的是使用递归hoo函数goo从20个父母的名单中培育20个后代.我的想法是将前两个父母列入名单,培育两个后代,接下来的两个父母列表,培育另一对后代,依此类推.)

mel*_*ene 5

如果您发现do符号令人困惑,我的建议是根本不使用它.你可以做你需要的一切>>=.只是假装它的类型是

(>>=) :: IO a -> (a -> IO b) -> IO b
Run Code Online (Sandbox Code Playgroud)

那就是说,让我们来看看你的代码.

letdo块中给出了某个值的名称.这与它在外面做的是一样的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
Run Code Online (Sandbox Code Playgroud)

如上所述,let只需将名称绑定到值,所以我们在这里并不需要它:

hoo :: [String] -> IO [String]
hoo (xs:ys:yss) = do
    rs <- goo xs ys
    hs <- hoo yss
    return (rs ++ hs)
Run Code Online (Sandbox Code Playgroud)

或者,没有do符号,<-我们按如下方式进行.

(>>=) :: IO a -> (a -> IO b) -> IO b
Run Code Online (Sandbox Code Playgroud)

>>=接受一个IO值和一个回调函数,它在"unwrapped"值(a)上运行函数.这意味着在函数内部,只要整个事物的结果IO b再次出现(对于某些任意类型b),我们就可以获得对值的本地访问.

hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
    goo xs ys -- :: IO [String]
    ...
Run Code Online (Sandbox Code Playgroud)

我们有一个IO [String],我们需要做一些事情[String],所以我们使用>>=:

hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
    goo xs ys >>= (\rs -> ...)
Run Code Online (Sandbox Code Playgroud)

如果你看一下>>=类型签名,那么这个角色a是由[String]here(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 -> ...))
Run Code Online (Sandbox Code Playgroud)

再次,hs :: [String]并且...更好地使用类型IO [String]来完成整个事情.

现在我们已经rs :: [String]hs :: [String],我们可以简单地连接它们:

hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
    goo xs ys >>= (\rs -> hoo yss >>= (\hs -> rs ++ hs))  -- !
Run Code Online (Sandbox Code Playgroud)

这是一个类型错误.rs ++ hs :: [String],但背景需要IO [String].幸运的是,有一个功能可以帮助我们:

return :: a -> IO a
Run Code Online (Sandbox Code Playgroud)

现在它是typechecks:

hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
    goo xs ys >>= (\rs -> hoo yss >>= (\hs -> return (rs ++ hs)))
Run Code Online (Sandbox Code Playgroud)

由于Haskell语法的工作方式(函数体尽可能向右扩展),这里的大多数parens实际上是可选的:

hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
    goo xs ys >>= \rs -> hoo yss >>= \hs -> return (rs ++ hs)
Run Code Online (Sandbox Code Playgroud)

通过一些重新格式化,整个事情可以看起来很有启发性:

hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
    goo xs ys >>= \rs ->
    hoo yss   >>= \hs ->
    return (rs ++ hs)
Run Code Online (Sandbox Code Playgroud)