假设你在Haskell中有一个空值函数,它在代码中被使用了好几次。它总是只被求值一次吗?我已经测试了下面的代码:
sayHello :: Int
sayHello = unsafePerformIO $ do
putStr "Hello"
return 42
test :: Int -> [Int]
test 0 = []
test n = (sayHello:(test (n-1)))
当我调用test 10
的时候,它只写了一次“Hello”,所以它表示函数的结果在第一次求值后被存储。我的问题是,它能保证吗?我能在不同的编译器上得到相同的结果吗?
编辑我使用unsafePerformIO
的原因是检查sayHello
是否被多次求值。我不在我的程序中使用它。通常我希望sayHello
每次求值时都有完全相同的结果。但是这是一个耗时的操作,所以我想知道它是否可以通过这种方式访问,或者是否应将其作为参数传递到需要它的任何地方,以确保不会对其进行多次求值,即:
test _ 0 = []
test s n = (s:(test (n-1)))
...
test sayHello 10
根据答案,应使用此选项。
3条答案
按热度按时间hmtdttj41#
不存在空值函数。Haskell中的函数只有一个参数,并且总是
... -> ...
类型。sayHello
是一个值--一个Int
--但不是函数。更多信息请参见this article。关于担保:不,你真的没有得到任何保证。Haskell报告指出Haskell是非严格的--所以你知道事情最终会减少到什么值--但没有任何特定的评估策略。GHC通常使用的评估策略是lazy evaluation,即非严格的共享评估。但是它并没有对此做出强有力的保证--优化器可能会打乱您的代码,从而使代码被多次求值。
也有各种各样的例外--例如,
foo :: Num a => a
是多态的,所以它可能不会被共享(它被编译成一个实际的函数)。有时一个纯值可能同时被多个线程求值(在本例中不会发生这种情况,因为unsafePerformIO
显式使用noDuplicate
来避免这种情况)。因此,当您编程时,通常可以“预期”惰性,但是如果你想要任何形式的保证,你必须非常小心。报告本身不会真正给予你任何关于你的程序是如何被评估的。当然,
unsafePerformIO
在保证方面给你的甚至更少,它被称为“不安全”是有原因的。yb3bgrhw2#
顶级的无参数函数如
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(常量应用形式),因为它没有参数。3qpi33ja3#
如果你确实想让“Hello”打印n次,你需要删除
unsafePermformIO
,这样运行时就知道它不能优化掉对putStr
的重复调用。我不清楚你是否想返回int的列表,所以我写了两个版本的test,一个返回(),一个返回[Int]。