I’m now about a month into learning the Elixir programming language. See my previous post for my initial thoughts.
And I don’t hate it. In contrast to my earlier encounter with Haskell, I’m keeping at it, and I’m enjoying it. Because Elixir is not quite as strict as Haskell, it has been way easier to wrap my head around it.
There was a very steep hurdle in the beginning - everything requiring any amount of design beyond “just a single function” was (and to a degree, still is) rather hard. I had single functions down relatively quickly, but building anything that required chaining multiple functions had me blank quite often. I spend an entire Sunday solving this exercise. It should have been relatively simple: Just mark the cells on a minesweeper board with the number of adjacent mines. And the hard part was not the syntax for doing it, but coming up with a design and architecture to solve the task.
Just for illustrative purposes, here’s what I came up with:
defmodule Minesweeper do
@doc """
Annotate empty spots next to mines with the number of mines next to them.
"""
@spec annotate([String.t()]) :: [String.t()]
def annotate([]), do: []
def annotate([""]), do: [""]
def annotate(board) do
fill_board(board)
end
defp create_board(board) do
%{
fields: parse(board, 0),
height: length(board),
width: String.length(Enum.at(board, 0))
}
end
defp parse([], _rownum), do: %{}
defp parse([head|tail], rownum) do
to_charlist(head)
|> Enum.with_index(fn element, index -> {{index,rownum},element} end)
|> Map.new()
|> Map.merge(parse(tail, rownum+1))
end
defp one_if_mine(x,y,boardmap) do
if Map.get(boardmap, {x,y}, ' ') == ?* do
1
else
0
end
end
defp mine_count(x,y,boardmap) do
one_if_mine(x-1,y,boardmap) +
one_if_mine(x-1,y-1,boardmap) +
one_if_mine(x-1,y+1,boardmap) +
one_if_mine(x+1,y,boardmap) +
one_if_mine(x+1,y-1,boardmap) +
one_if_mine(x+1,y+1,boardmap) +
one_if_mine(x,y-1,boardmap) +
one_if_mine(x,y+1,boardmap)
end
defp fill_char(x,y,boardmap) do
cell = Map.get(boardmap, {x,y})
if cell == ?* do
'*'
else
count = mine_count(x,y,boardmap)
if count == 0 do
' '
else
count
end
end
end
defp fill_board(board) do
bm = create_board(board)
Map.to_list(bm.fields)
|> Enum.map(fn {{x,y}, _cell} -> {{x,y},fill_char(x,y,bm.fields)} end)
|> Map.new()
|> to_string(bm.height, bm.width)
end
defp to_string(boardmap, height, width) do
for y <- 0..height-1 do
for x <- 0..width-1 do
Map.get(boardmap, {x,y})
end
|> Enum.join("")
end
|> Enum.to_list()
end
end
It is not particularly pretty, but it still took me quite a while to
arrive at this solution. The most difficult part were the two for
statements
in the to_string
function - because every time I thought “yeah, for loop!”
the other half of my brain screamed: Nonono, that’s imperative thinking. You
must be doing something wrong. Think!
It drove me crazy. Then I realized that I wasn’t actually building a for-loop in a functional language - I was using a comprehension. Not much difference in my mind, but it was enough to get me to using it.
The entire first couple of weeks, I had part of my brain screaming: Nooooooob! It was a really annoying feeling. I could have done these exercises with both my hands tied behind my back in C++ or even C, without having to really think too much. But in Elixir, I stared at an empty screen for an hour or so before I even came up with a first thing to try out.
This feeling of not knowing how to do anything at all really got to me at times. I’ve been programming for something like 15 years now. The last new language I learned was C++, if we discard all the DSLs of the different C++ build systems I’ve been working with. And still I sat in front of an empty screen for way too long for something as simple as a Minesweeper board evaluator. And mind you, the exercise was really just that - marking the fields. Not a full Minesweeper game.
I’m now feeling I’ve taken that initial hurdle - I know approximately what I’m doing, and when I have to look something up, it’s functions in the standard library, not syntax. The next step, going from “I know the syntax” to “I can design a program” is way more difficult.
Which leads me to the core point of this post: My second Elixir book, which I finished this week, was Learn Functional Programming with Elixir. I can warmly recommend it. It would have also served well as a first Elixir book.
I would like to note one specific chapter about it: Chapter six, where you
build a very simple dungeon crawler in Elixir. This chapter is the author’s
vehicle for going through Elixir’s mix
project management tool as well as all
the “more than one source file” topics in an Elixir project. And I absolutely
loved it. It being a dungeon crawler was pretty nice, but the most valuable part
was going beyond simple toy examples.
You had some data structures for storing monster stats and defining rooms. Some separation of concerns between the mechanics and the user frontend. Modules, behaviors (something akin to interfaces in OOP languages) and protocols (polymorphism) were all introduced to implement certain aspects of the game.
And that’s just sometimes missing, from both books and online resources. You do a lot of small stuff, couple of functions, perhaps one or two classes, and that’s it. You never get an example of what a full program looks like, or how it’s organized, both logically and physically, with folder and file structures.
One striking example was my first C++ book I (tried to) read when I was 14 or 15, and C++ was cool (it was the late 90s, okay? 😅). It never got beyond a single C++ file. At the end of the book, I still had no idea about include paths, let alone what a linker does. But Functional programming with Elixir did it right, I feel. Or I’m just better able to fill in the blanks these days. 😉
One thing I’ve found important, at least for my own learning, is being steady doing it. I tried to get into the Go language before Elixir, but never got very far. I mostly read and programmed on the weekends. I found myself having to look up basic syntax, e.g. for function definitions or for-loops, again and again. Not so now, with Elixir, where I’m working more steadily and put my other big hobby, my Homelab, a bit to the side. It’s fascinating how stable it has become once I stopped putting my fingers in it all the time. :grimacing_face:
Currently, I’m working through the standard book, Programming Elixir. I’m not actually sure how well-suited it would be as a first Elixir book. The author goes over the base syntax and data types pretty quickly. I’m feeling somewhat lucky that this is my third Elixir book. Looking at the ToC, most of the book will be spend on Elixir’s/Erlang’s OTP massive parallelization framework.
I’m looking forward to starting my deeper dive into Elixir now.