Daniel Perez

CTO @ClaudeTech

danhper

https://daniel.perez.sh/talks/2016/elixir-1-3

Blog article available at https://daniel.perez.sh/blog/2016/elixir-1-3/

Elixir experience

Today's topic

What's new in Elixir 1.3


Target audience

  • Familiar with Elixir
  • More or less aware of 1.2 features

Elixir 1.3

  • Deprecation of imperative assignment
  • with improvements
  • Date datatypes
  • Plenty of other stuff

How to try it

Normal Install

git clone https://github.com/elixir-lang/elixir.git
cd elixir
make
./bin/iex

With kiex

kiex install master
source $HOME/.kiex/elixirs/elixir-master.env
iex

Deprecation of imperative assignments

From 1.2, conditional variable declaration has been deprecated.

if condition do
  n = 1
else
  n = 2
end
IO.puts(n)

should be written as

n = if condition do
      1
    else
      2
    end
IO.puts(n)

Deprecation of imperative assignments

From 1.3 this code will also give a warning

def imperative_duplicate(str, n, opts \\ []) do
  if opts[:double] do
    n = n * 2
  end
  String.duplicate(str, n)
end

# warning: the variable "n" is unsafe as it has been set in a conditional clause

we can refactor

def functional_duplicate(str, n, opts \\ []) do
  n = double_if_true(n, opts[:double])
  String.duplicate(str, n)
end

defp double_if_true(n, true), do: n * 2
defp double_if_true(n, false), do: n

But why?

Compare

x = 1
with :ok <- :ok do
  x = 2
end
IO.puts(x) # 1

and

x = 1
case :ok do
  :ok -> x = 2
end
IO.puts(x) # 2

case, cond, receive scope leaks
try, with, for scope do not

Towards new scoping rules (Elixir 2.0)

Scope will be consistent, no leaking

x = 1
case :ok do
  :ok -> x = 2
end
IO.puts(x) # 1

Same goes for receive, cond, and constructs derived, such as if

What does this give us?

  • Pros
    • Consistency
    • Code easier to understand
    • Code easier to refactor
  • Cons
    • Code sometimes a little verbose

with improvements

The new special form with has been introduced in 1.2

case HTTPoison.get(url) do
  {:ok, %HTTPoison{body: body}} ->
    case Poison.decode(body) do
      {:ok, data} ->
        case process_data(data) do
          {:ok, _processed} = result -> result
          {:error, _err} = error -> error
        end
      {:error, _err} = error -> error
    end
  {:error, _err} = error -> error
end

becomes

with {:ok, %HTTPoison.Response{body: body}} <- HTTPoison.get(url),
     {:ok,  data} <- Poison.decode(body),
     {:ok, _processed} = result <- process_data(data) do
  result
end

with improvements

Improvements in 1.3

  • Addition of an else clause
  • Possibility to use guards

Addition of an else clause

We want to rewrite

case create_organization(params) do
  {:ok, organization} ->
    case create_address(organization, params) do
      {:ok, address} ->
        %{organization | address: address}
      {:error, changeset} -> {:error, changeset |> error_string}
    end
  {:error, changeset} -> {:error, changeset |> error_string}
end

Addition of an else clause

Without with else clause

with {:ok, organization} <- create_organization(params),
     {:ok, address}      <- create_address(organization, params) do
  {:ok, %{organization | address: address}}
end
|> case do
     {:ok, organization} -> {:ok, organization}
     {:error, changeset} -> {:error, changeset |> error_string}
   end

Addition of an else clause

With with else clause

with {:ok, organization} <- create_organization(params),
     {:ok, address}      <- create_address(organization, params) do
  {:ok, %{organization | address: address}}
else
  {:error, changeset} -> {:error, changeset |> error_string}
end

Support for guards in with

We can rewrite

case HTTPoison.get(url) do
  {:ok, %Response{status_code: code, body: body}} when code in 200..299 ->
    case Poison.decode(body) do
      {:ok, data} -> data
      _           -> :error
    end
  _ -> :error
end

into

with {:ok, %Response{status_code: code, body: body}}
        when code in 200..299 <- HTTPoison.get(url),
     {:ok, data} <- Poison.decode(body) do
  data
else
  _ -> :error
end

Date datatypes

Before Elixir 1.3, two main libraries for time/date

Both are great, but not (easily) interoperable

Date datatypes

Elixir 1.3 introduces the following types

  • Calendar
  • Date
  • Time
  • NaiveDateTime (without timezone)
  • DateTime (with timezone)

and will provide a ISO8601 compatible implementation

escript installation tasks

mix escript.build produces a single easy to run binary

In Elixir 1.3, tasks to manage escripts have been added

  • mix escript
  • mix escript.install URL
  • mix escript.uninstall NAME

One liner in README

mix escript.install https://example.com/my-escript

ExUnit pretty diff

Before Elixir 1.3

ExUnit pretty diff

From Elixir 1.3

Make compiler

Before 1.3 it was a little complicated to run make

With Elixir 1.3, simply add :make to compilers

defmodule MyApp.Mixfile do
  def project do
    [app: my_app,
     compilers: [:make] ++ Mix.compilers]
  end
end

Changes in defdelegate

Before 1.3 we could write this

defdelegate [reverse(list), map(list, callback)], to: :lists, append_first: true

This is now deprecated:

warning: passing a list to Kernel.defdelegate/2 is deprecated,
         please define each delegate separately
warning: Kernel.defdelegate/2 :append_first option is deprecated

Changes in defdelegate

Before 1.3 we had to write

defdelegate start_session,            to: Hound.Helpers.Session
defdelegate start_session(opts),      to: Hound.Helpers.Session

From 1.3, we can write

defdelegate start_session(opts \\ []), to: Hound.Helpers.Session

Addition of Process.sleep

Before 1.3, we could achieve this with :timer.sleep
Probably added mainly for documentation:
we usually do not need it

Process.sleep alternatives

Bad

Task.async fn ->
  do_something()
end
Process.sleep(30_000)

Good

parent = self()
Task.async fn ->
  do_something()
  send parent, :work_done
end
receive do
  :work_done -> :ok
after
  30_000 -> :timeout
end

Process.sleep alternatives

Bad

Task.start fn ->
  do_something()
end
# Wait until task terminates
Process.sleep(30_000)
# Check if process is alive

Good

{:ok, pid} = Task.start fn ->
  do_something()
end
ref = Process.monitor(pid)
receive do
  {:DOWN, ^ref, _, _, _} -> :task_is_down
after
  30_000 -> :timeout
end

optionalcallback and optionalmacrocallback

  • Possible to achieve something similar with defoverridable
  • Added for better Erlang interoperability
  • Still in discussion in #4037

app.tree and deps.tree mix tasks

$ mix deps.tree
foobar
├── gettext ~> 0.9 (Hex package)
├── cowboy ~> 1.0 (Hex package)
│   ├── cowlib ~> 1.0.0 (Hex package)
│   └── ranch ~> 1.0 (Hex package)
├── phoenix_html ~> 2.4 (Hex package)
│   └── plug ~> 0.13 or ~> 1.0 (Hex package)
│       └── cowboy ~> 1.0 (Hex package)
├── phoenix ~> 1.1.4 (Hex package)
│   ├── poison ~> 1.5 or ~> 2.0 (Hex package)
│   ├── plug ~> 1.0 (Hex package)
│   │   └── cowboy ~> 1.0 (Hex package)
│   └── cowboy ~> 1.0 (Hex package)
├── phoenix_live_reload ~> 1.0 (Hex package)
│   ├── phoenix ~> 0.16 or ~> 1.0 (Hex package)
│   └── fs ~> 0.9.1 (Hex package)
├── postgrex >= 0.0.0 (Hex package)
│   ├── decimal ~> 1.0 (Hex package)
│   ├── db_connection ~> 0.2 (Hex package)
│   │   ├── poolboy ~> 1.5 (Hex package)
│   │   └── connection ~> 1.0.2 (Hex package)
│   └── connection ~> 1.0 (Hex package)
└── phoenix_ecto ~> 2.0 (Hex package)
    ├── poison ~> 1.3 (Hex package)
    ├── phoenix_html ~> 2.2 (Hex package)
    └── ecto ~> 1.1.2 (Hex package)
        ├── postgrex ~> 0.11.0 (Hex package)
        ├── poolboy ~> 1.4 (Hex package)
        ├── poison ~> 1.0 (Hex package)
        └── decimal ~> 1.0 (Hex package)

app.tree and deps.tree mix tasks

$ mix app.tree
foobar
├── elixir
├── phoenix
│   ├── elixir
│   ├── plug
│   │   ├── elixir
│   │   ├── crypto
│   │   └── logger
│   │       └── elixir
│   ├── poison
│   │   └── elixir
│   ├── logger
│   │   └── elixir
│   └── eex
│       └── elixir
├── phoenix_html
│   ├── elixir
│   ├── logger
│   │   └── elixir
│   └── plug
│       ├── elixir
│       ├── crypto
│       └── logger
│           └── elixir
├── cowboy
│   ├── ranch
│   ├── cowlib
│   │   └── crypto
│   └── crypto
├── logger
│   └── elixir
├── gettext
│   ├── elixir
│   └── logger
│       └── elixir
├── phoenix_ecto
│   ├── elixir
│   ├── logger
│   │   └── elixir
│   └── ecto
│       ├── elixir
│       ├── logger
│       │   └── elixir
│       ├── decimal
│       │   └── elixir
│       └── poolboy
└── postgrex
├── elixir
├── logger
│   └── elixir
├── db_connection
│   ├── elixir
│   ├── logger
│   │   └── elixir
│   └── connection
│       └── elixir
└── decimal
└── elixir

When can we use it?

Soon enough?