Skip to content

Instantly share code, notes, and snippets.

@manpages
Last active December 11, 2015 22:39
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manpages/4671447 to your computer and use it in GitHub Desktop.
Save manpages/4671447 to your computer and use it in GitHub Desktop.
Elixir Newbie Digest, Issue 2

Intro

Today I'll cover three basic topics:

  • Mistakes and misuses every Erlanger does when he starts to write in Elixir

  • Dependencies and Mix

  • Writing OTP-enabled Elixir applications

As I stated in Issue #1: I'm focusing on things that in my opinion can help new Elixir developer before he or she starts her first Elixir production project.

The case for case

We, erlangers, do love our case expressions. We use those here and there and (what seems strange to Erlang newcomers) we almost always use those instead of somehow clumsy if expression.

In Elixir -- on the other hand -- if is really useful. As a rule of thumb, when you want to check if a boolean value is true, you should use if, not case.

But wait, there's more!

Elixir introduces concept of truthy and falsy values.

Everything is truthy but :false (which is equal to false, observe: iex(1)> false == :false #=> true) and nil. The later is a very special value in Elixir. It is used by its constructs in implicit returns, observe:

iex(2)> :nil == nil #nil is atom :nil
true

iex(4)> f = fn() -> end; f.() #function with no body implicitly returns nil
nil

iex(6)> if false, do: :nevermore #nil is returned implicitly from an if with no else
nil

Let's now compare how if behaves in Erlang and Elixir:

2> if ok -> shmoo; true -> con end.
con
iex(7)> if :ok, do: :shmoo, else: :con
:shmoo

As you see, as in Elixir we treat atoms as truthy values, :shmoo is returned while in Erlang atom ok is not a boolean, it matches fallback branch and returns con.

Hopefully that will remove if-anxiety and give you understanding of how to use this conditional operator in your code.

Now let's talk about case in Elixir. One thing to remember is that Elixir allows multiple variable assignment, observe:

iex(8)> a = :ok       
:ok
iex(9)> a = :notok
:notok

Thus, by default case assigns value to the match expression and when you face that for the first time you can find yourself in a tough spot:

iex(1)> a = :should_be_this
:should_be_this
iex(2)> b = a
:should_be_this
iex(3)> case b do 
...(3)>  a -> :expected
...(3)>  :should_be_this -> :unexpected
...(3)> end
:expected
iex(4)> a
:should_be_this
iex(5)> b
:should_be_this
iex(6)> b = :shouldnt_be_this
:shouldnt_be_this
iex(7)> case b do
...(7)>  a -> :unexpected
...(7)>  :shouldnt_be_this -> :expected
...(7)> end
:unexpected
iex(8)> a
:shouldnt_be_this
iex(9)> b
:shouldnt_be_this
iex(10)> a == :should_be_this
false

So the bottom line: when you want to pattern-match, don't forget the cap ^:

iex(11)> a = :should_be_this
:should_be_this 
iex(12)> b = :not_a 
:not_a
iex(13)> case b do
...(13)>  ^a -> "#{a} matched #{b}" 
...(13)>  _  -> "#{a} didn't match #{b}"
...(13)> end
"should_be_this didn't match not_a"

Also -- the mistake I always make -- writing case ... of instead of case ... do can produce not-fun-at-all debugging session, so once you get unexpected end error that's close to the end of module definition or to the end of file, look up your sources for case ... of ... end typos.

Long story short

Okay, so, this one is a really short hint. As for today, Mix can't fetch rebar deps, so you should put them into your mix.exs explicitly.

Indent those to show that those are deps like Mr. Yurii Rashkovskii does:

  defp deps do
    [
      {:validatex, github: "yrashk/validatex"},
      {:hackney, github: "benoitc/hackney"},
        {:edown, github: "esl/edown"},
      {:genx, github: "yrashk/genx"},
      {:cowboy, github: "extend/cowboy"},
        {:ranch, github: "extend/ranch"},
      {:mimetypes, github: "spawngrid/mimetypes"},
      {:lagerex, github: "yrashk/lagerex"},
      {:exreloader, github: "yrashk/exreloader"},
      {:erlpass, github: "ferd/erlpass", compile: "rebar compile deps_dir=.."},
        {:proper, github: "manopapad/proper"},
        {:bcrypt, github: "spawngrid/erlang-bcrypt"},
      {:relex, github: "yrashk/relex"},
      {:exconfig, github: "yrashk/exconfig"},
    ]
  end

Or do as I do to remove the boilerplate of manually typing in required applications from the dependency list in the applications list (full mix.exs):

defmodule Recipex.Mixfile do
  use Mix.Project

  def project do
    [ app: :recipex,
      version: "0.0.1",
      deps: deps ]
  end

  # Configuration for the OTP application
  def application do
    [
      applications: List.foldl(deps!, [], function do 
        {{app, _source}, :req}, acc0 -> [app|acc0] # if dep is marked with :req, start it.
        _, acc0 -> acc0 # else, skip it in the applications list
      end) ++ [:mix] ++ env_apps(Mix.env), # start otp/elixir apps and env-apps
      mod: {Recipex.App, []}
    ]
  end

  defp env_apps(:dev), do: [:exreloader]
  defp env_apps(_), do: []

  # Returns the list of dependencies in the format:
  # { :foobar, "0.1", git: "https://github.com/elixir-lang/foobar.git" }
  defp deps do
    #Enum.map ensures that compilation order is preserved.
    Enum.map deps!, fn(x) -> :erlang.element(1, x) end
  end

  defp deps! do
    [
     {{:genx, github: "yrashk/genx"}, :req},
     {{:xup, github: "yrashk/xup"}, :req},
     {{:mysql, github: "manpages/erlang-mysql-driver"}, :req},
     {{:proper, github: "manopapad/proper"}, :nreq},
     {{:cowboy, github: "extend/cowboy"}, :req},
       {{:ranch, github: "extend/ranch"}, :req},
     {{:lambdatools, github: "manpages/lambdatools"}, :req},
     {{:erlgit, github: "gleber/erlgit"}, :req},
       {{:erlsemver, github: "gleber/erlsemver"}, :nreq},
       {{:sh, github: "gleber/sh"}, :nreq},
     {{:erlydtl, github: "evanmiller/erlydtl"}, :nreq},
     {{:exlager, github: "khia/exlager"}, :req},
     {{:jsx, github: "talentdeficit/jsx"}, :req},
     {{:ossp_uuid, github: "yrashk/erlang-ossp-uuid"}, :req},
     {{:exconfig, github: "yrashk/exconfig"}, :req},
     {{:exreloader, github: "yrashk/exreloader"}, :nreq},
    ]
  end
end

I'll make that mix.exs slightly more beautiful and will update that guide right after. My thought is that we should make the second element of deps! tuple be :req or any atom that could be returned from Mix.env, or nil. In such way we'll be able to declare function deps_apps! that will start all the :req dependencies and all the dependencies that match against current output of Mix.env.

Anyway, bottom line is that you put deps of your deps into your deps.

How to OTP?

During the first couple of days of Elixir I was still getting used to syntax (never wrote in Ruby before, twice in Python: for Convergence and ICFPC), I was trying to master calls to the standard OTP functions from Elixir modules. It was rather hard not to mess something up, so in frustration I asked Mr. Rashkovskii to peek at my OTP code and tell what am I doing wrong. He answered that question but offered to use GenX instead. As I was both sceptical and anxious about whole Elixir, I was too lazy and too scared to use GenX off the bat (which was a bad-bad mistake).

Eventually I started to use GenX to remove boilerplate code when defining gen_servers and supervisors, but then Mr. Rashkovskii released Xup that replaced and improved GenX supervisor macros.

I'll just let the code speak, okay?

import Xup

defsupervisor Future.Sup, strategy: :rest_for_one do
  worker do: [id: Future.Mon]

  supervisor SOFO, strategy: :simple_one_for_one do
    worker do: [id: Future.Srv, shutdown: :brutal_kill]
  end
end
defmodule MockingBird do
  use GenServer.Behaviour
  import GenX.GenServer

  defrecord State, id: nil

  defcall get_state, from: _from, state: state, do: {:reply, state, state}

  def start_link(id) do
    :gen_server.start_link(__MODULE__, id, [])
  end

  def init(id), do: {:ok, State.new(id: id)}

end

Bottom line: don't try to avoid using GenX and Xup in your daily programming routine, those are truly awesome macro-libraries that make your OTP code crystal clear and process of writing down OTP trees joyful and intuitive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment