Charlie Harvey

Seven More Languages: Elixir Day One

Elixir has been getting a lot of interest recently. It's a functional, concurrent language that compiles to bytecode for the Erlang VM. As well as concurrency, it has strong support for metaprogramming with hygenic macros — think Scheme but without all the parentheses.

The syntax is really pretty. If you like how Ruby looks then you’ll find Elixir pleasant to work with. However, the semantics are very different from Ruby.

Cover of introducing elixir

I have played with Elixir a little before when reviewing the O’Reilly book Introducing Elixir, by Simon St. Laurent and J. David Eisenberg, for FLOSSUK — not a bad introduction, incidentally. So I have a bit of an head start.

Elixir doesn’t get in your way. The dynamic type system is refreshing after Elm’s ML-like pernicketyness. It’s a tradeoff, of course; using dynamic types often means you’re storing up debugging pain for later. And there is something undeniably satisfying about how the (presumably monadic) side-effect of wrestling Haskell’s type system into submission can be a program that is in all likliehood correct.

I digress. Elixir is fun to write. If you aren’t yet convinced, have a look at Dave Thomas’s talk Elixir: the power of Erlang, the joy of Ruby which gives a good introduction Elixir and why it is worth being excited about.

Exercises

Easy exercises

Express some geometry objects using tuple: a 2d point, a line, a circle, a polygon, a triangle.

I first of all declared a module to wrap this lot up in and then made a point that was just a 2-tuple of x and y co-ordinate. A line is a pair of points. A circle is a point and a radius. An ngon is a list of connected lines, in this case there are 4 of them. A triangle is the same but can only have 3 lines.defmodule Shapes do import :math point = {1,9} # x,y coords line = {{1,1}, point} # 2 points in cartesian space circle = {{6,6}, 5} # centre point, radius ngon = {line, {point, {15,9}}, {{15,9}, {15,1}}, {{15,1},{1,1}}} # lines between vertices triang = {line, {point,{15,1}}, {{15,1},{1,1}}} # lines between vertices

Write a function to compute the hypotenuse of a right angle triangle given the length of 2 sides.

I wasn’t able to find an exponentiation operator or the square function in the standard libraries (could be just failing google-fu). So I first implemented sq and then implemented my hypo function in terms of sq. I was able to use the sqrt function from the standard :math library, hence the import earlier. I read compute the hypotenuse as compute the length of the hypotenuse and I assumed that neither of the 2 lengths I was handed were the hypotenuse. # First implement square def sq(x) do x*x end # We can use the :math version of sqrt and our own sq to work out # the square root of the sum of the squares of the two non-hypotenuse sides # Which is the length of the hypotenuse. def hypo(a,b) do sqrt(sq(a) + sq(b)) end

Convert a string to an atom.

This seemed kind of trivial. Just use the library function to_atom# Convert string to atom IO.inspect String.to_atom("HalloElixir")

Test to see if an expression is an atom.

Similarly, I just used library functions# test to see if it is an atom IO.inspect is_atom(String.to_atom("HalloElixir"))

Medium exercises

Given a list of numbers, use recursion to find:
1. The size of the list
2. The maximum value
3. The minimum value

This is bread and butter functional programming. Start with a module to wrap stuff in, then implement size, min and max. I prepended c to my implementations to avoid names clashing with the standard libraries.

I use the hd function in the single arg cmin and cmax implementations. If xs is an empty list, this will fail. Otherwise I would have to implement the functions as monoids with an identity of -Infinity for max and Infinity for min. That is what happens in Javascript. I wrote about that in Why is Math.max() less than Math.min()? recently. defmodule Medium do import Keyword # Given a list of numbers use recursion to find: # size of list def csize([]), do: 0 def csize([_|t]), do: 1+csize(t) # max value def cmax(xs), do: cmax(xs,hd xs) def cmax([],n), do: n def cmax([h|t],n) do if(h>n) do cmax(t,h) else cmax(t,n) end end # min value def cmin(xs), do: cmin(xs, hd xs) def cmin([],n), do: n def cmin([h|t],n) do if(h<n) do cmin(t,h) else cmin(t,n) end end

Given a list of atoms, build a function called word_count that returns a keyword list, where the keys are atoms from the list and the values are the number of occurences of that word in the list. For example word_count([:one, :two, :two]) returns [ one: 1, two: 2 ]

Sounds like a fold, no? We use reduce from the Enum library to do the folding and the Keyword.get and Keyword.put to build ourselves a Keyword list. def word_count(xs) do Enum.reduce(xs, [], &(put(&2, &1, 1 + get(&2,&1,0)))) end

Hard exercises

Represent a tree of sentences as tuples. Traverse the tree, presenting an indented list. For example, traverse({"See Spot.",{"See Spot sit.","See Spot run."}}) would return:

See Spot.
  See Spot sit.
  See Spot run.

This was very much the easier of the hard exercises in my opinion. You can use a couple of mutually recursive functions indent and the poorly but briefly named t.defmodule SentenceTree do # Note that this only works for 2-tuples, which I am assuming was # what was intended. # Note also that we print things rather than constructing a list # which is a bit impure. Whatever. def traverse(st) do t(0, st) end def indent(n,x) do if is_tuple x do t(n+1, x) else IO.puts String.duplicate(" ",n) <> x end end def t(n, {x,y}) do indent(n,x) indent(n,y) end end stup = {"See Spot.", {"See Spot sit.", {"See spot run.", "See spot code."}}} SentenceTree.traverse(stup)

Given an incomplete tic-tac-toe board, compute the next player’s best move.

This was significantly harder than the previous exercise. I feel that the two_lines function could be more elegant. But it is done enough to sort of work. I call the game noughts and crosses, UK style. I represent the board as a list of chars that are either 'O', 'X' or ' '.

I first check if the game is over (i.e. the board has no ' 's) and print the result if it is. Next I create a list of ‘strategies’ to try in order of their relative value to the current player. A strategy changes the board if it is applicable. I map them over the board in order pulling out the first that succeeds. Which may mean doing a little extra bit of work, in this case I am not concerned with that because it is so little.

The strategies I try are:

  1. If this player has two pieces in a row, column, or diagonal put a piece in the third position in that row (or column or diagonal).
  2. If the other player has two pieces in a row, column, or diagonal, block them by putting our piece in the third position.
  3. Put this player’s piece in the centre square.
  4. Try the first empty square — which I term the crappy move.

I struggled a bit to recall (or work out) the syntax for higher order functions. Once I had that and pattern matching the problem became easier. I am fairly confident that I could do a better job on the two_lines function, but life is short and so is my spare time. And it is springtime now too. defmodule NoughtsAndCrosses do import Enum import List # check if there is a horizontal, vertical or diagonal line # for player p def winner?(p,[a,b,c,d,e,f,g,h,i]) do any?([ {a,b,c}, {d,e,f}, {g,h,i}, {a,d,g} \ , {b,e,h}, {c,f,i}, {a,e,i}, {c,e,g} ] \ , fn(x)->x=={p,p,p} end) end # define fst and snd. There is probably a builtin for this. def fst ({x,_}) do x end def snd ({_,y}) do y end def other_player(p) do cond do p=="O" -> "X" true -> "O" end end # given an opponent flag, a player p and a board, look for any nearly complete # lines of that player. # If opponent is true, then complete the line with the symbol of the # opposite player of p (for O,X and vice versa) # Otherwise complete the line with p's symbol # Return a modified board # This could undoubtedly be simplified with a lookup for the numbers and 3 tuples # for lines but it works fine for my purposes def two_lines(opponent,p,[a,b,c,d,e,f,g,h,i]) do danger = [ {{a,b},2}, {{b,c},0}, {{d,e},5}, {{e,f},3}, {{g,h},9}, {{h,i},7} \ , {{a,c},1}, {{d,f},4}, {{g,i},8} \ , {{a,d},7}, {{d,g},0}, {{b,e},8}, {{e,h},1}, {{c,f},9}, {{f,i},2} \ , {{a,g},3}, {{b,h},4}, {{c,i},5} \ , {{a,e},9}, {{e,i},0}, {{c,e},7}, {{e,g},2}, {{g,c},4}, {{a,i},4} ] match = find(danger, false, &(fst(&1) == {p,p})) cond do match == nil -> [a,b,c,d,e,f,g,h,i] opponent -> replace_at([a,b,c,d,e,f,g,h,i], snd(match), other_player(p)) true -> replace_at([a,b,c,d,e,f,g,h,i], snd(match), p) end end # is the centre available def centre_free([_,_,_,_,c,_,_,_,_]) do c==' ' end def try_own_twos do fn(player,board) -> two_lines(false,player,board) end end def try_opponent_twos do fn(player,board) -> two_lines(true,player,board) end end # Go in the centre square def try_centre do fn(player,board) -> if(centre_free(board)) do replace_at(board,4,player) else board end end end # take the first empty square def try_crappy_move do fn(player,board) -> replace_at(board,find_index(board, &(&1==' ')),player) end end def show_result(player,board) do oppo = other_player(player) cond do winner?(player, board) -> IO.puts(player <> " Wins!") winner?(oppo, board) -> IO.puts(oppo <> " Wins!") true -> IO.puts("A strange game. The only winning move is not to play. How about a nice game of chess?") end end def board_complete?(board) do not any?(board, &(&1==' ')) end def next_move(player, board) do if board_complete?(board) do show_result(player, board) board else strategies = [try_own_twos, try_opponent_twos, try_centre, try_crappy_move] find(map(strategies, &(&1.(player,board))), nil, &(&1!=board)) end end end testBoard = [ 'X', ' ', 'X' \ , 'O', ' ', 'X' \ , 'X', 'O', 'O' ] IO.inspect NoughtsAndCrosses.winner?('X',testBoard) IO.inspect NoughtsAndCrosses.next_move('X',testBoard)

My experiences

There is a lot to like in Elixir.

The syntax is just sugary enough not to get in your way, the libraries feel sensible and well throught out, the docs are well written and have examples. Dynamic typing won’t suit some programmers, but I found it pleasant enough not to worry about type errors for a bit.

The areas where Elixir really shines, in particular its support for metaprogramming through hygenic macros and its concurrency model and fault tolerance have yet to be touched on. Maybe in day two.


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.