我可以使用 StateT/MaybeT/forever 来消除此 IO 操作中的显式递归吗?

问题描述 投票:0回答:1

我有一个这样的程序,

start :: [Q] -> R -> IO R
start qs = fix $ \recurse r -> do
  q <- select qs
  (r', exit) <- askQ q r
  (if exit
    then return
    else recurse) r'

需要一个

Q
问题列表、一个
R
eport,并在
R
monad 中返回一个新的
IO
eport,因为
select
需要它随机选择一个问题(也因为
askQ 
将等待用户键盘输入);但是,用户在执行
exit
时没有选择
askQ
start
会递归调用自身。 (
fix $ \recurse
是编写递归 lambda 的技巧。

上面的代码听起来很像一些东西:

但无法真正判断是否可以使用这些抽象中的任何一个或多个以更惯用的方式编写上述代码,最重要的是避免显式递归。

haskell functional-programming state-monad io-monad
1个回答
0
投票
嗯,结果会是这样的:

type M = MaybeT (StateT R IO) start :: [Q] -> M () start qs = void $ many (select qs >>= askQ)
这假设 

select

askQ
 将被重写以在 
M
 monad 而不是 
IO
 中运行:

select :: [Q] -> M Q askQ :: Q -> M ()
结果非常...简洁。

一个独立的例子,供后代使用...

import Data.Coerce import Control.Applicative import Control.Monad import Control.Monad.State import Control.Monad.Trans.Maybe import System.Random newtype Q = Q String deriving (Show) newtype R = R [String] deriving (Show) type M = MaybeT (StateT R IO) runM :: M a -> IO (Maybe a, R) runM = flip runStateT (R []) . runMaybeT select :: [Q] -> M Q select qs = (qs !!) <$> randomRIO (0, length qs - 1) askQ :: Q -> M () askQ (Q q) = do liftIO $ putStrLn q r <- liftIO getLine if r == "exit" then mzero else modify (coerce (r:)) start :: [Q] -> M () start qs = void $ many (select qs >>= askQ) main :: IO () main = do result <- runM $ start [ Q "Is this idiomatic?" , Q "Seriously, what's wrong with recursion?" ] print result
似乎有效:

λ> main Seriously, what's wrong with recursion? nothing Is this idiomatic? yes Is this idiomatic? exit (Just (),R ["yes","nothing"])
    
© www.soinside.com 2019 - 2024. All rights reserved.