【问题标题】:Idiomatic stateful loop in HaskellHaskell 中惯用的有状态循环
【发布时间】:2018-07-26 00:09:41
【问题描述】:

在 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'

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

【问题讨论】:

  • 查看其他答案,我认为问题中的代码是我宁愿阅读的。这基本上很明显,如果你突然意识到runService2 需要依赖s1',你不需要做太多改变。

标签: haskell


【解决方案1】:
main = fix (zipWithM ($) >=>)
  [runService1, runService2, runService3]
  [initState1 , initState2 , initState3 ]

比较fix . (&gt;&gt;=) :: IO a -&gt; IO b,即forever

编辑:这仅适用于State1 = State2 = State3。如果没有,data-fix 允许:

main = fix (traverse unFix >=>)
  [ana runService1 initState1, ana runService2 initState2, ana runService3 initState3]

【讨论】:

    【解决方案2】:

    我练习,您可能希望将该状态推送到合适的 state 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
    

    因此,与原始代码相比,这不会给您带来太多好处,但如果您还为 runService 操作提供合适的 state-monad 类型,它会变得更加方便:

    runService0 :: StateT State0 IO ()
    runService1 :: StateT State1 IO ()
    runService2 :: StateT State2 IO ()
    

    ...那么您可以简单地使用zoom 机制:

    loop :: StateT AllState IO ()
    loop = do
       zoom s₀ runService0
       zoom s₁ runService1
       zoom s₂ runService2
       loop
    

    或者像古尔肯格拉斯所说的那样

    loop = forever $ do
       zoom s₀ runService0
       zoom s₁ runService1
       zoom s₂ runService2
    

    【讨论】:

    • 非常感谢,这也应该是答案。
    • 您可以将最后一个loop 中的显式递归替换为foreverdo 前面。
    【解决方案3】:

    这个解决方案不是更简洁,而是解决了原始公式的一个不合理之处:每个过程的“展开”与所有过程的“压缩在一起”混合在一起。如果我们可以独立定义每个流程,然后在我们认为合适的时候将它们组合起来,那就太好了。

    我们需要以下辅助类型:

    newtype Iter = Iter (IO Iter)
    
    unfoldIter :: (s -> IO s) -> s -> Iter
    unfoldIter f s = Iter (unfoldIter f <$> f s)
    
    runIter :: Iter -> IO ()
    runIter (Iter action) = action >>= runIter
    
    doNothingIter :: Iter
    doNothingIter = unfoldIter return ()
    
    zipIter :: Iter -> Iter -> Iter
    zipIter (Iter action1) (Iter action2) =
        Iter (zipIter <$> action1 <*> action2)
    
    instance Monoid Iter where
        mempty = doNothingIter
        mappend = zipIter
    

    那么loop就变成了:

    loop :: State1 -> State2 -> State3 -> IO ()
    loop s1 s2 s3 = 
        runIter $ unfoldIter runService1 s1
               <> unfoldIter runService2 s2
               <> unfoldIter runService3 s3
    

    如果我们不想定义自己的辅助类型,我们可以使用提供“压缩”操作的流式库,例如 streaming 所做的。

    【讨论】:

      猜你喜欢
      • 2013-08-11
      • 2015-06-01
      • 1970-01-01
      • 2011-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多