Haskell use Lazy evaluation by default, but also provides a strict version of function application with $!.
This means that f $! x behaves as normal function application f x except that the top level evaluation of the argument x is forced before the function f is applied.
Formally f $! x is only a redex once evaluation of x, using lazy evaluation as normal, has reached a point where it is known that the result is not undefined. Hereafter the expression can be reduced to the normal application f x.
Strict application are often used to improve space performance, since one can force argument-lists to be evaluated quickly instead of being repeatedly stored.
Curried Functions
When used with curried function with multiples arguments, e.g. f x y there are the three possibilities
(f $! x) yforces top-level evaluation of x(f x) $! yforces top-level evaluation of y(f $! x) $! yforces top-level evaluation of x and y
Examples
Square function
With the function
square :: Int -> Int
square n = n * nStrict application will evaluate like
square $! (1+2)
= {applying +}
square $! 3
= {applying $!}
square 3
= {applying square}
3 * 3
= {applying *}
9Saving Space with Strict Application
Consider a function sumwith that calculates the sum
of a list of integers using an accumulator value:
sumwith :: Int -> [Int] -> Int
sumwith v [] = v
sumwith v (x:xs) = sumwith (v+x) xsThen with lazy evaluation we have
sumwith 0 [1,2,3]
= { applying sumwith }
sumwith (0+1) [2,3]
= { applying sumwith }
sumwith ((0+1)+2) [3]
= { applying sumwith }
sumwith (((0+1)+2)+3) []
= { applying sumwith }
((0+1)+2)+3
= { applying the first + }
(1+2)+3
= { applying the first + }
3+3
= { applying + }
6where the entire summation ((0+1)+2)+3 is constructed before any of the additions are actually performed. In general the function will construct a summation whose size is proportional to the number of integers in the original list, which might require a lot of space.
One can instead use strict application to perform each addition as soon as it is introduced to improve space performance. We can force the evaluation of the accumulator value with
sumwith v [] = v
sumwith v (x:xs) = (sumwith $! (v+x)) xswhich could also be defined with sumwith = foldl' (+), with the Data.Foldable function foldl' that forces evaluation of its accumulator prior to processing tail of the list.
Now the evaluation will be
sumwith 0 [1,2,3]
= { applying sumwith }
(sumwith $! (0+1)) [2,3]
= { applying + }
(sumwith $! 1) [2,3]
= { applying $! }
sumwith 1 [2,3]
= { applying sumwith }
(sumwith $! (1+2)) [3]
= { applying + }
(sumwith $! 3) [3]
= { applying $! }
sumwith 3 [3]
= { applying sumwith }
(sumwith $! (3+3)) []
= { applying + }
(sumwith $! 6) []
= { applying $! }
sumwith 6 []
= { applying sumwith }
6Resources
Hutton - Programming in Haskell pp. 223-225