Michal Paszkiewicz

#4 year of languages - elm

Since the last 3 new languages I've tried were all back-end languages, I've decided to try a language that promises to be a "delightful language for reliable webapps". Enter elm, a language that is supposed to be easy, bug free and far more performant than ember, react or any of the angulars. So, I started off my elm training with the standard tutorial set.

I wasn't filled with confidence when I ran elm-make on one of the example projects "01-button.elm". The output file js code began like this:


function F2(fun)
{
  function wrapper(a) { return function(b) { return fun(a,b); }; }
  wrapper.arity = 2;
  wrapper.func = fun;
  return wrapper;
}

function F3(fun)
{
  function wrapper(a) {
    return function(b) { return function(c) { return fun(a, b, c); }; };
  }
  wrapper.arity = 3;
  wrapper.func = fun;
  return wrapper;
}

function F4(fun)
{
  function wrapper(a) { return function(b) { return function(c) {
    return function(d) { return fun(a, b, c, d); }; }; };
  }
  wrapper.arity = 4;
  wrapper.func = fun;
  return wrapper;
}

function F5(fun)
{
  function wrapper(a) { return function(b) { return function(c) {
    return function(d) { return function(e) { return fun(a, b, c, d, e); }; }; }; };
  }
  wrapper.arity = 5;
  wrapper.func = fun;
  return wrapper;
}

function F6(fun)
{
  function wrapper(a) { return function(b) { return function(c) {
    return function(d) { return function(e) { return function(f) {
    return fun(a, b, c, d, e, f); }; }; }; }; };
  }
  wrapper.arity = 6;
  wrapper.func = fun;
  return wrapper;
}

function F7(fun)
{
  function wrapper(a) { return function(b) { return function(c) {
    return function(d) { return function(e) { return function(f) {
    return function(g) { return fun(a, b, c, d, e, f, g); }; }; }; }; }; };
  }
  wrapper.arity = 7;
  wrapper.func = fun;
  return wrapper;
}

function F8(fun)
{
  function wrapper(a) { return function(b) { return function(c) {
    return function(d) { return function(e) { return function(f) {
    return function(g) { return function(h) {
    return fun(a, b, c, d, e, f, g, h); }; }; }; }; }; }; };
  }
  wrapper.arity = 8;
  wrapper.func = fun;
  return wrapper;
}

function F9(fun)
{
  function wrapper(a) { return function(b) { return function(c) {
    return function(d) { return function(e) { return function(f) {
    return function(g) { return function(h) { return function(i) {
    return fun(a, b, c, d, e, f, g, h, i); }; }; }; }; }; }; }; };
  }
  wrapper.arity = 9;
  wrapper.func = fun;
  return wrapper;
}

function A2(fun, a, b)
{
  return fun.arity === 2
    ? fun.func(a, b)
    : fun(a)(b);
}
function A3(fun, a, b, c)
{
  return fun.arity === 3
    ? fun.func(a, b, c)
    : fun(a)(b)(c);
}
function A4(fun, a, b, c, d)
{
  return fun.arity === 4
    ? fun.func(a, b, c, d)
    : fun(a)(b)(c)(d);
}
function A5(fun, a, b, c, d, e)
{
  return fun.arity === 5
    ? fun.func(a, b, c, d, e)
    : fun(a)(b)(c)(d)(e);
}
function A6(fun, a, b, c, d, e, f)
{
  return fun.arity === 6
    ? fun.func(a, b, c, d, e, f)
    : fun(a)(b)(c)(d)(e)(f);
}
function A7(fun, a, b, c, d, e, f, g)
{
  return fun.arity === 7
    ? fun.func(a, b, c, d, e, f, g)
    : fun(a)(b)(c)(d)(e)(f)(g);
}
function A8(fun, a, b, c, d, e, f, g, h)
{
  return fun.arity === 8
    ? fun.func(a, b, c, d, e, f, g, h)
    : fun(a)(b)(c)(d)(e)(f)(g)(h);
}
function A9(fun, a, b, c, d, e, f, g, h, i)
{
  return fun.arity === 9
    ? fun.func(a, b, c, d, e, f, g, h, i)
    : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);
}

What is going on here? What can a poor developer do if they want F10 or A10? Why couldn't it just be:


function F(fun, arity){
	var totalArgs = [];
	var wrapper = function(newValue){
		totalArgs.push(newValue)
		if(totalArgs.length >= arity){
			return fun.apply(this, totalArgs)
		}
		return wrapper;			
	}
	wrapper.arity = arity;
	wrapper.func = fun;
	return wrapper;
}
function A(fun, arity){
	var argCount = arguments.length - 2;
	var args = arguments.slice(2, arguments.length)
	if(fun.arity === arity){
		return fun.func.apply(this, args);
	}
	var result = fun;
	for(var i = 0; i < args.length; i++){
		result = result(args[i])
	}
	return result;
}

Also, since a magnitude more code was written than is really needed, why do the functions still have to be "F" and "A", why not put decent, understandable names, like "curryThisFunction/callThisFunctionWithArguments" or "makeAPipe/smokeAPipe"?

Anyway, the language itself has both advantages and disadvantages. I don't see the seperation of floating point (/) and integer (//) division as particularly useful, but I guess that it saves you having to write a rounding command. Writing a function is nice and easy, which is really great:


getHttpStatusDog httpStatusCode = "https://httpstatusdogs.com/" ++ httpStatusCode

Except, that couldn't possibly work, because when you try


httpStatusCode 204

The program will explode with errors, since - as you guessed - the function expected httpStatusCode to be a string, even though you clearly applied a string ++ operator instead of a normal +. You'd have thought the program would have naturally converted the integer to a string and made this all work swimmingly. Unfortunately, elm provides type safety which now makes it a chore to find out how to finish this simple function...

Searching for elm's String documentation gets a wonderful bit of documentation that tells you exactly how to get the length of a string, reverse it, or turn it into an integer. However, it does not bother covering how you might be able to acquire a string from an integer. Seeing the simple code for converting strings to integers:


String.toInt "404"

My hands automatically started typing what the code that would do reverse should have been. I tried each of the following:


String.fromInt 404
Int.toString 404
404.toString
<string>404
wtfIWantAString 404

but none of these are real in the elm world. Luckily google groups had the answer and I could finally come up with the proper solution to my function:


getHttpStatusDog httpStatusCode = "https://httpstatusdogs.com/" ++ (toString httpStatusCode)

Phew, finally I was going somewhere. The next thing that upset me was looking into the examples' code. The 01-button.elm example (2 buttons incrementing and decrementing a field with a number) requires the following elm code:


import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

main =
  Html.beginnerProgram
    { model = model
    , view = view
    , update = update
    }

type alias Model = Int

model : Model
model =
  0

type Msg
  = Increment
  | Decrement

update : Msg -> Model -> Model
update msg model =
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (toString model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

To do the same in vanilla html/javascript, you would only need the following:


<html>
    <body>
        <button onclick="decrement()">-
        <input id="num" type="number" value=0 />
        <button onclick="increment()">+
        <script>
            var num = document.getElementById("num");
            function decrement(e){
                num.value = +num.value - 1
            }
            function increment(e){
                num.value = +num.value + 1
            }
        </script>
    </body>
</html>

This could be done in even less code if one used a library like jQuery or a framework like Angular. Sure, you will get type security with elm, but as we've seen (with the int/string conversion) that this can sometimes be more of a burden than an advantage. The above javascript code could easily be written in Typescript which would require few additions but provide type safety that (at least to me) feels far more intuitive.

The thing that I started to like about elm at this point is the fact that it is vaguely a functional programming language. It generally seems to follow a flux pattern which is nice and neat and the type safety is definitely a benefit. Whether this gives it an edge over, for example, a TypeScript/React combination is probably more a matter of opinion, but at the time of writing elm has definitely not convinced me. I have to admit to a few advantages of elm over a TypeScript/React combination, though:

  • It would probably be quicker to learn just elm than both TypeScript + React
  • You only have to download elm - there is no need for tonnes of packages, modules, webpack and all the other things that scare people

However, one big disadvantage at this point is that Elm is only really used on the front end. Whether developers usually learn a server-side language and javascript, or use javascript throughout the whole stack, learning something like TypeScript wouldn't take much time. Elm is an entirely new language and developers using it will have to know both JavaScript and Elm (since some of the cool features of elm ARE to do with JavaScript interop). Obviously, this isn't the end of the world - developers should be language agnostic and capable of picking the best tools for the job. JavaScript to elm is also not the greatest paradigm shift ever - I've managed to pick elm up in just a few evenings, so anyone working with it full time would have a much easier job.

How I selected a project

This time it was easy to select a project - my driving instructor gave me a problem to solve (don't ask why). Here it is:

Given N closed doors and N people, every nth person (apart from the first) reverses the state (open/closed) of the doors. How many doors will be open at the end of this? Which door would have changed state the most times?

I found the second part of the problem easy to prove mathematically - the door that has the most prime factors (counting duplicates), will have changed state the most times. The numbers with the most prime factors lower than N will have to be numbers with the most LOW prime factors. A big prime factor (e.g. 5, which is > 2^2) increases the size of the number too much. This means that the door number with the most state switches will be of the format 2^x and possibly 2^(x-1) * 3 for the cases where that number is still smaller than N. For 1000, this number would be 512 (2^9) and 768 (2^8 * 3). The first part of the problem, I'm currently still working on how to solve with pencil and pen (without counting doors physically), so I decided to write a script to tell me the answer for a set number N.

Results

My script is uploaded here. I have enjoyed working with elm - I find thinking in a functional frame of mind challenging and enjoyable. However, I feel that elm is not yet mature enough to warrant using it in the workplace and even if it was, I am not personally convinced that I would ever choose it over a TypeScript/React combination.

published: Fri Mar 10 2017

Michal Paszkiewicz's face
Michal Paszkiewicz reads books, solves equations and plays instruments whenever he isn't developing software for Transport For London. All views on this site are the author's views only and do not necessarily represent the views of TfL.