Elixir versus Javascript

Notes (August 2021)

I wrote this quite a few years ago. I think it was before I officially and briefly became a 'professional Elixir developer' (I was paid to write a few lines of code in Elixir that were deployed).

Elixir versus Javascript

I have been following with interest and learning about Elixir (and Phoenix). They offer proper functional programming but with nice syntax, superficially similar to Ruby, and a structure somewhat like Rails. They offer proper concurrency, compile time macros. And I have really enjoyed working through the Exercism challenges on Elixir. But I've also been surprised how nice Javascript has been for this.

Taking one of the more realistic challenges (a data transformation one around seed planting), Javascript (ES6-ish) seems to result in a shorter and easier to follow solution which is nearly entirely functional.

First let's look at Elixir:

defmodule Garden do
  @doc """
    Accepts a string representing the arrangement of cups on a windowsill and a
    list with names of students in the class. The student names list does not
    have to be in alphabetical order.

    It decodes that string into the various gardens for each student and returns
    that information in a map.
  """

  @default_names [:alice, :bob, :charlie, :david, :eve, :fred, :ginny, :harriet, :ileana, :joseph, :kincaid, :larry]

  @plants %{"R" => :radishes, "C" => :clover, "G" => :grass, "V" => :violets}

  @spec info(String.t(), list) :: map
  def info(info_string, names \\ @default_names) do
    student_names = Enum.sort(names)

    [first_row, second_row] = String.split(info_string, "\n", trim: true)
      |> Enum.map(&(String.split(&1, "", trim: true)))

    seeds = Enum.zip(Enum.chunk(first_row, 2), Enum.chunk(second_row, 2))
      |> Enum.zip(student_names)
      |> Enum.reduce(%{}, fn {{[a,b], [c,d]}, name }, map ->
          Map.put(map, name, List.to_tuple(Enum.map([a,b,c,d], &(@plants[&1]))))
        end)

    Enum.reduce(student_names, seeds, fn name, map ->
        Map.put_new(map, name, {})
    end)
  end
end

and now Javascript

const default_names = ["alice", "bob", "charlie", "david", "eve", "fred",
                      "ginny", "harriet", "ileana", "joseph", "kincaid", "larry"];

const plants = {R: "radishes", C: "clover", G: "grass", V: "violets"};

const chunk = (arr, n) => {
  let newArr = [];
  for(let i=0; i <arr.length; i+= n){
    newArr.push([arr.slice(i, i + n)]);
  }
  return newArr;
};

const Garden = function(seeds, children = default_names){
  const sortedChildren = children.map(c => c.toLowerCase()).sort();
  const seedLists = seeds.split("\n");
  const firstRow = chunk(seedLists[0].split(""), 2);
  const secondRow = chunk(seedLists[1].split(""), 2);

  sortedChildren.forEach((child, idx) => {
    if(idx < firstRow.length)
      this[child] = (firstRow[idx].concat(secondRow[idx])).map(el => plants[el]);
  });
};

module.exports = Garden;

And while I'm not claiming either are perfect they probably represent a similar quality (within each language). A few observations:

  • Javascript one took less time to write (okay, I've used it a lot more).
  • Javascript one is easier to understand
  • They are similar (okay I may have looked at the Elixir one before doing the Javascript one).
  • In Javascript I created the chunk function, in Elixir it is included in standard library.
  • The Elixir challenge demanded the answer in a slightly awkward format (hence some extra conversion)
  • The Javascript one does more (converts names to lower case).
  • Javascript has nicer arrow/anonymous function syntax (though Elixir has a longer fn n -> n + 1 end form which I typically prefer).
  • Elixir one is properly functional, but so is (mostly) the Javascript one. The solution demanded the results via a constructor (yuck) so there is some this assignment and the chunk function internally mutates an array but otherwise all is functional.
  • Elixir has a typespec (but adding with Flow would have been easy, the requisite build system less so).
  • Elixir was probably more fun to write

But maybe all of this is irrelevant in 'real' applications. And Phoenix is way better than any Javascript backend framework I've used. And concurrency. To be continued.