DEV Community

Vince Campanale

Posted on • Updated on • Originally published at vincecampanale.com

"Elm Has Me Leaping For Joy"

I'm very new to Elm (a couple of weeks) and am working through exercises on exercism.io. I see a lot of potential in this language and am enjoying the learning process. Writing about what I learn usually helps me understand it better. Thus, this article was born.

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
divisibleBy100 year = year % 100 == 0
divisibleBy400 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)
``````

I've not even scratched the surface of this powerful language. Getting into the FRP mindset has been a challenge but I can already see the benefits of thinking in this paradigm. While the solution I've put together over the last few paragraphs gets the job done, there is surely a better way...

Follow me on Twitter at @_vincecampanale and/or Github for more Elm stuff soon to come...

Yossarian

Nice article. Not sure about the refactoring though. With rem and xor additions readability decreased. Not sure why it make it better, especially with the former?

As Martin Fowler once said 'Good programmers write code that humans can understand.'

Vince Campanale

I actually find the refactor more readable, but one man's variable is another man's constant I guess. To me, `rem` looks like remainder and `xor` is reminiscent of "except". The former is simpler and more traditional, but less semantic. At the end of the day though, it's a very simple exercise so I would have felt like I missed an opportunity to improve if I just solved it quick and moved on :)

Stephen Bell

You've got a typo in your second code block. "divisibleBy4" is used all three times.

Vince Campanale

Nice catch - thank you.

Bruno Oliveira

Nice! It's great to see the process of solving an exercism.io exercise. Keep doing this! What your thoughts about Clojure and ClojureScript?

Pavel Razgovorov

That XOR operator, dammmnnn! :O

Mac Siri

Aside from exercism.io, Where else are you learning elm from?

Vince Campanale

Pragmatic Studio has a great course: pragmaticstudio.com/elm

Rui Teixeira

Awesome post! Really shows how great Elm is for iterating/refatoring!

Doesn't xor break for years divisible by 400?

xor true (true or true) -> xor true true -> false

Or am I reading this wrong

Vince Campanale • Edited

You're correct. That's why the second half of the expression has `|| divisiblyBy 400 year`. For a number divisible by 400, the first half evaluates to false, then the second half evaluates to true, `false || true -> true`, so we get `true` for years divisible by 400.

maxdevjs

Is the link to exercism.io broken?