Elixir for Erlang developers

Igor Kapkov, Evil Martians

Elixir for Erlang developers

Igas

Igor Kapkov

Evil Martians

@igasgeek / [email protected]

History

Why we ♥ Erlang

Why we'll ♥ Elixir

Design Goals

Syntax

Syntax is user interface.

Brendan Eich

Programs must be written for people to read, and only incidentally for machines to execute.

Harold Abelson and Gerald Jay Sussman

Differences

Operator Names

Erlang Elixir Meaning
and NOT AVAILABLE Logical 'and', evaluates both arguments
andalso and Logical 'and', short-circuits
or NOT AVAILABLE Logical 'or', evaluates both arguments
orelse or Logical 'or', short-circuits
=:= === A match operator
=/= !== A negative match
/= != Not equals
=< <= Less than or equals

Types

Variables

Pin Operator / Static Single Assignment

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

Default arguments

        defmodule Concat do
          def join(a, b, sep \\ " ") do
            a <> sep <> b
          end
        end
         
        IO.puts Concat.join("Hello", "world")      #=> Hello world
        IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
      

UTF8

        iex> string = "hełło"
        "hełło"
        iex> byte_size string
        7
        iex> String.length string
        5
        iex> ?a
        97
        iex> 
        322
        iex> String.codepoints("hełło")
        ["h", "e", "ł", "ł", "o"]
      

Function capturing

        iex> Enum.all? [:a, :b, :c], &is_atom(&1)
        true
         
        iex> Enum.map  ["a", "B", "c"], &String.upcase &1
        ["A", "B", "C"]
         
        iex> Enum.sort [1, 2, 3], &( &2 < &1 )
        [3, 2, 1]
         
        iex> fun = &(&1 + &2 + &3)
        ...> fun.(1, 2, 3)
        6
      

Sigils

        ~w(--source test/enum_test.exs) #=> ["--source", "test/enum_test.exs"]
      
        defmodule My do
          defmacro sigil_i(string, []) do
            quote do: for(p <- String.split(unquote(string)), do: String.to_atom(p))
          end
        end
      
        ~i(asdf asdf) #=> [:asdf, :asdf]
      

Structs

        iex> defmodule User do
        ...>   defstruct name: "jose", age: 27
        ...> end
        {:module, User,
         <<70, 79, 82, ...>>, {:__struct__, 0}}
        iex> %User{}
        %User{name: "jose", age: 27}
        iex> %User{name: "eric"}
        %User{name: "eric", age: 27}
      

Exceptions

        File.read("hello.txt")
        #=> {:ok, "World"}
         
        File.read("invalid.txt")
        #=> {:error, :enoent}
         
        File.read!("hello.txt")
        #=> "World"
         
        File.read!("invalid.txt")
        #=> ** (File.Error) could not read file invalid.txt: no such file or directory
      

Pipe operator

        prepare_filing(sales_tax(Orders.for_customers(DB.find_customers), 2013))
      
        DB.find_customers
        |> Orders.for_customers
        |> sales_tax(2013)
        |> prepare_filing
      

Namespaces

        defmodule Foo do
          defmodule Bar do
          end
        end
         
        defmodule Foo.Bar do
        end
      

Comprehensions

        iex> for x <- [1, 2, 3, 4, 5], do: x * x
        [1, 4, 9, 16, 25]
      
        iex> for x <- [1, 2, 3, 4, 5], x < 4, do: x * x
        [1, 4, 9]
      
        iex> for x <- [1, 2], y <- [5, 6], do: x * y
        [5, 6, 10, 12]
      
        iex> for x <- [1, 2], y <- [5, 6], do: {x, y}
        [{1, 5}, {1, 6}, {2, 5}, {2, 6}]
      

Streams

        iex> Enum.map(1..10_000_000, &(&1*&1)) |> Enum.take(5)
        [1, 4, 9, 16, 25]
      
        iex> Stream.map(1..10_000_000, &(&1*&1)) |> Enum.take(5)
        [1, 4, 9, 16, 25]
      
        iex> Stream.iterate(2, &(&1*2)) |> Enum.take(10)
        [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
      
        iex> Stream.unfold({0,1}, fn {f1,f2} -> {f1, {f2, f1+f2}} end)
        ...> |> Enum.take(15)
        [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
      

Alias, Import, Require

DRY

        for {codepoint, _upper, lower, _title} <- codes, lower && lower != codepoint do
          defp do_downcase(unquote(codepoint) <> rest) do
            unquote(:binary.bin_to_list(lower)) ++ downcase(rest)
          end
        end
      

DSL

        defmodule User do
          use Ecto.Model
         
          schema "user" do
            field :name
            field :age, :integer
          end
        end
      
        get "/pages/:page", Controllers.Pages, :show, as: :page
      

Everything is an expression

        iex> defmodule First do
        ...>   IO.puts "Compiling"
        ...>   def hello_world do
        ...>     IO.puts "Hello, world!"
        ...>   end
        ...> end
      
        Compiling
      
        {:module, First,
         <<70, 79, 82, 49, 0, 0, 7, 124, 66, 69, 65, 77, 65, 116, 111, ...>>,
         {:hello_world, 0}}
      

Metaprogramming

Lisp isn't a language, it's a building material.

Alan Kay

Macros

        defmacro left ^^^ right do
          quote do: :erlang.bxor(unquote(left), unquote(right))
        end
      

Protocols

        defprotocol Inspect do
          def inspect(thing, opts)
        end
      
          defimpl Inspect, for: Atom do
            def inspect(false),  do: "false"
            def inspect(true),   do: "true"
            def inspect(nil),    do: "nil"
          end
        
          defimpl Inspect, for: Integer do
            def inspect(thing, _opts) do
              integer_to_binary(thing)
            end
          end
        

ExUnit

        defmodule MathTest do
          use ExUnit.Case
         
          test "basic operations" do
            assert 1 + 1 == 2
          end
        end
      

Docstrings

Doctests

        @doc """
        Checks if the given argument is nil or not.
         
        ## Examples
         
            iex> nil?(1)
            false
         
            iex> nil?(nil)
            true
         
        """
      

IEx / REPL

Also

Interoperability: Erlang ↝ Elixir

        iex> :crypto.rand_uniform(0, 10)
        3
         
        iex> :math.pi
        3.141592653589793
         
        iex> :ets.new(:cats, [:set, :named_table])
        :cats
      

Interoperability: Elixir ↝ Erlang

        1> 'Elixir.String':downcase(<<"ASD">>).
        <<"asd">>
         
        2> Task = 'Elixir.Task':async(fun() -> 1 + 1 end).
        3> 'Elixir.Task':await(Task).
        2
      

Books

Talks

Verdict

I think it’s pretty cool, I think we’re going to have fun together.

Joe Armstrong

Elixir

Igor Kapkov, Evil Martians