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