Visit the leap exercise on Exercism to read the full instructions and download the exercise files.
Dig Deeper
Logical expression
Logical expression
isLeapYear : Int -> Bool
isLeapYear year =
let
divisibleBy number =
modBy number year == 0
in
divisibleBy 4 && (not (divisibleBy 100) || divisibleBy 400)
We can combine smaller logical expressions into larger ones using the logical operators && (and), || (or), and not (negation).
A logicial expression is the most concise approach.
Precedence
In school they teach you that 2 + 3 * 4 is to be read as meaning 2 + (3 * 4).
This is a convention, chosen for its convenience.
We say that the * operator has higher precedence than +.
In logic similar ambiguities exist, and these are similarly resolved.
- and has higher precedence than or, and
- not has higher precedence than both and and or.
For example, p || q && r means the same as p || (q && r).
If you don’t wish to remember the precendence of all the things, or force readers of your code to do so, you can use parentheses to make it explicit.
Case expression
Case expression
isLeapYear : Int -> Bool
isLeapYear year =
let
isDivisibleByFour = (modBy 4 year) == 0
isDivisibleBy100 = (modBy 100 year) == 0
isDivisibleBy400 = (modBy 400 year) == 0
in
case (isDivisibleByFour, isDivisibleBy100, isDivisibleBy400) of
(True, _, True) -> True
(True, False, _) -> True
_ -> False
In this approach
In this approach we use a case expression, and match on a [Tuple][tuple].
It turns out that, if we are careful to ask questions in the right order, we can always potentially attain certainty about the answer by asking one more question.
This is a [truth-table][truth-table] like approach, which can be easier to read for complicated logic.
When to use a case expression?
Using a case expression with a Tuple is idiomatic in Elm, but tuples have a maximum of 3 values, so can’t be used for anything larger than this.
Strings and lists can hold more values and can also be used with case expressions, which are useful in many circumstances.
Using a list with a case expression and recursion is especially common.
[tuple]
https://elmprogramming.com/tuple.html
“Tuples in Elm”
[truth-table]:
https://brilliant.org/wiki/truth-tables/
“Truth tables”
If expression
If expression
isLeapYear : Int -> Bool
isLeapYear year =
let
divisibleBy number =
modBy number year == 0
in
if divisibleBy 100 then
divisibleBy 400
else
divisibleBy 4
An if expression (if … then … else …) is a compound expression that uses a test to determine which of two alternatives to evaluate to.
Many other languages feature a similar construct, often termed ‘ternary operator’.
In this approach
This approach uses exactly two tests to determine whether a year is a leap year.
The first test is for divisibility by 100.
Once we know if the year is a multiple of 100, we know which further test to perform.
- If the year is evenly divisible by 100, then
divisibleBy 100 will evaluate to True and the entire if expression will evaluate to whatever divisibleBy 400 evaluates to.
- If the year is not evenly divisible by 100, then
divisibleBy 100 is False and so the if expression evaluates to divisibleBy 4.
This approach is not as concise as the logical-expression, but it is easier to describe, which makes it easier to comunicate the problem domain.
When to use if?
if expressions might be a good fit when you
- need an expression that
- chooses between exactly two options
- depending on a single
Bool.
When you have something other than a Bool, use case instead.
Source: Exercism elm/leap