Victor.Arias

Starting with Elixir

I’m writing this post to publicize the material I’ve wrote to help organizers to setup a coding dojo in Elixir to a group of non-Elixir programmers. The material below (and in this repo) exposes the basics of the language – just enough to starting coding. I hope this can help people to begin programming with this great language.

DISCLOSURE: The material is mainly working code. Non-working code is found within comments. The material is intentionally simple and non-exhaustive, and requires previous knowledge of functional programming.

Basics

# because everything here is working code, is strongly recommended that
# this "guide" should be read alongside the Elixir interactive mode -> iex

# "assignment"

a = 1 # => 1
a + 4 # => 5

1 = a # => 1

2 = a # => ** (MatchError) no match of right hand side value: 1

# wtf? '=' isn't an assignment; it's a kind of "assert"

list = [1, 10, 20] # => [1, 10, 20]
[ a, b, c] = list # => a = 1; b = 10; c = 20;

# assert works here too

[z, 10, x] = list # => z = 1; x = 20;
[h, 15, j] = list # => ** (MatchError) no match of right hand side value: [1, 10, 20]

# "Joe Armstrong, the creator of Erlang, compares the equals sign in Erlang to that used in algebra.
# When you write the equation x = a + 1, you are not assigning the value of a + 1 to x.
# Instead, you’re simply asserting that the expressions x and a+1 have the same value.
# If you know the value of x, you can work out the value of a, and vice versa."
# - Dave Thomas - Programming Elixir

# Lists

list = [1, 10, 20] # => [1, 10, 20]

# Lists are immutable linked lists, so random access is expensive,
# however, they can be constructed or deconstructed with pattern matching

[head | tail] = list # => head = 1; tail = [10, 20]

# Lists Operations

[1, 2, 3] ++ [4, 5, 6]  # => [1, 2, 3, 4, 5, 6]
[1, 2, 3, 4] -- [1, 3]  # => [2, 4]
1 in [1, 2, 3]          # => true

# Tuples

tuple = { 10, 20 }

# pattern matching on tuples

{ a, b } = tuple

# Atoms

# atoms are constants - they are like symbols on Ruby.
# an atom is constant within the application and across machines

{ a, b } = { :foo, :bar }

# Keyword lists

[ fuu: "bar", goos: "fraba", life: 42 ] # => [ {:fuu, "bar"}, { :goos, "fraba" }, { :life, 42 } ]

Anonymous Functions

# anonymous Functions

add = fn a, b -> a + b end

add.(1, 2) # => 1 + 2

# parameters pattern matching, aka "overloading"

printer = fn
  0, 0, _ -> "FizzBuzz"
  0, _, _ -> "Fizz"
  _, 0, _ -> "Buzz"
  _, _, c -> c
end

printer = fn n ->
  printer.(rem(n, 3), rem(n, 5), n)
  |> to_string
  |> IO.puts
end

fizzbuzz = fn ->
  1..15 |> Enum.each printer
end

# the & notation

# the following function
Enum.map [1, 2, 3], fn n -> n * n end
# is the same as
Enum.map [1, 2, 3], &1 * &1

# the following function
add = fn a, b -> a + b end
# is also the same as
add = &1 + &2

# but the & notation requires some attention
# the following scenario is tricky
add_and_sum = { &1 + &2, &1 * &2 }  # => {&Kernel.+/2, &Kernel.*/2}
# the above function isn't what you expect
# what you really want is
add_and_sum = &{ &1 + &2, &1 * &2 } # => #Function<12.80484245 in :erl_eval.expr/5>

Modules and Functions

# modules

# defmodule ModuleName do
#   ... content inside module...
# end

# this is a module written with the "real" way of declaring a function

defmodule Factorial do
  def of(0), do: 1
  def of(n), do: n * of(n - 1)
end

# the module below is the same as the above, but using an alternative
# syntax that is transformed during compile time
# to the syntax on the previous example

defmodule Factorial2 do
  def of(0) do
    1
  end
  def of(n) do
    n * of(n - 1)
  end
end

# pattern matching on functions happens inside modules
# and is a great way to simplify solutions

defmodule GCD do
  def of(x, 0), do: x
  def of(x, y), do: of(y, rem(x, y))
end

# guard clauses are also supported

defmodule Factorial do
  def of(0), do: 1
  def of(n) when n > 0, do: n * of(n - 1)
end
# Factorial.of(-1)
# ** (FunctionClauseError) no function clause matching in Factorial.of/1

Erlang Interop

# erlang interop

:io.format("~.2f~n", [5.126])
# => 5.13
# => :ok

# module 'io' from Erlang's stdlib is accessible through an atom with the same name
# so, as you can imagine, there is an io module with the function format in it :)

Built-in data structures

dict = HashDict.new first: "Victor", last: "Arias",
  country: "Sweden", music: "Lungs from Florence + The Machine"

# Dict protocol
Dict.get dict, :music # => "Lungs from Florence + The Machine"
Dict.keys dict # => [:country, :first, :last, :music]
Dict.values dict # => ["Sweden", "Victor", "Arias", "Lungs from Florence + The Machine"]

# more in http://elixir-lang.org/docs/stable/Dict.html

set = HashSet.new [:first, :last, :country, :music]
set2 = HashSet.new [:city, :state]

# Set protocol

Set.member? set, :country # => true
Set.union set, set2 # => #HashSet<[:city, :country, :first, :last, :music, :state]>
Set.difference set, set2 # => #HashSet<[:country, :first, :last, :music]>

# more in http://elixir-lang.org/docs/stable/Set.html

# List

List.concat [1, 2], [10, 11] # => [1, 2, 10, 11]
List.flatten [[1], 2, [[[3]]]] # => [1, 2, 3]

# fold(l|r), zip, unzip, last, keyfind, keydelete, keyreplace
# more in http://elixir-lang.org/docs/stable/List.html

# Enum & Stream

Enum.each ["a", "b", "c"], IO.puts(&1)
Enum.all? [2, 4, 6], &(rem(&1, 2) == 0)
Enum.sort [1, 10, 2, 42, 17] # => [1, 2, 10, 17, 42]

List Comprehensions

list = lc x inlist [1, 2, 3], do: x - 1 # => [0, 1, 2]

list = lc x inlist [1, 2, 3], y inlist [:a, :b, :c], do: { x, y }
# => [{1, :a}, {1, :b}, {1, :c}, {2, :a}, {2, :b}, {2, :c}, {3, :a}, {3, :b}, {3, :c}]

list = lc x inlist [1, 2, 3], y inlist [:a, :b, :c], x > 2, do: { x, y }
# => [{3, :a}, {3, :b}, {3, :c}]

Records

defrecord Person, first_name: nil, last_name: nil, age: nil, country: "Brazil"

person = Person.new first_name: "Victor", last_name: "Arias", age: 26
# => Person[first_name: "Victor", last_name: "Arias", age: 26, country: "Brazil"]

Person[first_name: "Victor", last_name: "Arias", country: "Sweden"] # <- compile time!
# => Person[first_name: "Victor", last_name: "Arias", age: nil, country: "Sweden"]

# a record is just a tuple starting with a module
# so the following tuple declaration works as a record creation
# exactly as Person.new ...
another_person = {Person, "Victor", "Arias", 26, "Brazil"}
# => Person[first_name: "Victor", last_name: "Arias", age: 26, country: "Brazil"]

Mix

# mix is a command line utility to manage Elixir projects.
# it can be seen as a 'mix' between rake (db:|routes...) and rails (s|g|...)

# victorarias@Victors-MacBook-Pro ~/development/starting_with_elixir $ mix help
# mix                 # Run the default task (current: mix run)
# mix archive         # Archive this project into a .ez file
# mix clean           # Clean generated application files
# mix cmd             # Executes the given command
# mix compile         # Compile source files
# mix deps            # List dependencies and their status
# mix deps.clean      # Remove the given dependencies' files
# mix deps.compile    # Compile dependencies
# mix deps.get        # Get all out of date dependencies
# mix deps.unlock     # Unlock the given dependencies
# mix deps.update     # Update the given dependencies
# mix do              # Executes the tasks separated by comma
# mix escriptize      # Generates an escript for the project
# mix help            # Print help information for tasks
# mix local           # List local tasks
# mix local.install   # Install a task or an archive locally
# mix local.rebar     # Install rebar locally
# mix local.uninstall # Uninstall local tasks or archives
# mix new             # Creates a new Elixir project
# mix run             # Run the given file or expression
# mix test            # Run a project's tests

Testing

# Elixir bundles a simple yet effective testing library
# is worth noting that the 'assert' used below
# is actually a macro, and because of that
# it can "see" the code being asserted
# and print meaningful error messages when the assert fails

ExUnit.start

defmodule Tests do
  use ExUnit.Case

  test "fuu" do
    assert true
  end

  test "introspection" do
    assert 1 == 2
  end

  test "introspection 2" do
    assert 1 > 3
  end

  test "introspection 3" do
    assert 1 >= 3
  end
end

#  1) test introspection (Tests)
#     ** (ExUnit.ExpectationError)
#                  expected: 1
#       to be equal to (==): 2
#     at 9_testing.exs:11
#
#  2) test introspection 2 (Tests)
#     ** (ExUnit.ExpectationError)
#              expected: 1
#       to be more than: 3
#     at 9_testing.exs:15
#
#  3) test introspection 3 (Tests)
#     ** (ExUnit.ExpectationError)
#                          expected: 1
#       to be more than or equal to: 3
#     at 9_testing.exs:19

Any feedback is appreciated :)

comments powered by Disqus