monad中的执行顺序

时间:2015-04-19 16:19:51

标签: haskell

我正在学习如何使用State monad,我注意到在执行顺序方面有些奇怪的行为。删除涉及使用实际状态的令人分心的位,请说我有以下代码:

import Control.Monad
import Control.Monad.State
import Debug.Trace

mainAction :: State Int ()
mainAction = do
    traceM "Starting the main action"
    forM [0..2] (\i -> do
        traceM $ "i is " ++ show i
        forM [0..2] (\j -> do
            traceM $ "j is " ++ show j
            someSubaction i j
        )
    )

在ghci中运行runState mainAction 1会产生以下输出:

j is 2          
j is 1          
j is 0          
i is 2          
j is 2          
j is 1          
j is 0          
i is 1         
j is 2          
j is 1          
j is 0          
i is 0          
Outside for loop

这似乎是执行可能预期的相反顺序。我认为这可能是forM的一个怪癖,并尝试使用sequence,它特别声明它从左到右依次运行计算:

mainAction :: State Int ()
mainAction = do
    traceM "Outside for loop"
    sequence $ map handleI [0..2]
    return ()
    where
    handleI i = do
        traceM $ "i is " ++ show i
        sequence $ map (handleJ i) [0..2]
    handleJ i j = do
        traceM $ "j is " ++ show j
        someSubaction i j

但是,sequence版本会产生相同的输出。根据这里发生的执行顺序,实际的逻辑是什么?

2 个答案:

答案 0 :(得分:21)

Haskell是 lazy ,这意味着事情不会立即执行。只要需要结果,就会执行任务 - 但不久就会执行。如果不需要代码,有时根本不会执行代码。

如果你在一个纯函数中粘贴了一堆trace个调用,你会发现这种懒惰正在发生。首先需要执行的第一件事就是首先看到的trace来电。

当某事说“"计算是从左到右"这意味着结果与从左到右计算的结果相同。实际发生的事情可能会有很大不同。

事实上为什么在纯函数中进行I / O是一个坏主意。正如你所发现的那样,你会感到很奇怪"结果是因为执行顺序几乎可以产生正确的结果。

为什么这是个好主意?当语言没有强制执行特定的执行顺序时(例如传统的"从上到下"在命令式语言中看到的顺序),编译器可以自由地进行大量的优化,例如不完全执行一些代码,因为它的结果并不需要。

我建议你过多考虑Haskell中的执行顺序。应该没有理由。把它留给编译器。而是考虑一下你想要的值。该函数是否给出正确的值?然后它可以工作,无论它执行的是什么顺序。

答案 1 :(得分:7)

  

我认为这可能是forM的一个怪癖,并尝试使用sequence,它特别指出它从左到右依次运行其计算:[...]

你需要学会做出如下的,狡猾的区别:

  1. 评估
  2. 的顺序
  3. 效果的顺序(a.k.a。"行动")
  4. forMsequence和类似函数承诺的是,效果将从左到右排序。因此,例如,以下内容保证以与字符串中出现的顺序相同的顺序打印字符:

    putStrLn :: String -> IO ()
    putStrLn str = forM_ str putChar >> putChar '\n'
    

    但这并不意味着表达式按照从左到右的顺序进行评估。程序必须评估足够的表达式以确定下一个操作是什么,但这通常不需要评估早期操作中涉及的每个表达式中的所有内容。

    您的示例使用State monad,它是纯代码的最低点,因此突出了订单问题。在这种情况下,forM的遍历函数唯一承诺的是映射到列表元素的动作中的get将看到put s对左边元素的影响在列表中。