Reputation: 34071
I have following code:
foldr const 0 ([1] ++ undefined)
and I've got as the result 1
.
I know, that foldr
is right associated. Writing out the example from above:
const 1 (const undefined 0)
Why expression in the parentheses does not get evaluated as first?
Because function application has higher precedence then parentheses?
Upvotes: 0
Views: 131
Reputation: 675
What I believe you are expecting what is called "applicative evaluation order". In other words, in f (g x)
, you expect g x
to be evaluated first, then f
to be called on the result.
In your example, that would mean that evaluation would start with evaluating 0
, [1]
and undefined
which is where I believe you expect the crash.
Haskell has what is called "call by need" or "lazy evaluation". In f (g x)
, f
is evaluated first, providing it as an argument a program (often referred to as "thunk") to evaluate g x
. That means that if the value of g x
is never needed by f
, it never gets evaluated.
Here is how your example would get evaluated (I'm placing the definition of foldr
below for reference):
foldr const 0 ([1] ++ undefined) -- one step of evaluation of ++
= foldr const 0 (1 : ([] ++ undefined)) -- second equation of foldr
= const 1 (foldr const 0 ([] ++ undefined)) -- evaluate const
= 1
And here is the definitions of foldr
and (++)
:
foldr _ z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
(++) [] ys = ys
(++) (x:xs) ys = x : (xs ++ ys)
Upvotes: 2
Reputation: 15605
To properly evaluate const 1 (const undefined 0)
, we need to know the definition of const
, the evaluation strategy used by GHC, and the behavior of currying in functions which (appear to) take multiple arguments. (Not necessarily in that order.)
const
The definition of const is
const c _ = c
Functions which seem to take multiple arguments are actually curried:
f x y = (f x) y
We can explicitly curry const
using eta abstraction (f x = y
becomes f = \x -> y
), turning it into a function that takes an argument and gives another function:
const c _ = c
= { eta abstraction }
const c = \_ -> c
Combining these two concepts means that whenever we see const x y
, we can replace it with (\_ -> x) y
:
const x y
= { currying }
(const x) y
= { const c = \_ -> c }
(\_ -> x) y
Evaluation in Haskell* starts with the outermost reducible expression (redex) and is triggered by pattern matching. This means that in f (g x)
, f
is evaluated before g x
(it is outermost). g x
is then only evaluated if the evaluation of f
demands it. Then, in turn, x
is only evaluated if the evaluation of g
demands it.
One of the properties of outermost-first evaluation is *short-circuiting(: if the evaluation of an outer expression doesn't demand a result from an inner expression, the inner expression is never evaluated. In particular, this means that (\_ -> x) y = x
regardless of what y
is, because \_ -> x
doesn't demand the evaluation of y
.
const 1 (const undefined 0)
Putting these together, we can evaluate const 1 (const undefined 0)
const 1 (const undefined 0)
= { const x y = (\_ -> x) y }
(\_ -> 1) (const undefined 0)
= { (\_ -> x) y = x }
1
Once you understand this, it's easy enough to skip over the currying and just write:
const 1 (const undefined 0)
= { definition of const }
1
because const c _ = c
, c = 1
, and we know the second argument is never evaluated.
(You also asked about precedence but precedence is not relevant to the evaluation of const 1 (const undefined 0)
.)
* Strictly speaking**, Haskell does not define an evaluation order. It specifies that evaluation must be non-strict and leaves the implementation of this semantics up to Haskell implementations. GHC uses lazy evaluation, which is outermost-first evaluation combined with sharing (which says that x
will be evaluated at most once in let x = 1 + 1 in x * x
).
** Yes, this is a pun.
Upvotes: 3
Reputation: 370112
Why expression in the parentheses does not get evaluated as first?
Haskell uses lazy evaluation. This means an argument is evaluated if and when it is used by the function. This differs from eager evaluation where all arguments of the function are executed before the function is called.
Because function application has higher precedence then parentheses?
Precedence has nothing to do with it. Precedence just determines what's an operand to what (i.e. is "x ??? y !!! z" an application of the operator ???
to the operands x
and y !!! z
or of the operator !!!
to x ??? y
and z
). It does not affect evaluation order.
Upvotes: 6