1.1 Logic, atoms and tuples (Starting out (for real))
Boolean Algebra & Comparison operators
true
,false
- normal:
not
,and
,or
,xor
- short circuit:
andalso
,orelse
<
,>
,=<
,>=
number < atom < reference < fun < port < pid < tuple < list < bit string
Atoms: atom
, 'Special atom'
. Usage examples:
- Atoms vs strings:
- atoms consume less memory
- atoms are faster to compare
- the number of atoms is limited
- there is no GC for atoms
- What are atoms useful for? A few examples:
- Return value tag:
{ok, Result} | {error, Error}
- Type tag:
{person, Name, Address}
- Enums:
white | yellow | green | oolong | black | dark
- Return value tag:
Tuples:
-
Pattern matching:
> Point = {10, 4}. {10,4} > {X, Y} = Point. {4,5} > X. 4 > {X, _} = Point. {4,5} > {_, _} = {4, 5}. {4,5} > {_, _} = {4, 5, 6}. ** exception error: no match of right hand side value {4,5,6} > {Y, Y} = {4, 5}. ** exception error: no match of right hand side value {4,5}
1.2 Pattern matching (Syntax in functions)
Case expression
greet(Animal) ->
case Animal of
monkey ->
io:format("Be careful on the branches!~n");
tiger ->
io:format("Run!~n");
_ ->
io:format("Hello!~n")
end.
Case + Pattern matching
greet(Animal) ->
case Animal of
{monkey, Name} ->
io:format("Be careful on the branches, ~s!~n", [Name]);
{tiger, _Name} ->
io:format("Run!~n");
{_Species, Name} ->
io:format("Hello, ~s!~n", [Name]);
Other ->
throw({error, {unexpected_value, Other}})
end.
Pattern matching: =
vs case
Pattern = Value
case Value of
Pattern1 ->
Expression1;
Pattern2 ->
Expression2;
...
end
Guards
greet(Animal) ->
case Animal of
% We are interested only in middle-aged butterflies
{butterfly, Name, Age} when 2 =< Age, Age =< 5 ->
io:format("Come here, ~s!~n", [Name]);
{monkey, Name, _Age} ->
io:format("Be careful on the branches, ~s!~n", [Name]);
% We aren't afraid of young or old tigers
{tiger, _Name, Age} when Age =< 2; 20 =< Age ->
io:format("I'm not afraid of you!~n", []);
{tiger, _Name, _Age} ->
io:format("Run!~n");
{_Species, Name, _Age} ->
io:format("Hello, ~s!~n", [Name]);
Other ->
throw({error, {unexpected_value, Other}})
end.
Pattern matching in function clauses
-
The formal parameters of functions are actually also patterns:
greet({_, Name, Age}) when Age =< 1 -> io:format("Hellobello, ~s!~n", [Name]); greet({butterfly, Name, Age}) when 2 =< Age, Age =< 5 -> io:format("Come here, ~s!~n", [Name]); greet({monkey, Name, _Age}) -> io:format("Be careful on the branches, ~s!~n", [Name]); greet({tiger, _Name, Age}) when Age =< 2; 20 =< Age -> io:format("I'm not afraid of you!~n", []); greet({tiger, _Name, _Age}) -> io:format("Run!~n", []); greet({_Species, Name, _Age}) -> io:format("Hello, ~s!~n", [Name]); greet(Other) -> throw({error, {unexpected_value, Other}}).
-
These patterns may bind the same variables:
greet({Name, Name, _Age}) -> io:format("Your have a boring name, ~s!~n", [Name]);
-
They may even contain pattern matchings:
greet(Animal = {_Species, _Name, Age}) when Age > 1000 -> io:format("Warning. Unrealistic age for animal ~p~n", [Animal]);
If expressions
-
if
expressions don't have pattern matching, only guards:greet(Animal) -> if Animal =:= monkey -> io:format("Be careful on the branches!~n"); Animal =:= tiger -> io:format("Run!~n"); true -> io:format("Hello!~n") end.
1.3 Recursion
Recursion:
fac(1) ->
1;
fac(N) ->
N * fac(N - 1).
What is the call stack?
f() ->
g() + 1.
g() ->
h() + 2.
h() ->
0.
Call stack:
| | | | | | | | | |
| | | | | | | | | |
| | --> | | --> | h() | --> | | --> | |
| | | g() | | g() | | g() | | |
| f() | | f() | | f() | | f() | | f() |
+--------+ +--------+ +--------+ +--------+ +--------+
We can check the stack trace with the following call:
{backtrace, Trace} = process_info(self(), backtrace), io:format("~s~n", [Trace]).
Call stack of the previous fac
function:
| | | | | | | fac(1) |
| | | | | | | fac(2) |
| | --> | | --> | fac(3) | --> ... --> | fac(3) |
| | | fac(4) | | fac(4) | | fac(4) |
| fac(5) | | fac(5) | | fac(5) | | fac(5) |
+--------+ +--------+ +--------+ +--------+
Tail recursion:
fac(N) ->
fac(N, 1).
% Return fac(N) * Acc.
%
% Example call chain:
%
% fac(5, 1) ->
% fac(4, 5) ->
% fac(3, 4*5) ->
% fac(2, 3*4*5) ->
% fac(1, 2*3*4*5) ->
% 2*3*4*5
fac(1, Acc) ->
Acc;
fac(N, Acc) ->
% do_fac2(N, Acc) = N! * Acc = (N - 1)! * N * Acc = do_fac2(N - 1, N * Acc)
fac(N - 1, N * Acc).
Call stack:
| | | | | |
| | | | | |
| | --> | | --> | | --> ...
| | | | | |
| fac(5, Acc) | | fac(4, Acc) | | fac(3, Acc) |
+-------------+ +-------------+ +-------------+