TODO: What are "#" and "." in "Emp#employee.nam" Goals
- Introduction to the base of Erlang (as a language)
- Be able to read erlang code in 15 minutes
- Concurrency, communication and database for later
Things to know about Erlang:
- Immutable values (useful for concurrency)
- Notion of term (unbound symbol)
- Pattern-matching (ML-like)
Of course, you already know that Erlang is designed for implementing soft real-time distributed parallel applications.
Reading Erlang
The values
First things to know: in Erlang, values are immutable, meaning that values cannot change. This is true for lists and variables (which can only be bound once).
Values are numbers:
0 1.0
strings (are atoms):
'HelloWorld' "Hello World"
terms (it's just a name which value is itself, it's not a string nor a variable), which are lowercase (and are also atoms):
hello hello_world
variables (they can reference values, and can only be assigned a value once !), which start with an UpperCaseLetter
Hello Hello_world HelloWorld
tuples:
{}
{0, 1, 2, 3}
{one, "two", 3}
{{0, 1, 2}, {one, two}, {"one", "two"}}lists:
[] [0, 1, 2, 3] [one, "two", 3] [[0,1,2], [one,two], ["one, "two"]]
functions:
fun(X) -> X+1, end
Expressions (and pattern-matching)
Expressions in Erlang are basically values or application of values to functions. However, there is one mechanism which is the core of Erlang: pattern matching.
A = 10
which would be an “assignment” is actually the “unifcation” of 10 with A (and then A is bound to 10).
{A, B, C} = {10, foo, bar}this binds 10 to A, foo to B and bar to C. No surprise here.
{A, A, C} = {10, foo, bar}would fail because it cannot unify the two values (but {10, 10, bar} would work).
additionally you can use the “magic” _ (wildcard) coming from PROLOG in your expressions (to denote that you don't care binding a subset of your value).
{_,A,_,B} = {one,two,three,four}meaning that you only care about the second and forth item of the tuple. This implies (of course) that you cannot reference the first and third values.
These examples work also with lists instead of tuples, but with lists, you can use a rest notation to do the unification:
[Head, Tail] = [0,1,2,3,4]
will fail, however
[Head|Tail] = [0,1,2,3,4]
will bind 0 to Head and [1,2,3,4] to tail.
Statements, operations and functions
Statements are always terminated by ., and , can be used to join them.
A = [1,2,3,4]. B = [1,2,3,4], 5.
means that A will be bound to [1,2,3,4] but B will be bound to 5.
Comments always start with a %
% Hello, World !
Functions are defined like that:
f_name(ARG1, ARG2, ARG3) -> ... .
The return value is the value to which your body evaluates.
Functions are called very naturally:
f_name(ARG1,ARG2,ARG3)
If you want to overload a function (using pattern matching expressions) you can use ; to join your declarations (function clauses). Note that order is important (first matching, first applied).
f_name(EXPR) -> ... ; f_name(EXPR) -> ... ; f_name(EXPR) -> ... .
here is an example:
fac(1) -> 1; fac(N) -> N * fac(N-1).
Also, you can add predicates to guard your function clauses:
fac(N) when N > 0
however, guarding clauses will make them insensitive to ordering (TODO: I have to find out why…).
Functions can also be declared as values
F = fun() -> ... end
TODO: How to make the anonymous function recursive ?
Last but not least, functions are values, and can be applied at anytime using the apply function:
apply(module_name, function_name, [Arg1, Arg2, ...]).
More about operations
We've implicitly seen the "unification" operation (by the operator '='), but there are a couple of default operations available:
Control flow
Control flow in Erlang is all driven by pattern matching, and some more procedural-like operations are available:
case EXPR of PATTERN -> ... ; PATTERN -> ... ; PATTERN -> ... end
is a better version of the switch/case construct.
if EXPR -> ... ; EXPR -> ... ; EXPR -> ... end
is the famous if/elif/else with implicit elif and else.
Records
Erlang offers something that is close to C structures, which is called records. A record is as follows:
-record(RECORDNAME, {FIELD1, FIELD2=VALUE, FIELD3})for instance
-record(person,{first_name,last_name,age,sex})will create a record named person with the fields first_name, last_name, age and sex.
You can “instanciate” (create) records by using the # operator:
Robert = #person{first_name="Robert",last_name="Smith",age=47,sex=male}now you can access Robert's name easily, by using the record field access notation:
Robert#person.first_name
last but not least, when declaring records, you can use introspection functions:
record_info(fields, RECORD) -> [Field] record_info(size, RECORD) -> Size
TODO: I did not manage to get this working
Structuring your code
Declarations (module name, import and export symbols) are like
-DECL(VALUE).
ex:
-module(bank_client). -export([deposit/2, withdraw/2, balance/1]).
Symbols can be referenced by their absolute name, which is
MODULE:SYMBOL
ex:
io:format("Hello, World !").Erlang Idioms
Syntactic stuff
Number on is the naming scheme: terms are lowercase while variables start with an uppercase:
term Variable
Function declaration is pretty much like stating facts in PROLOG:
f(0) -> "odd" ; f(1) -> "even" ; f(X) -> f(X rem 2) .
Here, f(0) and f(1) are facts (the parameter is not a symbol, it is a value, implying that you will give the result
Idioms include automatic string concatenation:
-define(HELLO, "hello" " world !" ).
When you export a function from a module you have to explicitly state its arity (like hello/0 if hello takes no parameter, hello/1 if it takes 1, etc).
Thinking recursively
If you've never played with functional languages, you'll probably have a hard time with Erlang at first, because you have to think recursively.
Here are two common patterns:
double([H|T]) -> [2*H|double(T)]; double([]) -> [].
which maps a function that doubles every element to the list:
member(H, [H|_]) -> true; member(H, [_|T]) -> member(H, T); member(_, []) -> false.
which iterates on a list and stop at a specific condition.
List operations
TODO: Operations on lists (add, push, pop, subset, index, length, swap) TODO: List comprehension
Gotchas
- Symbols
- - Variables always start with an UpperCase - Globals (name → value, like
-define(V,0)) are UPPERCASE - Parameters are UPPERCASE or CamelCase - Functions are lowercase - Unbound symbols (terms) are lowercase - Functions
- -
-export([f/1, f2])meansf1andf2are different functions - usingwhen(guarded function) is not the same as doing overloading with pattern-matching. - Interpreter
- - don't forget to end your statements by '.' - you cannot declare functions in the interpreter (!!)
- Modules
- - if you
-export(M).then your file must be namedM.erl- to use stuff fromMyou have to first toc(M).
Summary about Erlang
The language is immutable (you could say also purely functional), meaning that everything is passed by value, which also implies that everything that can be referenced is a value (this includes functions).
Erlang uses pattern matching on values everywhere, from resolving the body for a function call to the communication primitives (send and receive).
Impressions
Erlang documentation is difficult to find because it's scattered in many places, where the links are broken. It's difficult to get a good short introduction on the syntax and how to get started.
However, there are two very useful documents:
- [Kickstarting Erlang][ERL-KICKSTART], which will you get started easily and fast.
- [An Erlang Course][ERL-COURSE], which contains a good summary of the things you've got to know to read and write Erlang.
Pattern matching is impressive, especially when you look at constructs such as case and if, which really simplify a lot.
The way named parameters are passed ([{name:value},{name:value},...]) is a bit ugly. Erlang can be quite verbose when you have to carry state from one function to another (but this could be simplified using macros).
- -