Elixir

Hal E. Fulton

Ruby Day    13 Nov 2015    Turin, Italy

for the

Rubyist

Why Ruby?

  • Radically object-oriented
  • Beautiful syntax
  • Consistent semantics
  • Highly expressive
  • But... not very concurrent
  • And some say it is slow

Why Erlang?

  • Functional language
  • Good support for concurrency
  • BEAM
  • Small, fast, lightweight processes
  • OTP

"If somebody came to me and wanted to pay me a lot of money to build a large-scale message handling system that really had to be up all the time, could never afford to go down for years at a time, I would unhesitatingly choose Erlang to build it in."

Tim Bray

Sun Microsystems

Why not Erlang?

  • Ugly syntax?
  • Inconsistencies
  • Old-fashioned pieces
  • "Onions in the varnish"

Onions in the Varnish?

Primo Levi

In The Periodic Table, Primo Levi tells a story that happened when he was working in a varnish factory. He was a chemist, and he was fascinated by the fact that the varnish recipe included a raw onion. What could it be for? No one knew; it was just part of the recipe. So he investigated, and eventually discovered that they had started throwing the onion in years ago to test the temperature of the varnish: if it was hot enough, the onion would fry.

                                                                                           Paul Graham

Why Elixir?

  • Runs on BEAM
  • Compatible with Erlang
  • Can access OTP
  • Prettier syntax?
  • Modernized language features
  • Borrows from Ruby

Elixir...

  • Obviously borrows from Erlang
  • Also borrows from Ruby
  • Also innovates (borrows from elsewhere)

Erlang

Ruby

Elixir

Anonymous functions

sum = &(&1 + &2)

c = sum.(4,3)
sum = fn(a, b) -> a + b end

c = sum.(4, 3)

The "pipeline" operator

means

filing = DB.find_customers 
|> Orders.for_customers 
|> sales_tax(2015) 
|> prepare_filing 
f(x,y)
filing = prepare_filing(sales_tax(Orders.for_customers(DB.find_customers),2015)) 
people = DB.find_customers 
orders = Orders.for_customers(people) 
tax    = sales_tax(orders, 2015) 
filing = prepare_filing(tax) 
x |> f(y)

defmodule AnagramFinder do
  def search_file(file) do   
    get_words(file)
    |> process_all
    |> Enum.each &print_words/1  
  end

  defp process_all(list) do
    list 
    |> Enum.group_by(HashDict.new, &(Enum.sort(String.codepoints(&1)))) 
  end

  defp get_words(file) do
    File.read!(file)
    |> String.split("\n", trim: true)
  end

  defp print_words({_key, [_word]}), do: nil

  defp print_words({_key, words}) do
    [first | rest] = words 
    |> Enum.reverse
    IO.puts "#{first}: #{Enum.join(rest, ", ")}"
  end
end

AnagramFinder.search_file("/usr/share/dict/words")

Anagram Finder in Elixir

Other features...

  • Multiple function heads
  • Guard clauses
  • Macros
  • Protocols
  • Streams
  • Tasks
  • Small, fast, lightweight processes
  • Process supervision

Multiple function heads

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

IO.puts Fibonacci.fib(5)     # 8
IO.puts Fibonacci.fib(10)    # 89

Guard clauses

# on a named function

def circle_area(radius) when is_number(radius)
  3.1416 * radius * radius
end


# on an anonymous function

func = fn 
         x when is_number(x) -> "a number"
         x when is_binary(x) -> "a binary"
         x -> "something else"
       end


Macros

defmodule MyMacros do

  defmacro unless(test, expr) do
    quote do
      if(!unquote(test), do: unquote(expr))
    end
  end

end




# in another context:

require MyMacros

unless 2 == 3, do: IO.puts "That's a relief."

Two observations...

Multiple function heads, pattern matching, and guard clauses can all help reduce branching logic.

Tail recursion makes it possible in many cases to reduce looping.

Avoiding branches

defmodule Quadratic do
  def real_roots(a, b, c) when b*b - 4*a*c > 0,  do: 2
  def real_roots(a, b, c) when b*b - 4*a*c == 0, do: 1
  def real_roots(a, b, c) when b*b - 4*a*c < 0,  do: 0
end

Avoiding loops

defmodule Multi do

  def print(1, str), do: IO.puts str 

  def print(n, str) do
    IO.puts str
    print(n-1, str)
  end

end



greeting = "Salve mundo!"

Multi.print(5, greeting)

Where does this

ultimately lead?

Functional programming...

Fast messaging...

Multiple processors/cores

Those who

dreamed

early...

What is "object-oriented" really?

"I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages. So messaging came at the very beginning -- it took a while to see how to do messaging in a programming language efficiently enough to be useful."

Alan Kay

"Can Programming Be Liberated
from the von Neumann Style?
A Functional Style
and Its Algebra of Programs"

Turing Award Lecture

of John Backus, 1978

The machine, as we envisioned it, would contain a million tiny computers, all connected by a communications network. We called it a "Connection Machine."

W. Daniel Hillis

"Richard Feynman and the Connection
Machine," Physics Today, 1989

But enough dreaming
 and back to reality...

Note: I'm working on a new book.

I hope you will buy it someday.

Questions?

Anagram Finder in Elixir


defmodule AnagramFinder do
  def search_file(file) do   
    get_words(file)
    |> process_all
    |> Enum.each &print_words/1  
  end

  defp process_all(list) do
    list 
    |> Enum.group_by(HashDict.new, &(Enum.sort(String.codepoints(&1)))) 
  end

  defp get_words(file) do
    File.read!(file)
    |> String.split("\n", trim: true)
  end

  defp print_words({_key, [_word]}), do: nil

  defp print_words({_key, words}) do
    [first | rest] = words 
    |> Enum.reverse
    IO.puts "#{first}: #{Enum.join(rest, ", ")}"
  end
end

AnagramFinder.search_file("/usr/share/dict/words")
1.0e3

grazie!

deck

By hal_9000

deck

  • 231