Reputation: 167
I'm following an online tutorial on Haskell. We define a function to add two-dimensional vectors, represented by tuple pairs of numbers. The following is the explicit type declaration, that ensures both inputs are two-dimensional vectors.
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
I understand why the following function definition uses pattern matching: it describes the patterns that input data should conform to.
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
Why does the following alternative function definition not use pattern matching? (fst) and (snd) are guaranteed to work, because the inputs are explicitly declared to be tuples of length two.
What is the difference between the two function definitions?
addVectors a b = (fst a + fst b, snd a + snd b)
Upvotes: 3
Views: 377
Reputation: 55079
They differ in strictness. Suppose we rename them:
> let addVectorsStrict (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
> let addVectorsLazy a b = (fst a + fst b, snd a + snd b)
addVectorsStrict undefined undefined
is undefined
—the pattern matching is performed as soon as the outer (,)
constructor of the result is demanded:
> case addVectorsStrict (error "A") (error "B") of (_, _) -> ()
*** Exception: A
But addVectorsLazy undefined undefined
is (undefined, undefined)
—the pattern matching is deferred until one of the elements of the result is demanded.
> case addVectorsLazy (error "A") (error "B") of (_, _) -> ()
()
Basically, addVectorsLazy
always returns a tuple, where the elements may be unevaluated. addVectorsStrict
may not return. You can also get the effect of addVectorsLazy
using a lazy pattern match:
> let addVectorsAlsoLazy ~(x1, y1) ~(x2, y2) = (x1 + x2, y1 + y2)
> case addVectorsAlsoLazy (error "A") (error "B") of (_, _) -> ()
()
To get a better understanding of evaluation order, you can observe it using Debug.Trace.trace
:
addVectors
(trace "first tuple evaluated"
(trace "x1 evaluated" 1, trace "y1 evaluated" 2))
(trace "second tuple evaluated"
(trace "x2 evaluated" 3, trace "y2 evaluated" 4))
The basic thing to remember about evaluation in Haskell is that it’s driven by pattern matching using case
and function equations (which desugar to case
).
It doesn’t matter much in this case, but you can write your functions lazily to avoid evaluating expensive computations if their results are never needed, or strictly to avoid the overhead of thunks if you know some result will always be needed.
In general, it’s a good idea to make the fields of your data structures strict unless you need them to be lazy, and make your functions lazy unless you need them to be strict. Here you could make a strict pair type to represent your vectors:
data Vector a = Vector !a !a
Upvotes: 7