2013-06-13 49 views
7

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

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

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

当我打电话测试10,它写道:“你好” ONY一次,所以它表示第一次评估之后,存储功能的结果。我的问题是,它有保证吗?我会在不同的编译器中得到相同的结果吗?

编辑 我使用unsafePerformIO的原因是为了检查sayHello是否被多次评估。我不会在我的程序中使用它。通常我希望sayHello在每次评估时都有完全相同的结果。但它是一个耗时的操作,所以我想知道这是否可以访问这种方式,或者如果它应该徘徊无论它以确保它不会多次评估真实需要一个参数,即传递:

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

根据这个答案应该使用。

回答

17

没有这样的东西作为无功功能。 Haskell中的一个函数只有一个参数,并且总是有类型... -> ...sayHello是一个值 - Int - 但不是函数。有关更多信息,请参阅this article

保证:不,你没有得到任何保证。 Haskell报告指出,Haskell不是严格的 - 所以你知道事情最终会减少什么价值 - 但不是任何特定的评估策略。 GHC通常使用的评估策略是lazy evaluation,即对共享进行非严格评估,但并未对此做出有力保证 - 优化程序可能会对代码进行随机混排,以便对其进行多次评估。

也有各种例外 - 例如,foo :: Num a => a是多态的,所以它可能不会被共享(它被编译为实际函数)。有时候,一个纯粹的值可能会被多个线程同时评估(在这种情况下不会发生这种情况,因为unsafePerformIO明确使用noDuplicate来避免它)。所以,当你编程时,你通常可以预期懒惰,但如果你想要任何形式的保证,你必须非常小心。报告本身不会给你任何东西如何你的程序被评估。

unsafePerformIO当然,在担保方式上给予您更少。有一个原因叫做“不安全”。

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),因为 它没有参数。

1

如果你想“你好”印刷N次,你需要删除unsafePermformIO,所以运行时将知道它不能优化掉重复调用putStr。我不清楚你是否想返回int列表,所以我写了两个版本的测试,其中一个返回(),一个[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