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 Elixir code that were deployed).

Elixir Versus JavaScript

I have been following with interest and learning about Elixir (and Phoenix). They offer proper functional programming with nice syntax, superficially similar to Ruby, and a structure somewhat like Rails. They offer proper concurrency and 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 that 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 is perfect, they probably represent a similar quality (within each language). A few observations:

  • The JavaScript one took less time to write (okay, I've used it a lot more).
  • The 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 the 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 lowercase).
  • JavaScript has nicer arrow/anonymous function syntax (though Elixir has a longer fn n -> n + 1 end form, which I typically prefer).
  • The 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 one 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.