gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 14:20:52 26 4
gpt4 key购买 nike

我正在寻找可用于跟踪程序进度的 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/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com