Haskell中的Nullary函数的评估

时间:2013-06-13 03:13:26

标签: haskell functional-programming lazy-evaluation

假设你在haskell中有一个nullary函数,它在代码中多次使用。它总是只评估一次吗?我已经测试了以下代码:

sayHello :: Int
sayHello = unsafePerformIO $ do
    putStr "Hello"
    return 42

test :: Int -> [Int]
test 0 = []
test n = (sayHello:(test (n-1)))

当我调用测试10时,它会将“Hello”写入一次,因此它表示在第一次评估后存储了函数的结果。我的问题是,它有保证吗?我会在不同的编译器中得到相同的结果吗?

修改 我使用unsafePerformIO的原因是检查sayHello是否被多次评估。我不在我的程序中使用它。通常我希望sayHello每次评估都会得到完全相同的结果。但这是一个耗时的操作,所以我想知道它是否可以通过这种方式访问​​,或者是否应该作为参数传递,以确保它不被多次评估,即:

test _ 0 = []
test s n = (s:(test (n-1)))
...
test sayHello 10

根据答案,应该使用它。

3 个答案:

答案 0 :(得分:17)

没有必然的功能。 Haskell中的函数只有一个参数,并且始终具有类型... -> ...sayHello是一个值 - Int - 但不是函数。有关详情,请参阅this article

保证:不,你没有得到任何保证。 Haskell报告指出Haskell是非严格的 - 所以你知道最终会减少什么价值 - 但不是任何特定的评估策略。 GHC通常使用的评估策略是lazy evaluation,即通过共享进行非严格评估,但它并没有对此做出强有力的保证 - 优化器可以对您的代码进行混洗,以便对事物进行多次评估。 / p>

还有各种例外 - 例如,foo :: Num a => a是多态的,因此它可能不会被共享(它被编译为实际函数)。有时,纯值可能会被多个线程同时评估(在这种情况下不会发生,因为unsafePerformIO明确使用noDuplicate来避免它。因此,当你编程时,你通常可以期待懒惰,但如果你想要任何形式的保证,你必须非常小心。报告本身并不会真正为您提供有关如何评估您的计划的信息。

当然,

unsafePerformIO会给你更少的保障。有一个原因,它被称为“不安全”。

答案 1 :(得分:5)

sayHello这样的顶级无参数函数被称为常量应用形式,并且总是被记忆(至少在GHC中 - 见http://www.haskell.org/ghc/docs/7.2.1/html/users_guide/profiling.html)。您将不得不求助于传递伪参数和将优化转移到而不是全局共享CAF的技巧。

编辑:引用上面的链接 -

  

Haskell是一种懒惰的语言,某些表达式只是永远存在   评估一次。例如,如果我们写:   x = nfib 25然后x只会被评估一次(如果有的话),和   随后对x的要求将立即查看缓存的结果。   定义x被称为CAF(Constant Applicative Form),因为   它没有争论。

答案 2 :(得分:1)

如果您确实希望“Hello”打印n次,则需要删除unsafePermformIO,这样运行时就会知道它无法优化掉putStr的重复调用。我不清楚你是否要返回int列表,所以我写了两个版本的test,其中一个返回(),一个[Int]。

sayHello2 :: IO Int
sayHello2 = do
    putStr "Hello"
    return 42

test2 :: Int -> IO ()
test2 0 = return ()
test2  n = do
  sayHello2
  test2 (n-1)

test3 :: Int -> IO [Int]
test3 0 = return []
test3 n = do
  r <- sayHello2
  l <- test3 (n-1)
  return $ r:l