Welcome to Elm Kata! A series of posts in which we solve Elm exercises of varying difficulty to learn about the language.

The exercise is simple: identify leap years with Elm. So, given a year, determine whether that year is a leap year or not. The rules are as follows:

leap years are on: 
- every year that is evenly divisible by 4
- except every year that is evenly divisible by 100
- unless the year is also evenly divisible by 400

Let’s start by writing some functions for checking these divisibility rules.

divisibleBy4 year = year % 4 == 0
divisibleBy4 year = year % 100 == 0
divisibleBy4 year = year % 400 == 0

Now for the boolean expression…

(divisibleBy4 year) && (not (divisibleBy100 year) || (divisibleBy400 year))

Cool. So now we can put the pieces together and build our function. But first, a function signature!

We want a function that takes an integer (Int) and returns a boolean (Bool):

isLeapYear : Int -> Bool

Bam.

Now, we can add a let-in expression to create a local function scope for our divisibleByX helpers.

isLeapYear year =
  let
    divisibleBy4 y = 
        y % 4 == 0

    divisibleBy100 y =
      y % 100 == 0

    divisibleBy400 y =
      y % 400 == 0
  in 
    (divisibleBy4 year) && (not (divisibleBy100 year) || (divisibleBy400 year))

This could actually be much better.

First off, we can make divisibleByX a lot more useful by having it take two arguments rather than one: the number to divide and the number to divide by.

divisibleBy n y =
 y % n == 0

Nice, but there’s more. One of Elm’s core libraries, “Basics” has a function called rem with the following type signature:

rem : Int -> Int -> Int

According to the docs, it does this:

Find the remainder after dividing one number by another.

e.g. rem 11 4 == 3

Is this exactly what we need? Yes, this is exactly what we need.

divisibleBy n y = rem y n == 0

Noice. Time to refactor.

isLeapYear : Int -> Bool
isLeapYear y =
    let
        divisibleBy n y =
            rem y n == 0
    in
        (divisibleBy 4 y) && (not (divisibleBy 100 y) || (divisibleBy 400 y))

That boolean expression looks like it could be simplified. There’s another interesting boolean operator in the Basics package…xor.

xor : Bool -> Bool -> Bool

The exclusive-or operator. True if exactly one input is True.

Cool beans. Let’s refactor our solution.

If we read our requirements again, this problem actually lends itself quite well to xor. We want all years divisible by 4 EXCEPT the ones divisible by 100.

xor (divisibleBy 4 year) (divisibleBy 100 year) || (divisibleBy 400 year)

This leaves us with a third iteration of:

isLeapYear : Int -> Bool
isLeapYear year =
    let
        divisibleBy n y =
            rem y n == 0
    in
        xor (divisibleBy 4 year) (divisibleBy 100 year) || (divisibleBy 400 year)

But surely there is a better way…