7

假设你在 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

根据答案,应该使用它。

4

3 回答 3

17

没有零函数之类的东西。Haskell 中的函数只有一个参数,并且总是有 type ... -> ...sayHello是一个值——一个Int——但不是一个函数。有关更多信息,请参阅本文

关于保证:不,你并没有真正得到任何保证。Haskell 报告指出 Haskell 是非严格的——所以你知道事情最终会降低到什么价值——但不是任何特定的评估策略。GHC 通常使用的评估策略是惰性评估,即具有共享的非严格评估,但它并没有对此做出强有力的保证——优化器可能会打乱你的代码,以便对事物进行多次评估。

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

unsafePerformIO当然,给你的保证更少。它被称为“不安全”是有原因的。

于 2013-06-13T03:37:51.107 回答
5

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

编辑:引用上面的链接 -

Haskell 是一种惰性语言,某些表达式只计算一次。例如,如果我们写: x = nfib 25thenx只会被评估一次(如果有的话),随后的 for 请求x将立即看到缓存的结果。该定义x称为 CAF(常量应用形式),因为它没有参数。

于 2013-06-13T04:31:05.730 回答
1

如果您确实希望打印 n 次“Hello”,则需要删除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
于 2013-06-13T05:48:38.760 回答