如何在monad中执行每个语句时写一个打印"N的步骤i"的monad?

lob*_*ism 8 monads haskell template-haskell

我甚至不确定任何一种单子都有可能; 它违反了monad法律吗?但似乎在某种构造或其他方面应该是可能的.具体是有什么方法可以写一些我可以写的东西

do
  someOp ()
  someOtherOp ()
  thirdOp ()
Run Code Online (Sandbox Code Playgroud)

它会打印出来

step 1 of 3
step 2 of 3
step 3 of 3
Run Code Online (Sandbox Code Playgroud)

这需要模板Haskell还是monad工作?(如果需要Template Haskell,那么怎么做?)

dan*_*iaz 10

我假设您希望自动显示步骤,而不必使用日志语句将代码洒在上面.

使用monad进行此操作的问题在于它们灵活:在任何时候,其余计算的"形状"可能取决于计算本身期间获得的值.这是明确的类型(>>=),即m a -> (a -> m b) -> m b.

因此,N在运行计算之前,您无法知道固定的总步数.

然而,Haskell提供了另外两个抽象,它们交换monad的一些功能和灵活性,以便有机会事先执行更大量的"静态"分析:applicative functorarrows.

应用仿函数虽然非常有用,但对于您的需求而言可能太"弱".您无法在applicative functor中编写一个函数,当应用于某个值时,该函数会将该值打印到控制台.这在文章中解释为"成语是遗忘的,箭头是一丝不苟的,单子是混杂的",其中包含了每个抽象限制的一些有启发性的例子(在该论文中,应用函子被称为"成语").

箭头在表达能力和静态分析的适应性之间提供了更好的折衷.箭头计算的"形状"在静态管道中是固定的.在计算过程中获得的数据可以影响管道中稍后的效果(例如,您可以打印通过计算中的先前效果获得的值),但不会更改管道的形状或步数.

因此,如果您可以使用Kleisli箭头(monad的箭头)表达您的计算,也许您可​​以编写某种箭头变换器(不是 monad变换器),它增加了自动记录功能.

箭头包提供了一些箭头变压器.我认为StaticArrow可用于自动跟踪总步数.但是你仍然需要编写一些功能来实际发出消息.

编辑:这是一个如何使用箭头计算计算中步数的示例:

module Main where

import Data.Monoid
import Control.Monad
import Control.Applicative
import Control.Arrow
import Control.Arrow.Transformer
import Control.Arrow.Transformer.Static

type SteppedIO a b = StaticArrow ((,) (Sum Int)) (Kleisli IO) a b

step :: (a -> IO b) -> SteppedIO a b
step cmd = wrap (Sum 1, Kleisli cmd)

countSteps :: SteppedIO a b -> Int
countSteps = getSum . fst . unwrap

exec :: SteppedIO a b -> a -> IO b
exec =  runKleisli . snd . unwrap 

program :: SteppedIO () ()
program =
    step (\_ -> putStrLn "What is your name?")  
    >>>
    step (\_ -> getLine)
    >>>
    step (putStrLn . mappend "Hello, ")

main :: IO ()
main = do
    putStrLn $ "Number of steps: " ++ show (countSteps program)
    exec program ()
Run Code Online (Sandbox Code Playgroud)

请注意,步骤3的效果受到步骤2中生成的值的影响.使用应用程序无法完成此操作.

我们确实使用了编译静态信息(,) (Sum Int)所需的应用程序StaticArrow(这里只是步骤数).

在执行步骤时显示这些步骤需要更多的工作.

编辑#2如果我们正在处理一系列命令,其中没有效果取决于前一个效果产生的值,那么我们可以避免使用箭头并仅使用应用仿函数来计算步数:

module Main where

import Data.Monoid
import Control.Applicative
import Data.Functor.Compose

type SteppedIO a = Compose ((,) (Sum Int)) IO a

step :: IO a -> SteppedIO a
step cmd = Compose (Sum 1, cmd)

countSteps :: SteppedIO a -> Int
countSteps = getSum . fst . getCompose

exec :: SteppedIO a -> IO a
exec =  snd . getCompose

program :: SteppedIO () 
program =
    step (putStrLn "aaa") 
    *>  
    step (putStrLn "bbb")
    *>
    step (putStrLn "ccc")

main :: IO ()
main = do
    putStrLn $ "Number of steps: " ++ show (countSteps program)
    exec program 
Run Code Online (Sandbox Code Playgroud)

Data.Functor.Compose来自transformers包裹.

编辑#3以下代码扩展了上Applicative一步计数解决方案,使用pipes包实际发出通知.基于箭头的解决方案可以以类似的方式进行调整.

module Main where

import Data.Monoid
import Control.Applicative
import Control.Monad.State
import Data.Functor.Compose
import Pipes
import Pipes.Lift

type SteppedIO a = Compose ((,) (Sum Int)) (Producer () IO) a

step :: IO a -> SteppedIO a
step cmd = Compose (Sum 1, yield () *> lift cmd)

countSteps :: SteppedIO a -> Int
countSteps = getSum . fst . getCompose

exec :: SteppedIO a -> Producer () IO a
exec =  snd . getCompose

stepper :: MonadIO m => Int -> Consumer () m a
stepper n = evalStateP 0 $ forever $ do 
    await
    lift $ modify succ
    current <- lift get
    liftIO $ putStrLn $ "step " ++ show current ++ " of " ++ show n

program :: SteppedIO () 
program = *** does not change relative to the previous example ***

main :: IO ()
main = runEffect $ exec program >-> stepper (countSteps program)
Run Code Online (Sandbox Code Playgroud)


lef*_*out 5

虽然我认为丹尼尔·迪亚斯"箭头的解决办法是要做到这一点,他完美的方式,有足够的肯定更简单的一个(其中,我刚才看到,他还表示在注释的话)提供的,在你的榜样,没有数据传递之间不同的函数调用.

请记住,由于Haskell很懒惰,函数可以执行许多需要其他语言中的宏的东西.特别是,拥有一系列IO动作没有任何问题.(也绝对安全:由于纯粹,在Haskell中没有办法可以"早点离开"!)然后你可以简单地将这个列表的长度作为总计数,将其与打印语句交错,然后完成.所有的核心语言,不需要TH!

sequenceWithStepCount :: [IO()] -> IO()
sequenceWithStepCount actions = go actions 0
 where nTot = length actions
       go [] _ = putStrLn "Done!"
       go (act:remains) n = do
             putStrLn ("Step "++show n++" of "++show nTot)
             act
             go remains $ succ n
Run Code Online (Sandbox Code Playgroud)

要用得像

do
 sequenceWithStepCount [
     someOp ()
   , someOtherOp ()
   , thirdOp ()
   ]
Run Code Online (Sandbox Code Playgroud)