Reasoning a computation based on problem and symantics
While it is important that our code be correct, it is equally important to review our work from efficiency standpoint. The number of steps that it takes for a function to compute its final value depends on the order of evaluation. Apparently, compiler can do a lot of optimization behind the scenes, and I don't know the details of that process. What we can do as programmers is see if we can optimize our function definitions using the knowledge of language symantics and something about the problem itself. While the example below is trivial, it is good to develop a mindset where we think of how our definitions get evaluated.
Check for a leap year
Since a leap year is the one that is evenly divisible by 4, except every year that is evenly divisible by 100 unless the year is also evenly divisible by 400, we can write the following definition:
isLeapYear :: Int -> Bool isLeapYear year = year `rem` 400 == 0 || (year `rem` 100 /= 0 && year `rem` 4 == 0)
First we note that since the main expression is an
or expression of the form
e1 || e2, if e1 evaluates to
False (often), e2 has to be evaluated to get final value of the entire expression. We can rewrite our expressions in terms of
and as follows.
Knowing the symantics of
e1 && e2, if e1 evaluates to false (often), e2 will not be evaluated and the entire expression evaluates to
False. Also, within the
or expression of e2 now, the first sub-expression (not divisible by 100) is more likely to be
True in which case the
or expression will evaluate to
True without evaluating the second sub-expression (divisible by 400).
isLeapYear2 :: Int -> Bool isLeapYear2 year = year `rem` 4 == 0 && (year `rem` 100 /= 0 || year `rem` 400 == 0)