Haskell中的惯用状态循环

use*_*183 8 haskell

在Haskell中是否存在表达以下代码的惯用方法?

main :: IO ()
main = loop initState1 initState2 initState3

loop :: State1 -> State2 -> State3 -> IO ()
loop s1 s2 s3 = do
  s1' <- runService1 s1
  s2' <- runService2 s2
  s3' <- runService3 s3
  loop s1' s2' s3'
Run Code Online (Sandbox Code Playgroud)

这段代码非常冗长,所以我可能会做一些奇怪的事情.

Gur*_*las 9

main = fix (zipWithM ($) >=>)
  [runService1, runService2, runService3]
  [initState1 , initState2 , initState3 ]
Run Code Online (Sandbox Code Playgroud)

比较fix . (>>=) :: IO a -> IO b,这是forever.

编辑:这仅在State1= State2=时有效State3.如果没有,data-fix允许:

main = fix (traverse unFix >=>)
  [ana runService1 initState1, ana runService2 initState2, ana runService3 initState3]
Run Code Online (Sandbox Code Playgroud)


lef*_*out 5

我的做法是,您可能希望以适当的状态monad来推动该状态。该lens库使访问它变得容易:

{-# LANGUAGE TemplateHaskell    #-}
import Control.Lens.TH
import Control.Monad.Trans.State

data AllState = AllState { _s? :: State0, _s? :: State1, _s? :: State2 }
makeLenses ''AllState

loop :: StateT AllState IO ()
loop = do
   s? <~ runService0 <$> use s?
   s? <~ runService1 <$> use s?
   s? <~ runService2 <$> use s?
   loop

main = evalStateT loop $ AllState initState0 initState1 initState2
Run Code Online (Sandbox Code Playgroud)

这样一来,您不会在原始代码上花太多钱,但是如果您还为runService动作指定合适的state-monad类型,它将变得更加方便:

runService0 :: StateT State0 IO ()
runService1 :: StateT State1 IO ()
runService2 :: StateT State2 IO ()
Run Code Online (Sandbox Code Playgroud)

...然后您可以简单地使用以下zoom机制:

loop :: StateT AllState IO ()
loop = do
   zoom s? runService0
   zoom s? runService1
   zoom s? runService2
   loop
Run Code Online (Sandbox Code Playgroud)

或古尔肯拉斯(Gurkenglas)建议

loop = forever $ do
   zoom s? runService0
   zoom s? runService1
   zoom s? runService2
Run Code Online (Sandbox Code Playgroud)