Language Reference¶
Values¶
Boolean¶
Boolean Data Type in Wikipedia
This is the simplest data type, it can take two values:
- true
- false
Efene has another data type that allows far more flexibility to signal “states” in your program called atoms, they are covered bellow.
Numbers¶
Integers¶
Integer Data Type in Wikipedia
This data type represents an integer number, it has only one syntax, the one you are used to, the following are examples of integers:
- 0
- 1
- 42
- 9002
Efene doesn’t have notation for hexadecimal, binary or other bases, they can be added using tagged data which is covered below.
Floats¶
This data type represents a decimal number, it has only one syntax, the one you are used to, the following are examples of floats:
- 0.0
- 1.2
- 42.3
- 9002.2009
Efene doesn’t have notation for scientific notation, they may be added in the future, for now it can be added using tagged data which is covered below.
Strings¶
There are two ways of representing text (strings) in erlang they have different pros and cons, we won’t cover them here.
List Strings¶
A List String is simply a list of characters represented as numbers, it’s actually not a data type on itself “hello” is shorthand for the list [104,101,108,108,111].
List Strings are enclosed in double quotes (”), examples of List Strings:
T1 = ""
T2 = "a"
T3 = "hi there"
T4 = "this \"is\" also a string"
Binary Strings¶
A Binary String is a binary representation of text, Binary Strings are enclosed in single quotes (‘), examples of Binary Strings:
B1 = ''
B2 = 'a'
B3 = 'hi there'
B4 = 'this \'is\' also a string'
Note
The Erlang atom syntax with single quotes is supported in efene with tagged values and backticks, see below.
Chars¶
A character is a number representing a character in a string:
A = #c "A"
Hello = [#c "h", #c "e", #c "l", #c "l", #c "o"]
Lists¶
A List is a variable sequence of elements, it’s represented by a comma separated sequence of other data types (including nested lists) enclosed in opening and closing square brackets ([ and ]), examples of lists:
L1 = []
L2 = [1]
L3 = [1, 2]
L4 = [[[]]]
the last element of a list can have a trailing comma:
L2 = [1,]
L3 = [1, 2,]
Cons Lists¶
You can create a list like [1,2,3] with an alternative syntax:
1 :: 2 :: [3]
It’s useful to extract the head and keep the tail:
H :: T = [1,2,3]
Now H is 1, and T is [2, 3]
You can do the reverse and create a new list by “consing” a new head to an existing list:
L = 1 :: [2, 3]
Now L is [1,2,3]
Maps¶
A Map is a sequence of elements associating keys to values, it’s represented by a comma separated sequence of association pairs enclosed in opening and closing curly brackets ({ and }), examples of maps:
M1 = {}
M2 = {one: 1}
M3 = {one: 1, 1: one}
The last element of a map can have a trailing comma:
M2 = {one: 1,}
M3 = {one: 1, 1: one,}
You can extract fields from a map by using pattern match replacing : for =
M = {one: 1, two: 2}
{one = One, two = Two} = M
You can update an existing map with the merge operator #:
M1 = M#{three: 3}
Tuples¶
A Tuple is a fixed sequence of elements, it’s represented by a comma separated sequence of other data types (including nested tuples) enclosed in opening and closing parenthesis ( and ), examples of tuples:
T1 = ()
T2 = (1,)
T3 = (1, 2)
T4 = (((),),)
The last element of a list can have a trailing comma, it’s obligatory in one item tuples to distinguish from an expression in parenthesis:
T2 = (1,)
T3 = (1, 2,)
Atoms¶
An atom is a literal, a constant with name, examples of atoms:
A1 = ok
A2 = error
A3 = hi_there
If you want to have spaces or symbols in an atom you can wrap it in “`”:
A4 = `hello world!`
Variables¶
If a variable is bound to a value, the return value is this value. Unbound variables are only allowed in patterns.
Variables start with an uppercase letter or underscore (_) and may contain alphanumeric characters and underscores. Examples:
X
Name1
PhoneNumber
Phone_number
_
_Height
Variables are bound to values using pattern matching. Erlang uses single assignment, a variable can only be bound once.
The anonymous variable is denoted by underscore (_) and can be used when a variable is required but its value can be ignored. Example:
H :: _ = [1,2,3]
Variables starting with underscore (_), for example _Height, are normal variables, not anonymous. They are however ignored by the compiler in the sense that they will not generate any warnings for unused variables.
Note that since variables starting with an underscore are not anonymous, this will match:
(_,_) = (1,2)
But this will fail:
(_N,_N) = (1,2)
Process Id (Pid)¶
A process identifier, pid, identifies a process.
spawn/1,2,3,4, spawn_link/1,2,3,4 and spawn_opt/4, which are used to create processes, return values of this type.
Reference¶
A reference is a term which is unique in an Erlang runtime system, created by calling make_ref/0.
Function¶
Anonymouse Functions¶
Functions can be created and assigned to variables inside other functions, the syntax is:
fn [case <parameter>*: <body>]+ [else: <body>] end
Simples function:
One = fn one case: 1 end
Receiving arguments:
Identity = fn case Val: Val end
AddTwo = fn case A, B: A + B end
Multiple case clauses:
Division = fn
case A, 0:
(error, division_by_zero)
case A, B:
A / B
end
Cases with else:
MyXor = fn
case true, false: true
case false, true: true
else: false
end
Named Functions¶
Named Functions exist to refer to a function inside of it to do recursion as you would do with a toplevel function.
The syntax is the same as an anonymous function but with a variable as it’s name, for example:
F3 = fn Fact
case 0: 1
case N: N * Fact(N - 1)
end
Notice that the resulting function is stored in F3 and you must use that name to call it, the “named” part is only to refer to itself, if a function doesn’t refer to itself then you don’t need a named function.
You can see more details and examples in this article: http://joearms.github.io/2014/02/01/big-changes-to-erlang.html
Function References¶
If we want to pass a reference to a function as a parameter or set it to a variable we can use the function reference syntax.
It’s composed of the keyword fn, the function name, including module if needed and it’s arity, that is, the number of parameters it receives.
Examples:
CR1 = fn a:0
CR3 = fn a.b:2
CR4 = fn a.B:3
CR5 = fn A.b:4
CR6 = fn A.B:5
Notice you can’t make a function reference to a function stored on a variable like this:
CR2 = fn A:1
since it’s already a function reference on itself, this will result in an error.
Function Calls¶
There are many ways to call a function, it depends if the function is local, from another module and if we know the name and/or the module in advance or we have a reference to it in a variable.
The simples way to call a local function (or an automatically imported function) is just giving the name and passing the parameters.
Local call:
One = identity(1)
Call to a function in another module:
R = lists.seq(1, 10)
Dynamic local call:
I = fn identity:1
One = I(1)
Dynamic call to a function in another module:
L = lists
S = seq
R = L.S(1, 10)
R = lists.S(1, 10)
R = L.seq(1, 10)
L1 = fn lists.seq:2
L2 = fn lists.S:2
L3 = fn L.seq:2
L4 = fn L.S:2
R = L1(1, 10)
R = L2(1, 10)
R = L3(1, 10)
R = L4(1, 10)
Threading function calls:
IsOdd = fn case X:
X % 2 is 0
end
Increment = fn case X:
X + 1
end
MyMap = fn case List, Fun:
lists.map(Fun, List)
end
lists.seq(1, 10) ->>
lists.filter(IsOdd) ->
MyMap(Increment)
(I define MyMap to reverse the order of the arguments of lists.map so I can use -> in the example)
the ->> operator inserts the value from the left as the last argument in the function on the right (imagine that ->> sends the value to the other side)
the -> operator inserts the value from the left as the first argument in the function on the right (imagine that -> sends the value to the closest side)
Higher order function calls:
MapR = fn case List, Fun:
lists.map(Fun, List)
end
R = lists.seq(1, 10)
lists.map(R) <<- case X:
X + 1
end
MapR(R) <- case X:
X + 1
end
The <- operator inserts the anonymous function as the last argument in the function (imagine that <- sends the value to the closest side).
The <<- operator inserts the anonymous function as the first argument in the function (imagine that <<- sends the value to the other side).
Tagged Values¶
Expressions and values can be tagged in efene, this is inspired from the edn format.
This allows to transform a value or expression at compile time to some other value or expression by tagging it.
a tagged value is comprised of the # sign followed by a path, that is a sequence of atoms or variables joined with dots, examples of tagged values:
#atom "I'm an atom"
#c "A"
The first case transforms the string to an atom at compile time, it has the same effect as the single quotes in erlang.
The second case transforms a string of length 1 into a character type, it has the same effect as the dolar sign in erlang.
A tagged expression works the same as a tagged value but applies to expressions, the syntax is the same except that the ^ symbol is used instead of #:
^_ begin "this is ignored" end
It just “ignores” the expression or value that follows.
Efene adds support for some erlang syntax via tagged values and expressions as you can see above.
In the future this functionality will be provided to compiler extensions that can convert at compile time values or expressions into extra functionality, imagine string internationalization, logging, profiling, stdlib type constructors using values etc.
Records¶
A record is a compile time data structure that erlang transforms into tuples at run time with the name of the record in it, it’s kind of a named tuple where at run time field names are translated into tuple indexes.
To declare a record you have to add a record declaration at the top level of your modules, for example:
@record(person) -> (name, lastname, sex=female, age)
The person part is the name of the record, the items after the arrow in parenthesis are the record fields, you can provide default values for fields.
To instantiate a record:
P = #r.person {name: "bob", lastname: "sponge", age:29}
To update a record:
P1 = #r.person P#{age:28}
To pattern match against a record:
#r.person {age: Age} = P1
To get the value of a field:
Counter = #r.state.counter State
To get the tuple index of a field:
Binary¶
Binary is a data type to express erlang’s bit syntax, where you can specify the format of a binary, you can read more at erlang’s bit syntax docs
In efene binaries are implemented using a tagged map that contains a sequence of key/value pair for each field describing format of that field, here is an example covering all the alternatives:
#b {_: _,
A: _,
JustSize: 8,
JustType: binary,
E: {},
_: {size: 8},
_: {type: float},
_: {sign: unsigned},
_: {endianness: big},
_: {unit: 8},
B: {size: 8, type: float, sign: signed, endianness: little, unit: 16}}
You can use _ on the key to ignore that field and on the value to provide defaults, on the value you can also provide {} to specify defaults.
If the value is an int it’s assumed to be the size property, if it’s an atom it’s assumed to be the type attribute.
Here is an example pattern matching an IPv4 packet:
#b {Version:4, IHL:4, TypeOfService:8, TotalLength:16, Identification:16,
FlagX:1, FlagD:1, FlagM:1, FragmentOffset:13, TTL:8, Protocol:8,
HeaderCheckSum:16, SourceAddress:32, DestinationAddress:32,
Rest: binary} = Packet
On a field you can specify the variable to match to, the size, type, sign, endianness and unit.
For a detailed explanation of what each of those values do please refer to erlang’s bit syntax docs.
Compile Time Information¶
Using tagged values we can get some information at compile time.
Current line:
Line = #i line
Current module name as an atom:
Module = #i module
Current module name as a string:
Module = #i module_string
Current function name:
FnName = #i function_name
Current function arity:
FnArity = #i function_arity
Operations¶
Boolean Operations¶
Op | Description | Erlang Equivalent |
---|---|---|
or | Short Circuit Or | orelse |
and | Short Circuit And | andalso |
xor | Xor | xor |
orr | Non Short Circuit Or | or |
andd | Non Short Circuit And | and |
Comparisson Operations¶
Op | Description | Erlang Equivalent |
---|---|---|
== | equal to | == |
!= | not equal to | /= |
< | less than | < |
<= | less than or equal to | =< |
> | greater than | > |
>= | greater than or equal to | >= |
is | exactly equal to | =:= |
isnt | exactly not equal to | =/= |
The arguments may be of different data types. The following order is defined:
number < atom < reference < fun < port < pid < tuple < list < bit string
Lists are compared element by element.
Tuples are ordered by size, two tuples with the same size are compared element by element.
When comparing an integer to a float, the term with the lesser precision will be converted into the other term’s type, unless the operator is one of is or isnt.
A float is more precise than an integer until all significant figures of the float are to the left of the decimal point.
This happens when the float is larger/smaller than +/-9007199254740992.0. The conversion strategy is changed depending on the size of the float because otherwise comparison of large floats and integers would lose their transitivity.
Concat Operations¶
Op | Description | Erlang Equivalent |
---|---|---|
++ | list concatenation | ++ |
– | list substraction | – |
The list concatenation operator ++ appends its second argument to its first and returns the resulting list.
The list subtraction operator – produces a list which is a copy of the first argument, subjected to the following procedure: for each element in the second argument, the first occurrence of this element (if any) is removed.
Warning
The complexity of A – B is proportional to length(A) * length(B), meaning that it will be very slow if both A and B are long lists.
Aritmetic Operations¶
Op | Description | Erlang Equivalent |
---|---|---|
+ | addition | + |
- | substraction | - |
* | multiplication | * |
/ | division | / |
% | remainder | rem |
// | integer division | div |
Binary Operations¶
Op | Description | Erlang Equivalent |
---|---|---|
| | binary or | bor |
& | binary and | band |
^ | binary xor | bxor |
<< | shift left | bsl |
>> | shift right | bsr |
Unary Operations¶
Op | Description | Erlang Equivalent |
---|---|---|
- | integer negative | - |
not | boolean not | not |
~ | binary not | bnot |
Expressions¶
When¶
Abstract Syntax¶
Simple:
when GuardSeq1:
Body1
else:
ElseBody
end
Complete:
when GuardSeq1:
Body1
else GuardSeq2:
Body2
...
else GuardSeqN:
BodyN
else:
ElseBody
end
Examples¶
when true:
io.format("guard evaluated to true")
else:
io.format("no guard evaluated to true")
end
when A < 10:
io.format("A < 10")
else A < 20:
io.format("A < 20 and >= 10")
else A < 30:
io.format("A < 30 and >= 20")
else:
io.format("A > 30")
end
Description¶
When expression is similar to if/else if/else in other languages but with some extra limitations.
This limitations come from the fact that when expressions are identical to function guard expressions.
The set of valid guard expressions (sometimes called guard tests) is a subset of the set of valid Erlang expressions. The reason for restricting the set of valid expressions is that evaluation of a guard expression must be guaranteed to be free of side effects. Valid guard expressions are:
the atom true,
other constants (terms and bound variables), all regarded as false,
term comparisons,
arithmetic expressions,
boolean expressions
short-circuit expressions (and/or)
calls to the BIFs
- is_atom/1
- is_binary/1
- is_bitstring/1
- is_boolean/1
- is_float/1
- is_function/1
- is_function/2
- is_integer/1
- is_list/1
- is_map/1
- is_number/1
- is_pid/1
- is_port/1
- is_record/2
- is_record/3
- is_reference/1
- is_tuple/1
If an arithmetic expression, a boolean expression, a short-circuit expression, or a call to a guard BIF fails (because of invalid arguments), the entire guard fails. If the guard was part of a guard sequence, the next guard in the sequence (that is, the guard following the next semicolon) will be evaluated.
A guard sequence is a sequence of guards, separated by semicolon (;). The guard sequence is true if at least one of the guards is true. (The remaining guards, if any, will not be evaluated.):
Guard1;...;GuardK
A guard is a sequence of guard expressions, separated by comma (,). The guard is true if all guard expressions evaluate to true:
GuardExpr1,...,GuardExprN
example:
when Cond1, Cond2; Cond3:
1
else Cond4; Cond5, Cond6, Cond7:
2
else:
3
end
Match¶
Abstract Syntax¶
match Expr:
case Pattern1 [when GuardSeq1]:
Body1
...
[case PatternN [when GuardSeqN]:
BodyN]
[else:
BodyElse]
end
Examples¶
Simple:
match Result:
case ok, Value:
io.format("everything ok ~p~n", [Value])
do_something(Value)
case error, Reason:
io.format("error: ~p~n", [Reason])
fail(Reason)
end
When and Else:
match Result:
case ok, Value when is_integer(Value), Value < 10:
io.format("everything ok, value is < 10: ~p~n", [Value])
do_something(Value)
case ok, Value when is_atom(Value):
io.format("everything ok, value is atom~p~n", [Value])
do_something_atom(Value)
case error, Reason:
io.format("error: ~p~n", [Reason])
fail(Reason)
else:
io.format("Value doesn't match any case")
end
Description¶
The expression Expr is evaluated and the patterns Pattern are sequentially matched against the result. If a match succeeds and the optional guard sequence GuardSeq is true, the corresponding Body is evaluated.
The return value of Body is the return value of the case expression.
If else is present and no Pattern matched BodyElse will be executed.
If there is no matching pattern with a true guard sequence, a case_clause run-time error will occur.
For¶
Abstract Syntax¶
for (<generator>;<filter>)+:
<body>
end
The for expression starts with the for reserved keywords followed with at least one generator and zero or more generators or filters separated by semicolons finished by a colon.
After the colon one or more expressions that will be evaluated as the body of the for for each element in the generators that pass the filter expressions.
Note that for is an expression, this means it returns a list with each item being the result of evaluating the last expression in the body. This means that if you are not interested in the result of the for expression, for example if you are only interested in side effects like printing sending messages or logging, then you should take care to avoid having a big value being generated as the last expression in the body, since this will be acumulated in a list and returned when the for finishes.
A generator is an expression like:
A in B
Where B should be evaluated to a sequence and each element of that sequence will be assigned to A and be available in the for body.
A filter is an expresion like:
when <condition>
where condition should be an expression that evaluates to true of false, if a filter returns false then the body wont be generated for that value.
Examples¶
For with one generator:
for X in lists.seq(1, 10):
X + 1
end
For with one generator and one filter:
for X in lists.seq(1, 10); when X % 2 is 0:
X + 1
end
For with two generators:
for X in lists.seq(1, 10); Y in lists.seq(10, 20):
(X, Y)
end
Try/Catch/After¶
Abstract Syntax¶
try
<body>
[catch <case>+ [<else>]]
[after <body>]
end
A try expression is an expression used to handle one or more expressions that may raise an exception, the expression starts with the try keyword followed by one or more expressions as the try body.
Optionally a catch section can be included starting with the catch keyword followed by one or more case clauses and optionally an else clause used when we want to catch any type of exception but we don’t care about the value being thrown.
Optionally an after* section can be included starting with the after keyword and followed by one or more expressions in the after body which will be executed no mather if an exception is being thrown or not, this is useful to run code that should run to do cleanup in both cases like closing a file handle.
The case clauses in the try expression are restricted to one or two arguments.
In case of having one argument the type of exception is assumed to be throw, in case of having two arguments the first must be the type of exception that the case clause will handle, the exception type must be one of:
- throw
- error
- exit
- an expression that evaluates to one of the above
- an unbound variable which will be bound with the exception type
the second argument can be used to pattern match or an unbound variable can be used to get the details of the exception.
Examples¶
No catch:
try
1/0
after
ok
end
Catch type and reason:
try
1/0
catch
case error, badarith: ok
end
Catch and after:
try
1/0
catch
case error, badarith: ok
after
ok
end
All possible catchs:
try
1/0
catch
case throw, T1: T1
case Throw: Throw
case error, E1: E1
case exit, X1: X1
case A, C: C
else: iselse
end
Receive/After¶
Abstract Syntax¶
receive
case Pattern1 [when GuardSeq1]:
Body1
...
[case PatternN [when GuardSeqN]:
BodyN]
[else:
BodyElse]
after ExprT:
BodyAfter
end
Examples¶
receive
case throw, T1: T1
case error, E1: E1
case exit, X1: X1
case A, C: C
else: iselse
after 1000:
ok
end
Description¶
Receives messages sent to the process.
The patterns Pattern are sequentially matched against the first message in time order in the mailbox, then the second, and so on.
If a match succeeds and the optional guard sequence GuardSeq is true, the corresponding Body is evaluated.
The matching message is consumed, that is removed from the mailbox, while any other messages in the mailbox remain unchanged.
The return value of Body is the return value of the receive expression.
receive never fails. Execution is suspended, possibly indefinitely, until a message arrives that does match one of the patterns and with a true guard sequence.
It is possible to augment the receive expression with a timeout, ExprT should evaluate to an integer. The highest allowed value is 16#ffffffff, that is, the value must fit in 32 bits. receive..after works exactly as receive, except that if no matching message has arrived within ExprT milliseconds, then BodyT is evaluated instead and its return value becomes the return value of the receive..after expression.
There are two special cases for the timeout value ExprT:
- infinity
- The process should wait indefinitely for a matching message, this is the same as not using a timeout. Can be useful for timeout values that are calculated at run-time.
- 0
- If there is no matching message in the mailbox, the timeout will occur immediately.
Macros¶
Macros are an extension to support using Erlang Macros defined in Erlang modules from efene, to use Erlang Macros from a module you need first to include that module in your efene module and then use them.
Macro Constants¶
Macro Constants are macros that are a definition of a name that expands to an expression, it can be used to name constants or to expand an expression in multiple places, to expand a macro constant you have to write the name of the macro constant tagged with the #m tag:
#m Author
#m LINE
#m PI
Macro Functions¶
Macro Functions are macros that receive arguments and use them to expand its definition using those arguments, to expand a macro call you have to write the macro as a function call tagged with the #m tag:
#m AUTHOR(bob)
#m Text(1 * 2 + 3)
#m AddPlusOne(2, 3)
Module Level Expressions¶
Top Level Function¶
Abstract Syntax¶
fn <name> [attribues] [cases] end
A top level function is defining by starting with the reserved keyword fn followed by the name as an atom.
Them zero or more attributes and then one or more case clauses finished with the end keyword.
Examples¶
Simples function:
fn one case: 1 end
Simple with attributes:
fn one @public case: 1 end
fn two @public
@doc("returns the number two")
case: 2
end
Receiving arguments:
fn identity case Val: Val end
fn add_two case A, B: A + V end
Multiple case clauses:
fn division
case A, 0:
(error, division_by_zero)
case A, B:
A / B
end
Cases with else:
fn my_xor_
case true, false: true
case false, true: true
else: false
end
Attributes¶
Well Known Attributes¶
Export¶
@export(hello/0, plus/2)
Has the same behavior as adding the @public attribute to a function, exports the function to be used from other modules.
Record Definition¶
@record(foo) -> (a, b = 12, c = true, d = 12)
Defined a record by providing a name and a tuple with field names as atoms and optionally a default value in case a value is not providing on construction.
For more information see Erlang’s Record Manual Page
Type Attributes¶
Literal Type¶
@type(tint) -> 42
@type(tatom) -> asd
@type(tbool) -> false
@type(lempty) -> []
List Type¶
@type(lone) -> [42]
@type(l3) -> [tatom()]
Range Type¶
@type(trange) -> range(1, 10)
Union Type¶
@type(tres) -> (ok, integer()) or (error, term()) or (stop, normal)
Binary Type¶
@type(bsempty) -> binary(0, 0)
@type(bsone) -> binary(4, 0)
@type(bsonemul) -> binary(0, 5)
@type(bstwo) -> binary(4, 5)
Parameterized Type¶
@type(p1(X)) -> (ok, X, X)
@type(p2(X, Y)) -> (ok, X, Y)
Function Type¶
@type(f1) -> fun()
@type(f2) -> fun(any, integer())
@type(f3) -> fun([boolean(), term()], integer())
@type(f4) -> fun([], integer())
Opaque Type and Record Type¶
@opaque(tperson) -> #r person
Version Attribute (vsn)¶
@vsn("1.2.0")
@vsn((1, 2, 0))
Module version. The parameter is any literal term and can be retrieved using beam_lib.version:1.
If this attribute is not specified, the version defaults to the MD5 checksum of the module.
On Load Attribute (on_load)¶
@on_load(fname/0)
This attribute names a function that is to be run automatically when a module is loaded. For more information, see Running a Function When a Module is Loaded.
Import Attribute (import)¶
@import(erlang, [phash2/1])
Imported functions. Can be called the same way as local functions, that is, without any module prefix.
Module, an atom, specifies which module to import functions from. Functions is a list similar as for export.
Include Attribute (include)¶
@include("path/to/file.hrl")
Include an erlang file in the current module, code is included in the current module and macros are available for use, see macro use example on how to use them here is the included file ms.hrl