Slide 1

Slide 1 text

Idiomatic Erlang MARK ALLEN MRALLEN1@YAHOO.COM @BYTEMEORG

Slide 2

Slide 2 text

Style suggestions u Use inline type specifications and documentation tools u Use list comprehensions u Use explicit functions in higher order functions u Don’t nest case statements u Use sweet syntactic sugar for maps and record pattern matching u Reply to unknown gen_server call messages with a weird answer. (Why?) u Use erlang:error/2 instead of error/1 u Start new projects using rebar3 templates u Make your editor help you (find and use Erlang plugins for your code editor) u Finite state machines are powerful – exploit their power

Slide 3

Slide 3 text

Example code -spec repeat( FunName :: atom() | function(), Args :: [ term() ], Count :: pos_integer() ) -> Result :: term() | { error, Reason :: term() }. %% @doc This function repeats calls to a function name which may fail %% given by the FunName parameter for Count number of times. repeat(_FunName, _Args, 0) -> {error, count_reached_zero); repeat(FunName, Args, Count) when is_atom(FunName) -> do_repeat({?MODULE, FunName}, Args, Count); repeat(FunName, Args, Count) when is_function(FunName) -> do_repeat(FunName, Args, Count); repeat(Other, _Args, _Count) -> erlang:error({error, badarg}, [Other, Args]). do_repeat(Fun, Args, C) -> try erlang:apply(Fun, Args) catch _:_ -> repeat(FunName, Args, C – 1) end.

Slide 4

Slide 4 text

Type specifications u Dialyzer is a type analysis tool for Erlang/Elixir. u You give dialyzer hints by providing a type specification for your (public) functions. u They help remind you what the arguments to a function are and what sorts of output might be returned. u Dialyzer can help you spot instances in your code when the specification is not true.

Slide 5

Slide 5 text

List comprehensions u Powerful and compact way to filter and iterate over a collection of items in a list. u Can replace lists:map and lists:filtermap [[ prepare_valid_element(E) || E <- List, E /= undefined ]]. lists:filtermap( fun(E) when E /= undefined -> {true, prepare_valid_element(E)}; (_E) -> false end, List).

Slide 6

Slide 6 text

Explicit function names u Using explicit function names gives two benefits: u Much better stack traces u Easy to write unit tests for the whole function and the higher order function lists:map(fun(X) -> X*X end, lists:seq(1,5)). lists:map(fun double/1, lists:seq(1,5)). double(X) -> X*X.

Slide 7

Slide 7 text

Don’t nest case statements u They’re hard to read and hard to reason about. u Use guard clauses and explicit functions. case foo(Arg1, Arg2) of true -> case of bar(Arg2) of true -> min(1, Arg1); false -> 100 end; false -> {{error, badarg}, Arg1} end.

Slide 8

Slide 8 text

Don’t nest case statements u They’re hard to read and hard to reason about. u Use guard clauses and explicit functions. case foo(Arg1, Arg2) of true -> case of bar(Arg2) of true -> min(1, Arg1); false -> 100 end; false -> {{error, badarg}, Arg1} end.

Slide 9

Slide 9 text

Don’t nest case statements case foo(Arg1, Arg2) of true -> test_arg2(Arg1, Arg2) false -> {{error, badarg}, Arg1} end. test_arg2(Arg1, Arg2) -> case bar(Arg2) of true -> min(1, Arg1); false -> 100 end.

Slide 10

Slide 10 text

Sweet syntactic sugar handle_call({incr, _Arg}, _From, S = #state{ state = lock }) -> {reply, {error, locked}, S}; handle_call({incr, Arg}, _From, S = #state{ cnt = C }) -> {reply, ok, S#state{ cnt = C + Arg }}. incr_foo(V, Map = #{ foo := Current }) -> Map#{ foo := Current + V }.

Slide 11

Slide 11 text

Use weird reply to unknown message handle_call(Call, From, State) -> ?LOG("Unknown call ~p from ~p!!”, [Call, From]), {reply, unknown_call_message_die_die_die, State}.

Slide 12

Slide 12 text

Use erlang:error/2 u Provides the specific bad argument along with the error atom in a stack trace. This is really helpful! u Demo

Slide 13

Slide 13 text

Remainder u Use rebar3 templates to start new projects u Make your editor help you! u Don’t ignore finite state machines! They provide great clarity/insight into your programming intentions.