Hello World in Erlang

optimized for Rubyists

Yuri Leikind / @less_software

Goals:

  • Show the basics of processes and message passing in Erlang
  • ... how cool this stuff is ...
  • ... and lighweight ...
  • make it simple
  • make you want to know more
  • all in 20 minutes

actually...

Hello World in Erlang

without Erlang

the language :-)

Usually

  • Runtime/VM
  • Language
  • Standard libraries
  • Tooling
  • Runtime/VM
  • Language
  • Standard libraries
  • Tooling
  • Runtime/VM: BEAM (Bogdan/Björn's Erlang Abstract Machine)
  • Language: Erlang

For our goal to write Hello World in BEAM in 20 minutes we're giving up on Erlang the language :-)

And we'll use Elixir, a BEAM language with modern syntax.

I want your brains cycles to be spent on the semantics of BEAM, and not on unfamiliar Erlang syntax.

  • BEAM: Bogdan/Björn's Erlang Abstract Machine
  • Erlang Elixir

Elixir is not Coffescript for Erlang!

It has some some really cool stuff of its own.

But here we're not looking at it.

We are only using features which are mapped to its Erlang equivalents.

So yes...

We are using Elixir as if it were Coffescript for Erlang...

Please don't tell anyone!

Because it isn't! :)

Hello World for the Erlang platform is a concurrent map function.

Idea © Dave Thomas

Spawning a process


pid = spawn(fn() -> :ok end)

IO.inspect Process.alive?(pid)
IO.inspect Process.alive?(pid)
					

true
false
					

Waiting for a message


pid = spawn fn() ->
  receive do
    message -> IO.puts "received message: \"#{message}\""
  end
end

IO.inspect Process.alive?(pid)
pid <- "Cheer up, beautiful people"
pid <- "this is where you get to make it right" # won't be received
:timer.sleep 100
IO.inspect Process.alive?(pid)
					

true
received message: "Cheer up, beautiful people"
false
					

Looping


defmodule Processes do
  def loop do
    receive do
      message -> IO.puts "received message: \"#{message}\""
    end
    loop
  end

  def run do
    pid = spawn Processes, :loop, []
    pid <- "Cheer up, beautiful people"
    pid <- "this is where you get to make it right"
  end
end

Processes.run
					

received message: "Cheer up, beautiful people"
received message: "this is where you get to make it right"
					

Responding with a message


defmodule Processes do
  def loop do
    receive do
      {reply_to, message} ->
        reply_to <- String.reverse(message)
    end
    loop
  end

  def run do
    pid = spawn Processes, :loop, []

    pid <- {self(), "Cheer up, beautiful people"}
    pid <- {self(), "this is where you get to make it right"}

    receive do
      response -> IO.inspect(response)
    end

    receive do
      response -> IO.inspect(response)
    end
  end
end

Processes.run
					

"elpoep lufituaeb ,pu reehC"
"thgir ti ekam ot teg uoy erehw si siht"
					

Processing selected messages



defmodule Processes do
  def loop do
    receive do
      {reply_to, :reverse, message} ->
        reply_to <- String.reverse(message)

      {reply_to, :upcase, message} ->
        reply_to <- String.upcase(message)

      {reply_to, _, _message} ->
        reply_to <- "What?"
    end

    loop
  end

  def run do
    pid = spawn Processes, :loop, []

    pid <- {self(), :upcase, "Cheer up, beautiful people"}
    pid <- {self(), :reverse, "this is where you get to make it right"}
    pid <- {self(), :say, "Say my name"}

    receive do
      response -> IO.inspect(response)
    end

    receive do
      response -> IO.inspect(response)
    end

    receive do
      response -> IO.inspect(response)
    end

  end
end

Processes.run


				


					"CHEER UP, BEAUTIFUL PEOPLE"
					"thgir ti ekam ot teg uoy erehw si siht"
					"What?"

				

Shutting down on a message



defmodule Processes do
  def loop do
    receive do
      {reply_to, :reverse, message} ->
        reply_to <- String.reverse(message)
        loop

      {reply_to, :upcase, message} ->
        reply_to <- String.upcase(message)
        loop

      {reply_to, _, _message} ->
        reply_to <- "What?"
        loop

      :shutdown ->
        IO.puts "shutting down..."
        :ok
    end
  end

  def run do
    pid = spawn Processes, :loop, []

    pid <- {self(), :upcase, "Cheer up, beautiful people"}

    receive do
      response -> IO.inspect(response)
    end

    pid <- :shutdown

    :timer.sleep 100
    IO.inspect Process.alive?(pid)
  end
end

Processes.run

				


"CHEER UP, BEAUTIFUL PEOPLE"
shutting down...
false

				

Implementation of map



defmodule MyOwn do

  def map([], _) do
    []
  end

  def map([head|tail], function) do
    [function.(head) | map(tail, function)]
  end

end

IO.inspect MyOwn.map([1,2,3,4], fn(e) -> e * e end)



				


[1, 4, 9, 16]

				

Concurrent map



defmodule TheHelloWordOfErlang do

  def process_one_element_and_send_return(function, element, pid) do
    pid <- {self(), function.(element)}
  end

  def concurrent_map(list, function) do

    parent_process_pid = self()

    pids = Enum.map(
      list,
      fn(element) ->
        spawn(TheHelloWordOfErlang, :process_one_element_and_send_return, [function, element, parent_process_pid])
      end
    )

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

end


				


func = fn(e)-> e * e end

IO.inspect TheHelloWordOfErlang.concurrent_map([1,2,3,4,5], func)
TheHelloWordOfErlang.concurrent_map(1..10_000, func)


				

Concurrent map



defmodule TheHelloWordOfErlang do
  def concurrent_map(list, function) do

    parent_process_pid = self()

    pids = Enum.map(
      list,
      fn(element) ->
        spawn(
          fn ->
            parent_process_pid <- {self(), function.(element)}
          end
        )
      end
    )

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

func = fn(e)-> e * e end

IO.inspect TheHelloWordOfErlang.concurrent_map([1,2,3,4,5], func)
TheHelloWordOfErlang.concurrent_map(1..10_000, func)


				


[1, 4, 9, 16, 25]

				

Concurrent map



defmodule TheHelloWordOfErlang do
  def concurrent_map(list, function) do

    parent_process_pid = self()

    pids = Enum.map(
      list,
      fn(element) ->
        spawn(
          fn ->
            parent_process_pid <- {self(), function.(element)}
          end
        )
      end
    )

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

func = fn(e)-> e * e end

IO.inspect TheHelloWordOfErlang.concurrent_map([1,2,3,4,5], func)
TheHelloWordOfErlang.concurrent_map(1..10_000, func)



				


[1, 4, 9, 16, 25]

				

Concurrent map the Elixir style



defmodule TheHelloWordOfErlang do

  def concurrent_map list, function do

    parent_process_pid = self

    list |>

      Enum.map(&(
        spawn fn -> parent_process_pid <- {self(), function.(&1)} end
      )) |>

      Enum.map &(receive do: ({^&1, result} -> result))
  end

end


func = fn(e)-> e * e end

IO.inspect TheHelloWordOfErlang.concurrent_map([1,2,3,4,5], func)
TheHelloWordOfErlang.concurrent_map(1..10_000, func)


				


[1, 4, 9, 16, 25]

				

A newly spawned Erlang process uses 309 words of memory in the non-SMP emulator without HiPE support.

The size includes 233 words for the heap area (which includes the stack). The garbage collector will increase the heap as needed.

If you want to know more...

Thank You