Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Evaluation of nullary functions in Haskell

Suppose you have a nullary function in haskell, which is used several times in the code. Is it always evaluated only once? I already tested the following code:

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

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

When I call test 10, it writes "Hello" ony once, so it's indicating the result of function is stored after first evaluation. My question is, is it guaranteed? Will I get the same result across different compilers?

Edit The reason I used unsafePerformIO is to check whether sayHello is evaluated more than once. I don't use that in my program. Normally I expect sayHello to have exactly the same result every time its evaluated. But it's a time-consuming operation, so I wanted to know if it could be accessed this way, or if it should be passed as an argument whereever it's needed to ensure it is not evaluated multiple times, ie:

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

According to the answers this should be used.

like image 202
Firielentul Avatar asked Jun 13 '13 03:06

Firielentul


3 Answers

There is no such thing as a nullary function. A function in Haskell has exactly one argument, and always has type ... -> .... sayHello is a value -- an Int -- but not a function. See this article for more.

On guarantees: No, you don't really get any guarantees. The Haskell report specifies that Haskell is non-strict -- so you know what value things will eventually reduce to -- but not any particular evaluation strategy. The evaluation strategy GHC generally uses is lazy evaluation, i.e. non-strict evaluation with sharing, but it doesn't make strong guarantees about that -- the optimizer could shuffle your code around so that things are evaluated more than once.

There are also various exceptions -- for example, foo :: Num a => a is polymorphic, so it probably won't be shared (it's compiled to an actual function). Sometimes a pure value might be evaluated by more than one thread at the same time (that won't happen in this case because unsafePerformIO explicitly uses noDuplicate to avoid it). So when you program, you can generally expect laziness, but if you want any sort of guarantees you'll have to be very careful. The Report itself won't really give you anything on how your program is evaluated.

unsafePerformIO gives you even less in the way of guarantees, of course. There's a reason it's called "unsafe".

like image 105
shachaf Avatar answered Nov 11 '22 09:11

shachaf


Top level no-argument functions like sayHello are called Constant Applicative Forms and are always memoised (atleast in GHC - see http://www.haskell.org/ghc/docs/7.2.1/html/users_guide/profiling.html). You would have to resort to tricks like passing in dummy arguments and turning optimisations off to not share a CAF globally.

Edit: quote from the link above -

Haskell is a lazy language, and certain expressions are only ever evaluated once. For example, if we write: x = nfib 25 then x will only be evaluated once (if at all), and subsequent demands for x will immediately get to see the cached result. The definition x is called a CAF (Constant Applicative Form), because it has no arguments.

like image 37
Anupam Jain Avatar answered Nov 11 '22 11:11

Anupam Jain


If you do want "Hello" printed n times, you need to remove the unsafePermformIO, so the runtime will know it can't optimize away repeated calls to putStr. I'm not clear whether you want to return the list of int, so I've written two versions of test, one of which returns (), one [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
like image 44
ja. Avatar answered Nov 11 '22 11:11

ja.