What Can We (Rubyists)

Learn From Elixir

About Me

@qhwa on

creator of

worked at Alibaba

now at Helijia.com

About Me

  • work in web industry since 2004

  • built web products with Ruby since 2011

  • use Elixir since 2016

I ❤️ Building Web Products 

I ❤️ Ruby 

program happily

high productivity

  •  easy to transform minds to codes

high maintainability

  • easy to write expressive and clean codes

amazing community

  • always seeking a better way
  • always positive on new things

But Ruby does not fit

in all situations.

Erlang

1980s

for telecom companies

still a general-purpose language

for scalable, reliable systems

constantly provide service

with little or no downtime

Morden web apps are a good fit, too

Erlang's weakness

unfriendly syntax

not so shining tools (compared to ruby)

Hello.Elixir

Elixir

created in 2012

by José Valim

  • was a member of Rails core team

  • creator of Devise

Based on Erlang

  • compiled into Erlang VM codes

  • runs in Erlang VM (BEAM)

  • can use Erlang libararies

Ships with great tools inspired by Ruby

  • mix (rake)

  • hex (gem & bundler)

  • iex (irb)

  • documentating

Ruby-like Syntax

IO.puts "Hello, world!"

List

list = [:this, "is", 1, "list"]
list ++ ["!"]
# => [:this, "is", 1, "list", "!"]

Tuple

{:this, :is, 1, "tuple"} 

Keyword List

args = [{:timeout, 5000}, {:retry, false}]
# equals to
args = [timeout: 5000, retry: false]
# shortcut
# can be used as:
http(url, timeout: 5000, retry: false)

Map

cities = %{cd: "成都", hz: "杭州"}
Map.get(cities, :cd)
#=> "成都"
cities.cd
#=> "成都"

Function

double = fn (n) -> n * 2 end
double.(11)
#=> 22

Function

triple = &(&1 * 3)
triple.(11)
#=> 33

Module

defmodule Greet do
  def welcome do
    "Welcome to RubyConfChina!"
  end
end
Greet.welcome # or Greet.welcome()
#=> "Welcome to RubyConfChina!"

Alright, but Ruby already offers all of these...

Pattern Matching

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

Pattern Matching

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

Pattern Matching

iex> {x, y} = {100, 50}
{100, 50}
iex> x
100
iex> y
50

Pattern Matching

iex> [1, 2, funny | tail] = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> funny
3
iex> tail
[4, 5]

Pattern Matching

iex> {:ok, f} = File.read("./mix.exs")
# if read successfully
{:ok, "..."}
iex> {:ok, f} = File.read("NON_EXIST_FILE")
# oops
** (MatchError) no match of right hand side
 value: {:error, :enoent}

Pattern Matching

result = do_something()

case result do
  {:ok, result}    -> process(result)
  {:error, reason} -> show_msg(reason)
end

Pattern Matching

defmodule Fibonacci do
  def fib(0), do: 0
  def fib(1), do: 1
  def fib(n) do
    fib(n - 2) + fib(n - 1)
  end

end
  def fib(n) when n > 1 do
    fib(n - 2) + fib(n - 1)
  end

Pattern Matching

Suppose we are going to implement:

[1, 2, 4, 5, "hello", "world"]

[2, 1, 5, 4, "world", "hello"]

defmodule MyList do

  def swap(list) do
    _swap(list, [])
  end
  defp _swap([], current) do
    current
  end
  defp _swap([a], current) do
    current ++ [a]
  end
  defp _swap([a, b | tail], current) do
    _swap(tail, current ++ [b, a])
  end

end

Pattern Matching

defmodule UsersController do

  def register(conn, %{"agreed" => false}) do
    msg = "Please agree to continue."
    render(conn, "new.html", alert: msg)
  end

  def register(conn, params) do
    create_user_in_db(params)
    redirect_to_profile(conn)
  end

end

Logic branch

Logic trunk

your code = small isolated logic branches.

Pipe

people = DB.find_customers
orders = Orders.for_customers(people)
tax    = sales_tax(orders, 2013)
filing = prepare_filing(tax)

Pipe

filing = prepare_filing(
  sales_tax(
    Orders.for_customers(
      DB.find_customers
    ), 2013
  )
)

Pipe

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

Pipe

(1..10)
  |> Enum.map(&(&1 * &1))
  |> Enum.filter(&(&1 < 40))
#=> [1, 4, 9, 16, 25, 36]

Meta Programming

Meta Programming

assert 1 == 2

  1) test parse messages from ...
     test/my_test.exs:5
     Assertion with == failed
     code: 1 == 2
     lhs:  1
     rhs:  2

Functional Programming

immutable data

pure functions

FP: same argument

= same result

OO way

class Employee
  def initialize(name, salary)
    @name   = name
    @salary = salary
  end
  def change_salary_by(amt)
    @salary = @salary + amt
  end
  def description
    "#{@name} makes #{@salary}"
  end
end
bob = Employee.new("Bob", 10_000)
bob.change_salary(1000)
puts bob.description # "Bob makes 11000"
  def change_salary_by(amt)
    # make function pure by returning a new object
    self.class.new(@name, @salary + amt)
  end
bob = Employee.new("Bob", 10_000)
bob = bob.change_salary_by(1000)

FP way

Functional way with Elixir

defmodule Employee do
  defstruct name: nil, salary: 0
  def change_salary_by(person, amt) do
    Map.update(person, :salary, amt, &(&1 + amt))
  end
  def description(person) do
    "#{person.name} makes #{person.salary}"
  end
end

%Employee{name: "Bob", salary: 10_000}
  |> Employee.description()
  |> Employee.change_salary_by(1000)
  |> IO.puts # "Bob makes 11000"

Reuse your code without worrying.

Process

Process

1.spawn

2. wait (block)

2. send message

3. receive messages

Basic Concurrency in Elixir

Concurrency

defmodule SpawnExample do
  def test do
    spawn(Greet, :hello, [self, "Billy"])
    receive do
      {:ok, msg} -> IO.puts msg
    end
  end
end

Concurrency

defmodule Greet do
  def hello(from, who) do
    send(from, {:ok, "Hello, #{who}!"})
  end
end

Concurrency

iex> SpawnExample.test
Hello, Billy!
:ok

Process

Process

1.spawn

2. wait (block)

2. send message

3. receive messages

Basic Concurrency in Elixir (Again)

Paralleling

[1, 2, 3, 4] |> Enum.map(&(&1 * &1))
#=> [1, 4, 9, 16]
# parallel mapping!
[1, 2, 3, 4] |> Pmap.map(&(&1 * &1))
#=> [1, 4, 9, 16]

Paralleling

defmodule Pmap do

  def map(collection, f) do
    me = self
    pids = collection
      |> Enum.map(fn(element) ->
        spawn_link(fn -> send(me, {self, f.(element)}) end)
      end)

    pids
      |> Enum.map(fn(pid) ->
        receive do
          {^pid, result} -> result
        end
      end)
  end

end

Processes share nothing.

Processes everywhere, services everywhere.

OTP -- toolset for massive concurrent systems

(should be another topic)

(Some) Hard things in Ruby no more exist in Elixir.

Elixir is so different with but close to Ruby.

Take it as your next language

to learn

if not yet, ...

... and you will find

the same fun when learning Ruby.

Thanks!

Q/A

What Can We (Rubyists) Learn From Elixir

By qhwa

What Can We (Rubyists) Learn From Elixir

A brief introduction to Elixir for Rubyists.

  • 581