Haskell中零值函数的计算

2mbi3lxu  于 2023-03-03  发布在  其他
关注(0)|答案(3)|浏览(135)

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

根据答案,应使用此选项。

hmtdttj4

hmtdttj41#

不存在空值函数。Haskell中的函数只有一个参数,并且总是... -> ...类型。sayHello是一个值--一个Int--但不是函数。更多信息请参见this article
关于担保:不,你真的没有得到任何保证。Haskell报告指出Haskell是非严格的--所以你知道事情最终会减少到什么值--但没有任何特定的评估策略。GHC通常使用的评估策略是lazy evaluation,即非严格的共享评估。但是它并没有对此做出强有力的保证--优化器可能会打乱您的代码,从而使代码被多次求值。
也有各种各样的例外--例如,foo :: Num a => a是多态的,所以它可能不会被共享(它被编译成一个实际的函数)。有时一个纯值可能同时被多个线程求值(在本例中不会发生这种情况,因为unsafePerformIO显式使用noDuplicate来避免这种情况)。因此,当您编程时,通常可以“预期”惰性,但是如果你想要任何形式的保证,你必须非常小心。报告本身不会真正给予你任何关于你的程序是如何被评估的。
当然,unsafePerformIO在保证方面给你的甚至更少,它被称为“不安全”是有原因的。

yb3bgrhw

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(常量应用形式),因为它没有参数。

3qpi33ja

3qpi33ja3#

如果你确实想让“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

相关问题