Does a function in Haskell always evaluate its return value?
I'm trying to better understand Haskell's laziness, such as when it evaluates an argument to a function.
From this source:
But when a call to const
is evaluated (that's the situation we are interested in, here, after all), its return value is evaluated too ... This is a good general principle: a function obviously is strict in its return value, because when a function application needs to be evaluated, it needs to evaluate, in the body of the function, what gets returned. Starting from there, you can know what must be evaluated by looking at what the return value depends on invariably. Your function will be strict in these arguments, and lazy in the others.
So a function in Haskell always evaluates its own return value? If I have:
foo :: Num a => [a] -> [a]
foo [] = []
foo (_:xs) = map (* 2) xs
head (foo [1..]) -- = 4
According to the above paragraph, map (* 2) xs
, must be evaluated. Intuitively, I would think that means applying the map
to the entire list- resulting in an infinite loop. But, I can successfully take the head of the result. I know that :
is lazy in Haskell, so does this mean that evaluating map (* 2) xs
just means constructing something else that isn't fully evaluated yet?
What does it mean to evaluate a function applied to an infinite list? If the return value of a function is always evaluated when the function is evaluated, can a function ever actually return a thunk?
Edit:
bar x y = x
var = bar (product [1..]) 1
This code doesn't hang. When I create var
, does it not evaluate its body? Or does it set bar
to product [1..]
and not evaluate that? If the latter, bar
is not returning its body in WHNF, right, so did it really 'evaluate' x? How could bar
be strict in x
if it doesn't hang on computing product [1..]
?
First of all, Haskell does not specify when evaluation happens so the question can only be given a definite answer for specific implementations.
The following is true for all non-parallel implementations that I know of, like ghc, hbc, nhc, hugs, etc (all G-machine based, btw).
BTW, something to remember is that when you hear "evaluate" for Haskell it normally means "evaluate to WHNF".
Unlike strict languages you have to distinguish between two "callers" of a function, the first is where the call occurs lexically, and the second is where the value is demanded. For a strict language these two always coincide, but not for a lazy language. Let's take your example and complicate it a little:
foo [] = []
foo (_:xs) = map (* 2) xs
bar x = (foo [1..], x)
main = print (head (fst (bar 42)))
The foo
function occurs in bar
. Evaluating bar
will return a pair, and the first component of the pair is a thunk corresponding to foo [1..]
. So bar
is what would be the caller in a strict language, but in the case of a lazy language it doesn't call foo
at all, instead it just builds the closure.
Now, in the main
function we actually need the value of head (fst (bar 42))
since we have to print it. So the head
function will actually be called. The head
function is defined by pattern matching, so it needs the value of the argument. So fst
is called. It too is defined by pattern matching and needs its argument so bar
is called, and bar
will return a pair, and fst
will evaluate and return its first component. And now finally foo
is "called"; and by called I mean that the thunk is evaluated (entered as it's sometimes called in TIM terminology), because the value is needed. The only reason the actual code for foo
is called is that we want a value. So foo
had better return a value (ie, a WHNF). The foo
function will evaluate its argument and end up in the second branch. Here it will tail call into the code for map
. The map
function is defined by pattern match and it will evaluate its argument, which is a cons. So map will return the following {(*2) y} : {map (*2) ys}
, where I have used {}
to indicate a closure being built. So as you can see map
just returns a cons cell with the head being a closure and the tail being a closure.
To understand the operational semantics of Haskell better I suggest you look at some paper describing how to translate Haskell to some abstract machine, like the G-machine.
I always found that the term "evaluate," which I had learned in other contexts (eg, Scheme programming), always got me all confused when I tried to apply it to Haskell, and that I made a breakthrough when I started to think of Haskell in terms of forcing expressions instead of "evaluating" them. Some key differences:
In Haskell the "certain property" has the unfriendly name weak head normal form ("WHNF"), which really just means that the expression is either a nullary data constructor or an application of a data constructor.
Let's translate that to a very rough set of informal rules. To force an expression expr
:
expr
is a nullary constructor or a constructor application, the result of forcing it is expr
itself. (It's already in WHNF.) expr
is a function application f arg
, then the result of forcing it is obtained this way: f
. arg
? If not, then force arg
and try again with the result of that. f
with the parts of (the possibly rewritten) arg
that correspond to them, and force the resulting expression. One way of thinking of this is that when you force an expression, you're trying to rewrite it minimally to reduce it to an equivalent expression in WHNF.
Let's apply this to your example:
foo :: Num a => [a] -> [a]
foo [] = []
foo (_:xs) = map (* 2) xs
-- We want to force this expression:
head (foo [1..])
We will need definitions for head
and `map:
head [] = undefined
head (x:_) = x
map _ [] = []
map f (x:xs) = f x : map f x
-- Not real code, but a rule we'll be using for forcing infinite ranges.
[n..] ==> n : [(n+1)..]
So now:
head (foo [1..]) ==> head (map (*2) [1..]) -- using the definition of foo
==> head (map (*2) (1 : [2..])) -- using the forcing rule for [n..]
==> head (1*2 : map (*2) [2..]) -- using the definition of map
==> 1*2 -- using the definition of head
==> 2 -- using the definition of *
I believe the idea must be that in a lazy language if you're evaluating a function application, it must be because you need the result of the application for something. So whatever reason caused the function application to be reduced in the first place is going to continue to need to reduce the returned result. If we didn't need the function's result we wouldn't be evaluating the call in the first place, the whole application would be left as a thunk.
A key point is that the standard "lazy evaluation" order is demand-driven. You only evaluate what you need. Evaluating more risks violating the language spec's definition of "non-strict semantics" and looping or failing for some programs that should be able to terminate; lazy evaluation has the interesting property that if any evaluation order can cause a particular program to terminate, so can lazy evaluation.1
But if we only evaluate what we need, what does "need" mean? Generally it means either
foo
without knowing whether the argument is []
or _:xs
) Int
values to call such operations) main
IO action needs to know what the next thing to execute is So say we've got this program:
foo :: Num a => [a] -> [a]
foo [] = []
foo (_:xs) = map (* 2) xs
main :: IO ()
main = print (head (foo [1..]))
To execute main
, the IO driver has to evaluate the thunk print (head (foo [1..]))
to work out that it's print
applied to the thunk head (foo [1..])
. print
needs to evaluate its argument on order to print it, so now we need to evaluate that thunk.
head
starts by pattern matching its argument, so now we need to evaluate foo [1..]
, but only to WHNF - just enough to tell whether the outermost list constructor is []
or :
.
foo
starts by pattern matching on its argument. So we need to evaluate [1..]
, also only to WHNF. That's basically 1 : [2..]
, which is enough to see which branch to take in foo
.2
The :
case of foo
(with xs
bound to the thunk [2..]
) evaluates to the thunk map (*2) [2..]
.
So foo
is evaluated, and didn't evaluate its body. However, we only did that because head
was pattern matching to see if we had []
or x : _
. We still don't know that, so we must immediately continue to evaluate the result of foo
.
This is what the article means when it says functions are strict in their result. Given that a call to foo
is evaluated at all, its result will also be evaluated (and so, anything needed to evaluate the result will also be evaluated).
But how far it needs to be evaluated depends on the calling context. head
is only pattern matching on the result of foo
, so it only needs a result to WHNF. We can get an infinite list to WHNF (we already did so, with 1 : [2..]
), so we don't necessarily get in an infinite loop when evaluating a call to foo
. But if head
were some sort of primitive operation implemented outside of Haskell that needed to be passed a completely evaluated list, then we'd be evaluating foo [1..]
completely, and thus would never finish in order to come back to head
.
So, just to complete my example, we're evaluating map (2 *) [2..]
.
map
pattern matches its second argument, so we need to evaluate [2..]
as far as 2 : [3..]
. That's enough for map
to return the thunk (2 *) 2 : map (2 *) [3..]
, which is in WHNF. And so it's done, we can finally return to head
.
head ((2 *) 2 : map (2 *) [3..])
doesn't need to inspect either side of the :
, it just needs to know that there is one so it can return the left side. So it just returns the unevaluated thunk (2 *) 2
.
Again though, we only evaluated the call to head
this far because print
needed to know what its result is, so although head
doesn't evaluate its result, its result is always evaluated whenever the call to head
is.
(2 *) 2
evaluates to 4
, print
converts that into the string "4"
(via show
), and the line gets printed to the output. That was the entire main
IO action, so the program is done.
1 Implementations of Haskell, such as GHC, do not always use "standard lazy evaluation", and the language spec does not require it. If the compiler can prove that something will always be needed, or cannot loop/error, then it's safe to evaluate it even when lazy evaluation wouldn't (yet) do so. This can often be faster so GHC optimizations do actually do this.
2 I'm skipping over a few details here, like that print
does have some non-primitive implementation we could step inside and lazily evaluate, and that [1..]
could be further expanded to the functions that actually implement that syntax.
上一篇: >在Haskell中未定义