AddThis

Monday, August 29, 2016

First Sip of Elixir and Beer

Trying out Elixir

I've always been meaning to try out some Elixir but wasn't until I chatted a bit with José Valim about it until my curiosity was really piqued. I love the idea of embracing failure and not worry about catching and trying to handle exceptional cases. After all, this is what exceptions are, exceptional things. Just let the process die and rely on the framework to spin another one up. Beautiful! Additionally, add in the fact that it's a functional language, really makes it shine. Pattern matching and data transformation are so nice. Today I'll talk a bit about how to get started with Elixir and a bit about building a command line program. I chose to solve the "99 bottles of beer" problem, which is essentially just to print out the lyrics to the 99 bottles of beer song.

First installation of Elixir and project setup.

  # install Elixir, mac specific instructions
  brew install Elixir
  
  # create a new project, I named it elixir_99_bottles
  mix new elixir_99_bottles

Now a bit of setup to turn this into a command line tool. First open up mix.exs.

# A defmodule is sort of like a `class` in the object oriented world.
# But since this is a functional language, this is really more like a
# collection of related functions.
defmodule Elixir99Bottles.Mixfile do
  use Mix.Project
  ...

  # add the escript line.  
  # this tells elixir that this is a command line tool.
  # and that to invoke it, the escript_config method must be called
  def project do
    [app: :elixir_99_bottles,
     version: "0.1.0",
     elixir: "~> 1.3",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     escript: escript_config,
     deps: deps()]
  end

  ...

  # Notice that it's defp - meaning its a private function.
  # Compare that with def, which means a public function.
  defp escript_config do
    [main_module: Elixir99Bottles]
  end

end

Next bit of magic, go to lib/elixir_99_bottles.ex. Here you need to add a special main method that will serve as the entry point.

defmodule Elixir99Bottles do

  # entry point
  def main(args) do
    # args is a list, so we grab the first element and "set" that into string_val
    # if there are no args, we set string_val to 99
    string_val = List.first(args) || "99"

    # we want an integer, so we parse it.
    # the return is actually a tuple, which is a nice set of numbers.  
    # kind of like how x and y coordinates on a map make a nice tuple.
    # a tuple is a finite list of things that all logically belong together
    # the return tuple here is actually the { parsed integer, remained after parsing }.
    # but since we are using base 10, all our integers will parse nicely
    # `start` will be set to the parsed integer
    {start, _} = Integer.parse(string_val)

    # loop over the entered `start` value down to 1, i will be the iterator
    for i <- start..1
      do 
        IO.puts format i
      end
  end
  
  # when the num argument is 1, call this function and return this interpolated string
  defp format(num) when num == 1 do
    line_one = "#{num} bottle of beer on the wall, #{num} bottle of beer.\n"
    line_two = "Go to the store and buy some more, 99 bottles of beer on the wall."
    line_one <> line_two
  end

  # when the num argument is more than 1, call this function and return this interpolated string
  defp format(num) when num > 1 do
    line_one = "#{num} bottles of beer on the wall, #{num} bottles of beer.\n"
    line_two = "Take one down and pass it around, #{num-1} bottles of beer on the wall.\n"
    line_one <> line_two
  end

end

Now lastly we add some tests. Open up test/elixir_99_bottles_test.exs.

defmodule Elixir99BottlesTest do
  use ExUnit.Case
  doctest Elixir99Bottles

  # this import allows us to capture and compare things printed to standard out
  import ExUnit.CaptureIO

  # first test is when we have an input of 1.
  # notice we call our class with a list of "1" and compare that with our output
  # """ -> this is a heredoc where you can create multiline strings.  
  # """ starts and ends the multiline string.
  test "1 line" do
    assert capture_io(fn ->
      Elixir99Bottles.main(["1"])
    end) == """
    1 bottle of beer on the wall, 1 bottle of beer.
    Go to the store and buy some more, 99 bottles of beer on the wall.
    """
  end

  # first test is when we have an input of 2.
  # notice we call our class with a list of "2" and compare that with our output
  test "more than 1 line" do
    assert capture_io(fn ->
      Elixir99Bottles.main(["2"])
    end) == """
    2 bottles of beer on the wall, 2 bottles of beer.
    Take one down and pass it around, 1 bottles of beer on the wall.\n
    1 bottle of beer on the wall, 1 bottle of beer.
    Go to the store and buy some more, 99 bottles of beer on the wall.
    """
  end
end

Now, time to run it all!

  # compile the code
  mix escript.build

  # run it.  defaults to 99
  ./elixir_99_bottles 

  # run it for 50 beers
  ./elixir_99_bottles 50

  # run the tests
  mix test

Boom! A very simple demonstration of using mix to create an Elixir project complete with tests. Check out the code here:
https://github.com/jkeam/elixir_99_bottles