Reputation: 774
I was messing around with Haskell, and wondered if there were any differences in performances between these two functions:
count :: (Eq a) => [a] -> a -> Int
count xs e = foldr countCheck 0 xs
where countCheck x
| x == e = (1+)
| otherwise = (0+)
count' :: (Eq a) => [a] -> a -> Int
count' xs e = foldr countCheck 0 xs
where countCheck x acc
| x == e = acc + 1
| otherwise = acc
I tried the following code as a benchmark: main = print (count [1..10000] 1)
, which resulted in the first (using partially applied +
) function being slightly faster on average.
I mainly wondered because, to me, count
is way more confusing to read than count'
. Other than being "clever", I figured that another tradeoff must be that it's faster. So does using partially applied functions make code run faster? If so, why?
Upvotes: 2
Views: 252
Reputation: 15273
Using partially applied functions in programs compiled with sufficiently high level of optimization could hardly make them faster, only slower. Simply because in this case you provide the compiler with less information about your intents.
With the exception, if the partially applied function is given the name and INLINE
pragma (in some special cases). This is a kind of hint for GHC.
Upvotes: 1
Reputation: 53881
Well I took a look at the core from the following programs
main = print (count [1..100000] 1)
and
main = print (count' [1..100000] 1)
Compiling these and prettying up the resulting core gives almost identical files, I've removed the not interesting bits about [1..10000]
and print
since they're identical.
count:
main_e = __integer 1
main7 = \ x -> x
main6 =
\ a -> case a of _ { I# b -> I# (+# 1 b) }
main5 =
\ x ->
case eqInteger x main_e of _ {
False -> main7;
True -> main6
}
count':
main_e = __integer 1
main5 =
\ x acc ->
case eqInteger x main_e of _ {
False -> acc;
True -> case acc of _ { I# x1 -> I# (+# x1 1) }
}
So it actually generates almost identical core, with count
generating slightly more complicated core. However inlining main6
and main7
and lifting the lambda abstraction into one gives completely identical core.
Since GHC can perform all the optimizations I'd be very surprised at any difference between the code's performance. And even more surprised if it was in favour of count
.
The whole zen of Haskell is to just write clean programs and let the compiler deal with making it faster. If the non-foldr
version is clearer, write that. But after reading fold
s for a few years the foldr
version looks clearer so I'd write that. The cool part though, it doesn't matter which you use since they both are compiled to the same code.
Upvotes: 5