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