Charlie Harvey

Seven More Languages: Elm Day One

I am pretty excited about the latest installment of my Seven More Languages journey. Elm is a functional reactive programming (FRP) language with an Haskell-like syntax that compiles to Javascript. The idea is to be able to write in a language with a decent type system and support for FRP, without having to piss around in callback hell.

The language creator, Evan Czaplicki, has done some really excellent talks introducing Elm and making the case for FRP. Here’s a sneak preview of some of the stuff I hope we’ll be covering in the next few days.

First impressions of Elm

The first day introduces some of the basics of Elm syntax and is spent entirely in the Elm REPL, which seems modelled after GHCi but not quite as powerful. You can certainly see Elm’s ML/Haskell roots and I found the syntax I was learning pretty familiar.

Incidentally, there is a great online editor where you can compile and run Elm code, letting you ‘try before you buy’ as it were. There are a bunch of really quite fun examples there for you to tweak. Like I was saying on Factor Day 3, I find examples a really helpful way of documenting, so this seems a good sign to me.

Installation was a little bit faffy. I went down the root of installing everything in a sandbox, using the BuildFromSource script. I had to tell cabal to do force-reinstalls and had problems with the REPL not finding a file called interfaces.data. On my system I needed to make a symlink like this to appease the cabal gods.

$ ln -s sandbox/Elm-Platform/0.13/share/x86_64-linux-ghc-7.6.3/Elm-0.13 sandbox/Elm-Platform/0.13/share/compiler

Elm was also struggling to find nodejs (what modern web development system is comelete without a dependency on node?), and needed an environment variable called ELM_HOME setting. So I made a little shell script to launch the REPL.

#!/bin/bash PATH=$PATH:/usr/local/bin/Elm-Platform/0.13/bin export PATH ELM_HOME=/usr/local/bin/Elm-Platform/0.13/share export ELM_HOME elm-repl --interpreter /usr/bin/nodejs

Once that was in place I was ready to play!

Exercises

Easy exercises

Write a function to find the product of a list of numbers.

Right oh. product xs = case xs of [] -> 1 x::xs' -> x * product xs' I’ll just mention that having this function would let us define factorial a little more cleanly than the example in the text. fac n = product [1..n]

Write a function to return all of the x fields from a list of point records.

allX ps = case ps of [] -> [] p::ps' -> abs p.x :: allX ps'

Use records to describe a person containing name, age and address. You should also express the address as a record.

address = { street = "Cowley rd." , town = "Oxford" , county = "Oxon." , pc = "OX4 1DN" } person = { name = "Edward Normalhands" , age = 28 , address = address }

Is it easier to use abstract data types or records to solve the previous problem? Why?

Let’s do the pedantic answer first.

It’s easier to use records. Why? Because the specification says Use records to describe a person…

The less pedantic answer is still that it is easier to use records, in my opinion. I prefer using the person.field syntax to extract fields from records to using pattern matching on ADTs. There is also a bit of syntactic overhead requred to specify the fields. Compared to the previous record-based code the definition of a person type is a bit longer.

data Street = Street String data Town = Town String data County = County String data Postcode = Postcode String data Address = Address Street Town County Postcode data Name = Name String data Age = Age Int data Person = Person Name Age Address ad = Address (Street "Cowley rd.") (Town "Oxford") (County "Oxon.") (Postcode "OX4 1DN") p = Person (Name "Edward Normalhands") (Age 28) ad

That said, there are advantages to having the type system catch attempts to, for example, set the first line of a person’s address to their name. It’s a trade-off. On balance using the ADTs, though slightly less convenient, helps you to not shoot yourself in the foot. Not that you’d ever do that of course.

Medium exercises

Write a function called multiply.

Unless I have missed something, this should have been in the easy exercises… multiply x y = x * y

Use currying to express 6 * 8.

The way I read this is that I am being asked to make a partially applied function and then show it being used. So, my function could be as simple as multiplyBy6 y = multiply 6 y And then I could call it thus > multiplyBy6 8 48 : number

Make a list of person records. Write a function to find all of the people in your list older than 16.

Slightly more challenging, but still very much a first day exercise. people = [ { name = "Edward Normalhands", age = 28, address = addr } , { name = "Luke Landwalker", age = 16, address = addr } , { name = "Dave Vader", age = 48, address = addr } , { name = "Dan Solo", age = 33, address = addr } , { name = "Pizza the Hutt", age = 164, address = addr } ] oldies ps = case ps of [] -> [] p::ps' -> if p.age > 16 then p :: oldies ps' else oldies ps'

Of course, this approach is a bit longwinded and bruteforcey (to coin a phrase). We can express the same idea in a filter like this. oldies' = filter (\p -> p.age > 16) Much nicer.

For testing, I was mapping over the result of oldies and oldies' to pull out just the names from the records. The output is a little easier for a programmer of very little brain like myself to parse than is a complete dump of the entire content of the records. map (.name) (oldies' people)

Hard exercises

Write the same function, but allow records where the age field might be nothing. How does Elm support nil values?

I think that Bruce is guiding readers towards looking at Maybe with this question.

Scala and ML programmers will know Maybe as Option. Elm goes with the more Haskell-like Maybe.

A Maybe is either Nothing or Just something. If we don’t know a person’s Age, we say age = Nothing If we do know their age, and it is 50 we can say instead age = Just 50 We then use pattern matching to deal with the cases when we are looking at people’s ages.

The code is a little more complex to deal with the Maybe Int that now holds the person’s age. addr = { street = "Cowley rd." , town = "Oxford" , county = "Oxon." , pc = "OX4 1DN" } people = [ { name = "Edward Normalhands", age = Just 28, address = addr } , { name = "Luke Landwalker", age = Just 16, address = addr } , { name = "Dave Vader", age = Nothing, address = addr } , { name = "Dan Solo", age = Just 33, address = addr } , { name = "Pizza the Hutt", age = Just 164, address = addr } ] oldies ps = case ps of [] -> [] p::ps' -> case p.age of Just n -> if n> 16 then p :: oldies ps' else oldies ps' Nothing -> oldies ps'

For the sake of completeness, let’s just take a look at the filter version of the code now that we need to support Maybes. oldies' = filter (\p -> case p.age of Just n -> n > 16 Nothing -> False )

Wrapping up day one

Elm is nice. Really nice. Despite the slight faff in getting it set up, I enjoyed its clear, functional syntax. Having had a sneak preview of what Elm can do, I am excited to see where day 2 and 3 lead.


Comments

  • Be respectful. You may want to read the comment guidelines before posting.
  • You can use Markdown syntax to format your comments. You can only use level 5 and 6 headings.
  • You can add class="your language" to code blocks to help highlight.js highlight them correctly.

Privacy note: This form will forward your IP address, user agent and referrer to the Akismet, StopForumSpam and Botscout spam filtering services. I don’t log these details. Those services will. I do log everything you type into the form. Full privacy statement.