haskell - 用于进度跟踪的 Monad 转换器

标签 haskell functional-programming monads coroutine monad-transformers

我正在寻找可用于跟踪程序进度的 monad 转换器。要解释如何使用它,请考虑以下代码:

procedure :: ProgressT IO ()
procedure = task "Print some lines" 3 $ do
  liftIO $ putStrLn "line1"
  step
  task "Print a complicated line" 2 $ do
    liftIO $ putStr "li"
    step
    liftIO $ putStrLn "ne2"
  step
  liftIO $ putStrLn "line3"

-- Wraps an action in a task
task :: Monad m
     => String        -- Name of task
     -> Int           -- Number of steps to complete task
     -> ProgressT m a -- Action performing the task
     -> ProgressT m a

-- Marks one step of the current task as completed
step :: Monad m => ProgressT m ()

我意识到 step由于一元法则,必须明确存在,并且 task由于程序确定性/停机问题,必须有一个明确的步数参数。

如我所见,上述 monad 可以通过以下两种方式之一实现:
  • 通过将返回当前任务名称/步骤索引堆栈的函数,以及过程中在它停止的点处的继续。在返回的延续上重复调用此函数将完成过程的执行。
  • 通过一个函数,该函数描述了当一个任务步骤完成时要做什么。该过程将不受控制地运行,直到完成,通过提供的操作“通知”环境有关更改。

  • 对于解决方案 (1),我查看了 Control.Monad.CoroutineYield悬浮仿函数。对于解决方案 (2),我不知道有任何可用的单子(monad)转换器有用。

    我正在寻找的解决方案不应该有太多的性能开销,并允许尽可能多地控制过程(例如,不需要 IO 访问或其他东西)。

    这些解决方案中的一个听起来可行吗,还是已经有其他解决方案可以解决这个问题?这个问题是否已经用我找不到的单子(monad)变压器解决了?

    编辑:目标不是检查是否所有步骤都已执行。目标是能够在进程运行时“监控”进程,以便知道它已经完成了多少。

    最佳答案

    这是我对这个问题的悲观解决方案。它使用 Coroutine s 暂停每一步的计算,这允许用户执行任意计算以报告一些进度。

    编辑:该解决方案的完整实现可以在 here 找到.

    这个解决方案可以改进吗?

    首先,它是如何使用的:

    -- The procedure that we want to run.
    procedure :: ProgressT IO ()
    procedure = task "Print some lines" 3 $ do
      liftIO $ putStrLn "--> line 1"
      step
      task "Print a set of lines" 2 $ do
        liftIO $ putStrLn "--> line 2.1"
        step
        liftIO $ putStrLn "--> line 2.2"
      step
      liftIO $ putStrLn "--> line 3"
    
    main :: IO ()
    main = runConsole procedure
    
    -- A "progress reporter" that simply prints the task stack on each step
    -- Note that the monad used for reporting, and the monad used in the procedure,
    -- can be different.
    runConsole :: ProgressT IO a -> IO a
    runConsole proc = do
      result <- runProgress proc
      case result of
        -- We stopped at a step:
        Left (cont, stack) -> do
          print stack     -- Print the stack
          runConsole cont -- Continue the procedure
        -- We are done with the computation:
        Right a -> return a
    

    上述程序输出:
    --> line 1
    [Print some lines (1/3)]
    --> line 2.1
    [Print a set of lines (1/2),Print some lines (1/3)]
    --> line 2.2
    [Print a set of lines (2/2),Print some lines (1/3)]
    [Print some lines (2/3)]
    --> line 3
    [Print some lines (3/3)]
    

    实际实现(见 this 的注释版本):
    type Progress l = ProgressT l Identity
    
    runProgress :: Progress l a
                   -> Either (Progress l a, TaskStack l) a
    runProgress = runIdentity . runProgressT
    
    newtype ProgressT l m a =
      ProgressT
      {
        procedure ::
           Coroutine
           (Yield (TaskStack l))
           (StateT (TaskStack l) m) a
      }
    
    instance MonadTrans (ProgressT l) where
      lift = ProgressT . lift . lift
    
    instance Monad m => Monad (ProgressT l m) where
      return = ProgressT . return
      p >>= f = ProgressT (procedure p >>= procedure . f)
    
    instance MonadIO m => MonadIO (ProgressT l m) where
      liftIO = lift . liftIO
    
    runProgressT :: Monad m
                    => ProgressT l m a
                    -> m (Either (ProgressT l m a, TaskStack l) a)
    runProgressT action = do
      result <- evalStateT (resume . procedure $ action) []
      return $ case result of
        Left (Yield stack cont) -> Left (ProgressT cont, stack)
        Right a -> Right a
    
    type TaskStack l = [Task l]
    
    data Task l =
      Task
      { taskLabel :: l
      , taskTotalSteps :: Word
      , taskStep :: Word
      } deriving (Show, Eq)
    
    task :: Monad m
            => l
            -> Word
            -> ProgressT l m a
            -> ProgressT l m a
    task label steps action = ProgressT $ do
      -- Add the task to the task stack
      lift . modify $ pushTask newTask
    
      -- Perform the procedure for the task
      result <- procedure action
    
      -- Insert an implicit step at the end of the task
      procedure step
    
      -- The task is completed, and is removed
      lift . modify $ popTask
    
      return result
      where
        newTask = Task label steps 0
        pushTask = (:)
        popTask = tail
    
    step :: Monad m => ProgressT l m ()
    step = ProgressT $ do
      (current : tasks) <- lift get
      let currentStep = taskStep current
          nextStep = currentStep + 1
          updatedTask = current { taskStep = nextStep }
          updatedTasks = updatedTask : tasks
      when (currentStep > taskTotalSteps current) $
        fail "The task has already completed"
      yield updatedTasks
      lift . put $ updatedTasks
    

    关于haskell - 用于进度跟踪的 Monad 转换器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8556746/

    相关文章:

    haskell - Applicative (Functor) 类型类的简单概括;构造函数的模式匹配

    haskell - 刚性类型变量麻烦/可疑

    Haskell 自定义数据类型和表示

    haskell - 它不是单子(monad),但它是什么?

    haskell - `join` 和 `fmap join` 在 Haskell 中是否相等(从范畴论的角度来看)?

    haskell - 与fundeps捆绑约束

    java - 使用流API对列表数据进行多重操作

    clojure - 我的 Clojure 排列实现有什么问题

    c# - 参数数量未知的 Func<>

    scala - 除了 Option 之外,标准的 Scala monad 是什么?