假设你在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
根据答案,应该使用它。
答案 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