cukeup nyc ian dees on elixir, erlang, and cucumberl
DESCRIPTION
Elixir, Erlang, and Cucumberl Elixir is a new Ruby-inspired programming language that uses the powerful concurrent machinery of Erlang behind the scenes. Cucumberl is a port of Cucumber to Erlang. Let's see what happens when we put them together. In this talk, we'll discuss: How Erlang's concurrency makes it easier to write robust programs Elixir's approachable syntax How to test Erlang and Elixir programs using Cucumberl Attendees will walk away with a solid introduction to the principles of Erlang, and an appreciation of the way Elixir brings the joy of Ruby to the solidity of the Erlang runtime.TRANSCRIPT
Cucumber and ElixirIan Dees • @undeesCukeUp! NYC 2013
Why Elixir?The sad state of concurrency
int HandleMessage(MessageType type, unsigned long arg1, unsigned long arg2) { switch (type) { case SOME_MESSAGE: INFO* info = *((INFO*)arg2); doSomethingWith(info.field); return MEANINGFUL_ERROR_CODE; // ... } // ...}
Semaphore& s = resourceSemaphore();s.acquire();laboriouslyCopyDataFromResource();s.release();doSomethingWithData();
• Dangerous and hard-to-use message types
• Corruption- and deadlock-prone locks
It doesn’t have to be this way!
A taste of Elixirhttp://elixir-lang.org
Ruby style, Erlang substance
Pattern matching
defmodule Ackermann do def ack(0, n), do: n + 1 def ack(m, 0), do: ack(m - 1, 1) def ack(m, n), do: ack(m - 1, ack(m, n - 1))end
IO.puts Ackermann.ack(3, 9)
Actor-style concurrency
defmodule ShoutyEcho do def echo_loop do receive do {sender, msg} -> sender <- {:ok, String.upcase(msg)} echo_loop end endend
defmodule ShoutyEcho do def echo_loop do receive do {sender, msg} -> sender <- {:ok, String.upcase(msg)} echo_loop end endend
defmodule ShoutyEcho do def echo_loop do receive do {sender, msg} -> sender <- {:ok, String.upcase(msg)} echo_loop end endend
defmodule ShoutyEcho do def echo_loop do receive do {sender, msg} -> sender <- {:ok, String.upcase(msg)} echo_loop end endend
defmodule ShoutyEcho do def echo_loop do receive do {sender, msg} -> sender <- {:ok, String.upcase(msg)} echo_loop end endend
pid = spawn(ShoutyEcho, :echo_loop, [])pid <- {self, "Your name here"}
receive do {:ok, response} -> IO.puts response after 500 -> IO.puts "Done"end
pid = spawn(ShoutyEcho, :echo_loop, [])pid <- {self, "Your name here"}
receive do {:ok, response} -> IO.puts response after 500 -> IO.puts "Done"end
pid = spawn(ShoutyEcho, :echo_loop, [])pid <- {self, "Your name here"}
receive do {:ok, response} -> IO.puts response after 500 -> IO.puts "Done"end
pid = spawn(ShoutyEcho, :echo_loop, [])pid <- {self, "Your name here"}
receive do {:ok, response} -> IO.puts response after 500 -> IO.puts "Done"end
Cucumberlhttps://github.com/membase/cucumberl
Feature: Gray Code Scenario: Reset Given the LEDs read "ooo"
-module(graycode).
-export([given/3, main/0]).
given([the, leds, read, _Input], State, _) -> {ok, State}.
main() -> cucumberl:run("./features/graycode.feature").
$ cucumberl
Feature: Gray Code Scenario: Reset Given the LEDs read "ooo"
1 scenarios1 steps
Feature: Gray Code Scenario: Reset Given the LEDs read "ooo" When I reset the counter
---------NO-STEP--------
a step definition snippet...'when'([i,reset,the,counter], State, _) -> undefined.
---------NO-STEP--------
a step definition snippet...'when'([i,reset,the,counter], State, _) -> undefined.
-module(graycode).
-export([given/3, 'when'/3, main/0]).
%% ...
'when'([i, reset, the, counter], State, _) -> {ok, State}.
%% ...
$ cucumberl
Feature: Gray Code Scenario: Reset Given the LEDs read "ooo" When I reset the counter
1 scenarios2 steps
I know what you’re thinking...
What happens with the next “When” step?
Feature: Gray Code Scenario: ... ... When I reset the counter
Scenario: ... ... When I press the button
Pattern matching!
'when'([i, reset, the, counter], State, _) -> {ok, State};'when'([i, press, the, button], State, _) -> {ok, State}.
$ find . -type f
./ebin/graycode.beam
./features/graycode.feature
./src/graycode.erl
$ find . -type f
./ebin/graycode.beam
./features/graycode.feature
./src/graycode.erl
Just a .beam file!
And now in Elixir!
Feature: Gray Code Scenario: Reset Given the LEDs read "ooo"
-module(graycode).
-export([given/3, main/0]).
given([the, leds, read, _Input], State, _) -> {ok, State}.
main() -> cucumberl:run("./features/graycode.feature").
defmodule :graycode do def given([:the, :leds, :read, input], _state, _) do {:ok, input} end
def main() do :cucumberl.run("./features/graycode.feature") endend
$ iex
c("src/graycode.ex", "ebin")
$ find . -type f
./ebin/graycode.beam
./features/graycode.feature
./src/graycode.ex
$ cucumberl
Feature: Gray Code Scenario: Reset Given the LEDs read "ooo"
1 scenarios1 steps
Feature: Gray Code Scenario: Reset Given the LEDs read "ooo" When I reset the counter
---------NO-STEP--------
a step definition snippet...'when'([i,reset,the,counter], State, _) -> undefined.
---------NO-STEP--------
a step definition snippet...'when'([i,reset,the,counter], State, _) -> undefined.
def 'when'([:i, :reset, :the, :counter], _state, _) do {:ok, '...'}end
⬇
== Compilation error on file src/graycode.ex ==** (SyntaxError) ./src/graycode.ex:6: syntax error before: ')'
How to translate?
Hacking Cucumberl
%% cucumberl_gen.erl
When = process_clauses('when', lists:reverse( sets:to_list(dict:fetch('when', Dict)))),
⬇
When_ = process_clauses(when_, lists:reverse( sets:to_list(dict:fetch(when_, Dict)))),
%% cucumberl_gen.erl
When = process_clauses('when', lists:reverse( sets:to_list(dict:fetch('when', Dict)))),
⬇
When_ = process_clauses(when_, lists:reverse( sets:to_list(dict:fetch(when_, Dict)))),
%% cucumberl_parser.erl
string_to_atoms(StrWords) -> lists:map(fun (Y) -> list_to_atom(string:to_lower(Y)) end, string:tokens(StrWords, " ")).
⬇
list_to_valid_atom("when") -> list_to_atom("when_");list_to_valid_atom(Str) -> list_to_atom(Str).
string_to_atoms(StrWords) -> lists:map(fun (Y) -> list_to_valid_atom(string:to_lower(Y)) end, string:tokens(StrWords, " ")).
%% cucumberl_parser.erl
string_to_atoms(StrWords) -> lists:map(fun (Y) -> list_to_atom(string:to_lower(Y)) end, string:tokens(StrWords, " ")).
⬇
list_to_valid_atom("when") -> list_to_atom("when_");list_to_valid_atom(Str) -> list_to_atom(Str).
string_to_atoms(StrWords) -> lists:map(fun (Y) -> list_to_valid_atom(string:to_lower(Y)) end, string:tokens(StrWords, " ")).
def when_([:i, :press, :the, :button], state, _) do {:ok, _next(state)}end
$ cucumberl
Feature: Gray Code Scenario: Reset Given the LEDs read "ooo" When I reset the counter
1 scenarios2 steps
def then([:the, :leds, :should, :read, expected], state, _) do {expected === state, state}end
Scenario Outline: Counter Given the LEDs read "<n>" When I press the button Then the LEDs should read "<nplus1>" Examples: | n | nplus1 | | ... | ..o | | ..o | .oo | | .oo | .o. | | .o. | oo. | | oo. | ooo | | ooo | o.o | | o.o | o.. | | o.. | ... |
def when_([:i, :press, :the, :button], state, _) do {:ok, _next(state)}end
defp _next(leds) do case leds do '...' -> '..o' '..o' -> '.oo' '.oo' -> '.o.' '.o.' -> 'oo.' 'oo.' -> 'ooo' 'ooo' -> 'o.o' 'o.o' -> 'o..' 'o..' -> '...' endend
Caveats
1. Cucumberl has no idea about the Elixir runtime
2. Who will do whatabout When?