Laziness

以为自己摸索haskell这么久了,以为对laziness了然于胸,但是今天还是栽了一个大跟头。我本来写了一小段程序:

time :: IO t -> IO t
time a = do
    start <- getCPUTime
    v <- a
    end   <- getCPUTime
    let diff = (fromIntegral (end – start)) / (10^12)
    printf "Computation time: %0.3f secn" (diff :: Double)
    return v

fermat_prime_test = { — blablabla… –}

main = do
     v <- time $ liftM (fermat_prime_test 1105) (randomList 100000 :: IO [Int])
     putStrLn $ show v

然后我想测试一下执行的时间。结果,一刷出来时间为0。我就奇怪了,我明明都已经感觉到至少六七秒钟,怎么就测不出来呢。然后犯傻就开始了,猜测getCPUTime是否有问题,然后改成getClockTime,一刷,还是0。TMD,我郁闷了,想也想不通,按照Monad的本意,这里的a,b的肯定是保持sequatial的,那中间的这段运算过程肯定也是花了时间,那时间跑到哪里去了呢?

其实到这里如果我在这个问题上继续想下去的话,答案立马就出来了。但是我就是懒,于是google去了,狗了半天也没有个结论。于是又猜测是否这段timing代码有问题吧,于是在wiki上找了一段timeing抄来用了。但是结果是,测例子能测出时间差来,但是测试我的这段程序就是不行。

实际我错过了一个小细节,犯了巨大的错误。原先的代码会输出如下的东西 :
Computation time: 0.000 sec
True
关键问题在这两行的顺序上面:先输出了统计时间,再输出了结算结果。这么想下去,那问题就很清楚了,在做timing的时候根本没有去计算我的子程序,实际上根本什么也没有执行。而在最后一行输出v的时候才对我的小程序进行了计算,这也就是lazy的本质,当我们需要直到v具体是什么的时候,计算才执行。

再回头一想,我不是用了liftM了吗,Monad的性质应当保证v先被计算出来,不是吗?No,确实不是这样。Monad的性质保证的Computation当中的side effect被sequetial,而pure的计算依然是lazy的。这里虽然我用了liftM,但是fermat_prime_test当中根本没有涉及IOMonad所要catch的side effect。这也就导致了v所代表的一个Pure computation直到打印到屏幕的时候才被计算。

那接下来的修改变法就很直白了:

main = do
     l <- (randomList 1000000 :: IO [Int])
     v <- return $ (fermat_prime_test 1105) l
     time $ putStrLn (show v)
with output:
True
Computation time: 9.797 sec

此条目发表在Uncategorized分类目录。将固定链接加入收藏夹。

2 Responses to Laziness

  1. yuanzu说道:

    相信没几个人能看懂

  2. 佳森说道:

    确实只有接触了Haskell的话才能了解

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

您正在使用您的 WordPress.com 账号评论。 登出 /  更改 )

Google photo

您正在使用您的 Google 账号评论。 登出 /  更改 )

Twitter picture

您正在使用您的 Twitter 账号评论。 登出 /  更改 )

Facebook photo

您正在使用您的 Facebook 账号评论。 登出 /  更改 )

Connecting to %s