Typing in Elixir

Alex Rozumii, Toptal

Elixir Club 6, Kyiv

What is this talk about?

Basic data types

Arithmetic

iex> 1 + 1
2

iex> 1 + 1.5
2.5

iex> 1.5 + 1.5
3.0

Atoms

iex> :hello
:hello

iex> :hello == :world
false

iex> is_integer(:one)
false

iex> is_boolean(true)
true

Boolean

iex> true && false
false

iex> true || false
true

iex> is_atom(true)
true

iex> false == :false
true

iex> true == :false
false

String/Binary

iex> name = "リック"
"リック"

iex> "Greetings, #{name}"
"Greetings, リック"

iex> byte_size name
9

iex> String.length name
3

Lists / Char lists

iex> [:Kyiv, "Elixir", 'Club']
[:Kyiv, "Elixir", 'Club']

iex> [75, 121, 105, 118]
'Kyiv'

iex> 'Kyiv' == "Kyiv"
false

iex> ['E', 432123534]
['E', 432123534]

iex> [1 | []]
[1]

Tuples

iex> {1,2,3}
{1, 2, 3}

iex> File.read "/etc/ntp.conf"
{:ok, "server time.apple.com.\n"}

iex> elem({:ok, 321}, 1)
321

iex> tuple_size {:ok, "hello"}
2

Anonymous functions

iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>

iex> add.(1, 2)
3

iex> is_function(add)
true

Not so basic built in data types

Keyword lists / Maps

Maps vs Keyword lists

iex> keyword_list = [{:user, "Homer"}, {:message, "I want donuts"}]
[user: "Homer", message: "I want donuts"]

iex> Map.new keyword_list
%{message: "I want donuts", user: "Homer"}

iex> List.first keyword_list
{:user, "Homer"}

iex> map = Map.new keyword_list
%{message: "I want donuts", user: "Homer"}

iex> map[:user]
"Homer"

Date / Time / DateTime

iex> Date.utc_today
~D[2017-03-10]

iex> Time.utc_now
~T[12:07:23.037483]

iex> DateTime.utc_now
%DateTime{calendar: Calendar.ISO, day: 10, hour: 12, 
 microsecond: {146776, 6}, minute: 7, month: 3, 
 second: 31, std_offset: 0, time_zone: "Etc/UTC",
 utc_offset: 0, year: 2017, zone_abbr: "UTC"}

Regex

iex> Regex.split(~r/\s+/, "/pizzabot --again order  great")
["/pizzabot", "--again", "order", "great"]

Range

iex> 1..10 |> Enum.each(&{IO.puts &1})
1
2
3
...

Thank you for listening.

Any questions?

Structs (e.g. Custom types)

Bare maps underneath

Bare maps underneath

iex> :maps.keys(1..100000)
[:__struct__, :first, :last]

iex> Map.keys(1..100000)
[:__struct__, :first, :last]

iex> (1..10).__struct__
Range

iex> Map.delete (1..1000000000), :__struct__
%{first: 1, last: 1000000000}

iex> Map.from_struct(1..1000)
%{first: 1, last: 1000}

defstruct

defmodule ChatCommand do
  defstruct user: nil, message: ""
end

iex> %ChatCommand{}
%ChatCommand{message: "", user: nil}

iex> %ChatCommand{user: "Batman"}
%ChatCommand{message: "", user: "Batman"}

iex> %ChatCommand{user: "Batman", message: "I love black!"}
%ChatCommand{message: "I love black!", user: "Batman"}

OOP principles

OOP principles

  • Inheritance

OOP principles

  • Inheritance

OOP principles

  • Inheritance
  • Encapsulation

OOP principles

  • Inheritance
  • Encapsulation

OOP principles

  • Inheritance
  • Encapsulation
  • Polymorphism

OOP principles

  • Inheritance
  • Encapsulation
  • Polymorphism ...

Protocols

Protocols

defprotocol Bot.Sendable do
  def to_message(data)
end

defimpl Bot.Sendable, for: ChatCommand do
  def to_message(data) do
    "<#{data.user}> #{data.message}"
  end
end

msg = %ChatCommand{user: "Batman", message: "I love black!"}

iex> Bot.Sendable.to_message msg
"<Batman> I love black!"

iex> Bot.Sendable.to_message "ADSF"
** (Protocol.UndefinedError) protocol Bot.Sendable 
    not implemented for "ADSF"
    iex:109: Bot.Sendable.impl_for!/1
    iex:110: Bot.Sendable.to_message/1

Standard protocols

iex> to_string msg
** (Protocol.UndefinedError) protocol String.Chars not 
    implemented for %ChatCommand
    (elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
    (elixir) lib/string/chars.ex:17: String.Chars.to_string/1

defimpl String.Chars, for: ChatCommand do
  def to_string(data) do
    data.message
  end
end

iex> to_string msg
"I love black!"

Specifications

Specifications

defmodule ChatCommand do
  @type t :: %ChatCommand{user: String.t, text: String.t}
  defstruct user: nil, message: ""
end

> elixir chatbot.ex
** (CompileError) chatbot.ex:2: undefined field text on struct ChatCommand
    (elixir) lib/kernel/typespec.ex:1012: Kernel.Typespec.compile_error/2
    (stdlib) lists.erl:1338: :lists.foreach/2
    (elixir) lib/kernel/typespec.ex:811: Kernel.Typespec.typespec/3
    (elixir) lib/kernel/typespec.ex:400: Kernel.Typespec.translate_type/3
    chatbot.ex:1: (file)

Specifications

defmodule Messenger do
  @spec repeat(integer, %ChatCommand{user: String.t, message: String.t})
  def repeat(times, msg) do
  end
end

> elixir chatbot.ex
** (CompileError) chatbot.ex:27: type specification missing return 
    type: repeat(integer, %ChatCommand{user: String.t(), message: String.t()})
    (elixir) lib/kernel/typespec.ex:1012: Kernel.Typespec.compile_error/2
    (elixir) lib/code.ex:370: Code.require_file/2

Specifications

defmodule Messenger do
  @spec repeat(%ChatCommand{user: String.t, message: String.t}) :: String.t
  def repeat(times, msg) do
  end
end

> elixir chatbot.ex
** (CompileError) chatbot.ex:27: spec for undefined function repeat/1
    (stdlib) lists.erl:1338: :lists.foreach/2
    (elixir) lib/code.ex:370: Code.require_file/2

Specifications

defmodule Messenger do
  @spec repeat(integer, %ChatCommand{user: String.t, 
        message: String.t}) :: String.t

  def repeat(times, msg) do
  end
end

dialyzer

Behaviours

Behaviours

defmodule Plug.Session.Store do
  @moduledoc """
  Specification for session stores.
  """
  @type sid :: term | nil
  @type cookie :: binary
  @type session :: map

  @callback init(Plug.opts) :: Plug.opts
  @callback get(Plug.Conn.t, cookie, Plug.opts) :: {sid, session}
  @callback put(Plug.Conn.t, sid, any, Plug.opts) :: cookie
  @callback delete(Plug.Conn.t, sid, Plug.opts) :: :ok
end

Behaviours

defmodule Plug.Session.COOKIE do
  @behaviour Plug.Session.Store
  def init(opts) do
    encryption_salt = opts[:encryption_salt]
    ....
    %{key_opts: key_opts,
      serializer: serializer,
      log: log}
  end

  def get(conn, cookie, opts) do
    %{key_opts: key_opts, log: log, serializer: serializer} = opts
    ...
  end

  def put(conn, _sid, term, opts) do
    %{serializer: serializer, key_opts: key_opts} = opts
    ....
  end

  def delete(_conn, _sid, _opts) do
    :ok
  end
end

Time for questions

Thank you for listening

Typing in Elixir

By Alex Rozumii

Loading comments...

More from Alex Rozumii