Like most programming languages, Erlang lets you define functions to help you represent repeated calculations. While Erlang functions can become complicated, they start out reasonably simple.
Erlang provides a tool for creating functions in the shell, the appropriately named fun
. For example, to create a function that calculates the velocity of a falling object based on the distance it drops in meters, you could create the following:
1>
FallVelocity
=
fun
(
Distance
)
->
math
:
sqrt
(
2
*
9
.
8
*
Distance
)
end
.
#Fun<erl_eval.6.111823515>
You can read that as a pattern match that binds the variable FallVelocity
to a function that takes an argument of Distance
. The function returns (I like to read the ->
as “yields”) the square root of 2 times a gravitational constant for Earth of 9.8 m/s squared, times Distance (in meters). Then the function comes to an end
, and a period closes the statement.
If you want to include multiple statements in a function defined by a fun
, separate them with commas, like FallVelocity = fun(Distance) -> X = (2 * 9.8 * Distance), math:sqrt(X) end.
You can read the commas as and.
The return value in the shell, #Fun<erl_eval.6.111823515>
, isn’t especially meaningful by itself, but it tells you that you’ve created a function and didn’t just get an error. The number in the return value will probably be different. If you want a slightly more detailed sign that Erlang understood you, you can use the b()
shell command to see what it thinks:
2>
b
().
FallVelocity =
fun(Distance) ->
math:sqrt(2 * 9.8 * Distance)
end
ok
Conveniently, binding the function to the variable FallVelocity
lets you use that variable to calculate the velocity of objects falling to Earth:
3>
FallVelocity
(
20
).
19.79898987322333
4>
FallVelocity
(
200
).
62.609903369994115
5>
FallVelocity
(
2000
).
197.9898987322333
If you want those meters per second in miles per hour, just create another function. You can copy and paste the earlier results into it (as I did here), or pick shorter numbers:
6>
Mps_to_mph
=
fun
(
Mps
)
->
2
.
23693629
*
Mps
end
.
#Fun<erl_eval.6.111823515>
7>
Mps_to_mph
(
19
.
79898987322333
).
44.289078952755766
8>
Mps_to_mph
(
62
.
609903369994115
).
140.05436496173314
9>
Mps_to_mph
(
197
.
9898987322333
).
442.89078952755773
I think I’ll stay away from 2000-meter drops. Prefer the fall speed in kilometers per hour?
10>
Mps_to_kph
=
fun
(
Mps
)
->
3
.
6
*
Mps
end
.
#Fun<erl_eval.6.111823515>
11>
Mps_to_kph
(
19
.
79898987322333
).
71.27636354360399
12>
Mps_to_kph
(
62
.
609903369994115
).
225.3956521319788
13>
Mps_to_kph
(
197
.
9898987322333
).
712.76363543604
You can also go straight to your preferred measurement by nesting the following calls:
14>
Mps_to_kph
(
FallVelocity
(
2000
)).
712.76363543604
However you represent it, that’s really fast, though air resistance will slow those down a lot in reality.
This is handy for repeated calculations, but you probably don’t want to push this kind of function use too far in the shell, as flushing your variables or quitting the shell session makes your functions vanish.
Most Erlang programs define their functions in compiled modules rather than in the shell. Modules are a more formal place to put programs, and they give you the ability to store, encapsulate, share, and manage your code more effectively.
Each module should go in its own file, with an extension of .erl. You should use name_of_module.erl, where name_of_module is the name you specify inside of the module file. Example 2-1, which you can find in the examples archive at ch02/ex1-drop, shows what a module, drop.erl, containing the functions previously defined might look like.
-
module
(
drop
).
-
export
([
fall_velocity
/
1
,
mps_to_mph
/
1
,
mps_to_kph
/
1
]).
fall_velocity
(
Distance
)
->
math
:
sqrt
(
2
*
9
.
8
*
Distance
).
mps_to_mph
(
Mps
)
->
2
.
23693629
*
Mps
.
mps_to_kph
(
Mps
)
->
3
.
6
*
Mps
.
There are two key kinds of information in this module. At the top, the -module
and the -export
directives tell the compiler key things about the module—its name and which functions it should make visible to other code that uses this module. The -export
directive gives a list of functions that should be made visible—not just their names, but their arity, the number of arguments they take. Erlang considers functions with the same name but different arity to be different functions.
All of the code in a module must be contained in functions.
Below the directives is a set of expressions defining functions, which look similar to the fun
declarations used earlier but not quite the same. The function names start with lowercase, not uppercase, and the syntax is slightly different. fun
and end
don’t appear, and the function name is immediately followed by parentheses containing a set of arguments.
If you get errors like "drop.erl:2: bad function arity drop.erl:6: syntax error before: Fall_velocity
“, it’s probably because you didn’t convert the names from your fun
/s so they start with a lowercase letter.
How do you make this module actually do something?
It’s time to start compiling Erlang code. The shell will let you compile modules and then use them immediately. The c()
function lets you compile code. You don’t need to (and shouldn’t) include the .erl file extension in the name you pass to c()
, though you can specify directory paths.
1>
ls
().
drop.erl
ok
2>
c
(
drop
).
{ok,drop}
3>
ls
().
drop.beam drop.erl
ok
Line 1 checks to see if the drop.erl source file is there, and shows the directory listing. Line 2 actually compiles it, and line 3 shows that a new file, drop.beam, is now available.
While you can compile code in another directory by specifying the directory path, the compiled code will end up in your current working directory. I prefer to be in the same directory as the file, whether I started the Erlang shell from there or navigated there with the commands shown in the previous chapter. For small projects, that tends to keep things more findable.
Now that you have drop.beam, you can call functions from the module. You need to prefix those calls with drop
, as shown in lines 4 and 5 of the following code.
4>
drop
:
fall_velocity
(
20
).
19.79898987322333
5>
drop
:
mps_to_mph
(
drop
:
fall_velocity
(
20
)).
44.289078952755766
It works the same as its predecessors, but now you can quit the shell, return, and still use the compiled functions.
6>
q
().
ok
$ erl
Erlang/OTP 19 [erts-8.2] [64-bit] [smp:8:8] [async-threads:10]
Eshell V8.2 (abort with ^G)
1>
drop
:
mps_to_mph
(
drop
:
fall_velocity
(
20
)).
44.289078952755766
Most Erlang programming (beyond tinkering in the shell) is creating functions in modules and connecting them into larger programs.
If you like the style of code that fun
allowed but also want your code stored more reliably in modules where it’s easier to debug, you can get the best of both worlds by using the fun
keyword to refer to a function you’ve already defined. To do that, you don’t use parentheses after fun
, and give the module name, function name, and arity.
1>
F_v
=
fun
drop
:
fall_velocity
/
1
.
#Fun<drop.fall_velocity.1>
2>
F_v
(
20
).
19.79898987322333
You can also do this within code in a module, and if you’re referring to code in the same module, you can leave off the module name preface. (In this case, that would mean leaving off drop:
and just using fall_velocity/1
.)
Erlang lets you bind a variable only once, but you might call a function many times over the course of a program. Doesn’t that mean the same variable will be bound many times?
Yes, it will be bound many times but always in separate contexts. Erlang doesn’t consider multiple calls to the same function to be the same thing. It starts with a fresh set of unassigned variables every time you call that function.
Similarly, Erlang doesn’t worry if you use the same variable name in different functions or function clauses. They aren’t going to be called in the same context at the same time, so there isn’t a collision.
The place you need to avoid reassigning values to an already bound variable is within a given path through a given function. As long as you don’t try to reuse a variable in a given context, you shouldn’t have to worry.
By default, modules have very thick walls, and everything inside of them is considered private. Everything going in or out of the module needs a pass to do so, and you grant those passes through module directives (sometimes called module attributes).
The preceding example showed two module directives—-module
and -export
. The -module
directive sets the name for the module, which outside code will need to know in order to call the functions. The -export
directive specifies the functions that outside code can reach.
This version of the drop
module mixes two different kinds of functions. The fall_velocity/1
function fits the name of the module, drop
, very well, providing a calculation based on the height from which an object falls. The mps_to_mph/1
and mps_to_kph/1
functions, however, aren’t about dropping. They are generic measurement-conversion functions that are useful in other contexts and really belong in their own module. Examples 2-2 and 2-3, both in ch02/ex2-combined, show how this might be improved.
-
module
(
drop
).
-
export
([
fall_velocity
/
1
]).
fall_velocity
(
Distance
)
->
math
:
sqrt
(
2
*
9
.
8
*
Distance
).
-
module
(
convert
).
-
export
([
mps_to_mph
/
1
,
mps_to_kph
/
1
]).
mps_to_mph
(
Mps
)
->
2
.
23693629
*
Mps
.
mps_to_kph
(
Mps
)
->
3
.
6
*
Mps
.
Next, you can compile them, and then the separated functions are available for use:
Eshell V5.9 (abort with ^G)
1>
c
(
drop
).
{ok,drop}
2>
c
(
convert
).
{ok,convert}
3>
ls
().
convert.beam convert.erl drop.beam drop.erl
ok
4>
convert
:
mps_to_mph
(
drop
:
fall_velocity
(
20
)).
44.289078952755766
That reads more neatly, but how might this code work if a third module needed to call those functions? Modules that call code from other modules need to specify that explicitly. Example 2-4, in ch02/ex3-combined, shows a module that uses functions from both the drop and convert modules.
-
module
(
combined
).
-
export
([
height_to_mph
/
1
]).
height_to_mph
(
Meters
)
->
convert
:
mps_to_mph
(
drop
:
fall_velocity
(
Meters
)).
That looks much like the way you called it from the Erlang shell, but if you have a lot of calls to external modules, that can get verbose quickly. The -import
directive, shown in Example 2-5, lets you simplify your code, though it comes with the risk of confusing other people who think the imported functions must be defined within this module. (You can find this in ch02/ex4-combined.)
-
module
(
combined
).
-
export
([
height_to_mph
/
1
]).
-
import
(
drop
,
[
fall_velocity
/
1
]).
-
import
(
convert
,
[
mps_to_mph
/
1
]).
height_to_mph
(
Meters
)
->
mps_to_mph
(
fall_velocity
(
Meters
)).
For now, it’s probably best to know about the -import
directive so you can read other people’s code, but not to use it unless you just can’t resist. It can make it harder to figure out where bugs are coming from, which may cost you more time than the extra typing.
Erlang includes one other directive that’s similarly convenient but not best practice to use: -compile(export_all)
. That directive tears down the module wall, making all functions available for external calls. In a module where everything is supposed to be public, that might save you typing out all the functions and all the arities of your module. However, it also means anyone can call anything in your code, exposing a lot more surface area for misunderstandings and complex debugging. If you just can’t resist, it’s available, but try to resist.
You can also make up your own user directives. -author(Name)
and -date(Date)
are commonly used. If you make up your own directives, they can have only one argument. If you spend enough time in Erlang, you’ll also encounter the following directives: -behaviour(Behaviour)
, -record(Name, Fields)
, and -vsn(Version)
.
Your programs can run perfectly well without documentation. Your projects, however, will have a much harder time.
While programmers like to think they write code that anyone can look at and sort out, the painful reality is that code even a little more complicated than that shown in the previous examples can prove mystifying to other developers. If you step away from code for a while, the understanding you developed while programming it may have faded, and even your own code can seem incomprehensible.
The simplest way to add more explicit explanations to your code is to insert comments. You can start a comment with %
, and it runs to the end of the line. Some comments take up an entire line, while others are short snippets at the end of a line. Example 2-6 shows both varieties of comments.
-
module
(
combined
).
-
export
([
height_to_mph
/
1
]).
% there will be more soon!
%%% combines logic from other modules into a convenience function.
height_to_mph
(
Meters
)
->
convert
:
mps_to_mph
(
drop
:
fall_velocity
(
Meters
)).
The Erlang compiler will ignore all text between the %
sign and the end of the line, but humans exploring the code will be able to read them.
Why are there multiple percent signs at the start of the line? The Erlang Emacs mode and many other Erlang tools expect the number of percent signs to indicate levels of indentation. Three percent signs (%%%
) mean the comment will be formatted flush left, two percent signs (%%
) mean the comment is indented with surrounding code, and a single percent sign (%
) is used for comments on the end of a line.
Informal comments are useful, but developers have a habit of including comments that help them keep track of what they’re doing while they’re writing the code. Those comments may or may not be what other developers need to understand the code, or even what you need when you return to the code after a long time away. More formal comment structures may be more work than you want to take on in the heat of a programming session, but they also force you to think about who might be looking at your code in the future and what they might want to know.
Erlang includes a documentation system called EDoc, which converts comments placed in the code into navigable HTML documentation. It relies on specially formatted comments, a directive, and occasionally an extra file to provide structured information about your modules and application.
The modules in this chapter are very simple so far, but there is enough there to start documenting, as shown in the files at ch02/ex5-docs. Example 2-7 presents the drop
module with more information about who created it and why.
%% @author Simon St.Laurent <simonstl@simonstl.com> [http://simonstl.com]
%% @doc Functions calculating velocities achieved by objects
%% dropped in a vacuum.
%% @reference from <a href= "http://shop.oreilly.com/product/0636920025818.do" >
%% Introducing Erlang</a>,
%% O'Reilly Media, Inc., 2017.
%% @copyright 2017 by Simon St.Laurent
%% @version 0.1
-
module
(
drop
).
-
export
([
fall_velocity
/
1
]).
fall_velocity
(
Distance
)
->
math
:
sqrt
(
2
*
9
.
8
*
Distance
).
Erlang can build the files for you using the EDoc file
function:
Eshell V5.9 (abort with ^G)
1>
edoc
:
files
([
"drop.erl"
],
[{
dir
,
"doc"
}]).
ok
You’ll now have a collection of files in the doc subdirectory. If you open drop.html in a browser, you’ll see something like Figure 2-1.
All of that metadata is great, and it can be gratifying to see your name “in lights.” However, unless you have a complex story to tell about your module as a whole, it’s likely that the core of the documentation will appear at the function level.
The drop
module contains one function: fall_velocity/1
. You probably know that it takes a distance in meters and returns a velocity in meters per second for an object dropped in a vacuum on Earth, but the code doesn’t actually say that. Example 2-8 shows how to fix that with EDoc comments and the @doc
tag.
%% @doc Calculates the velocity of an object falling on Earth
%% as if it were in a vacuum (no air resistance). The distance is
%% the height from which the object falls, specified in meters,
%% and the function returns a velocity in meters per second.
fall_velocity
(
Distance
)
->
math
:
sqrt
(
2
*
9
.
8
*
Distance
).
Figure 2-2 shows the result, which is considerably more helpful than the previous blank space around the function. It neatly takes the first sentence of the information following @doc
and puts it in the index, using the whole description for the Function Details section. You can also use XHTML markup in the @doc
section.
That’s a major improvement, but what if a user specifies “twenty” meters instead of 20 meters? Because Erlang doesn’t worry much about types, the Erlang code doesn’t say that the value for Distance
has to be a number or the function will return an error.
You can add a directive, -spec
, to add that information. In some ways it feels like a duplicate of the method declaration. In this case, it’s simple, as shown in Example 2-9.
%% @doc Calculates the velocity of an object falling on Earth
%% as if it was in a vacuum (no air resistance). The distance is
%% the height from which the object falls, specified in meters,
%% and the function returns a velocity in meters per second.
-
spec
(
fall_velocity
(
number
())
->
number
()).
fall_velocity
(
Distance
)
->
math
:
sqrt
(
2
*
9
.
8
*
Distance
).
EDoc will combine the types specified in the -spec
directive with the parameter names in the actual function declaration to produce the documentation shown in Figure 2-3.
This chapter has really demonstrated only the number()
type, which combines integer()
and float()
. Appendix A includes a full list of types. If you really want to explore types in Erlang, Dialyzer is the primary tool at the moment.
Sometimes you want information like the author and copyright data to appear in every module, often when it varies from module to module. Other times that becomes clutter, and it’s easier to put it into one place where it applies to all of your modules.
You can create an overview.edoc file in your project’s doc directory. Its content looks much like the markup used in the modules, but because it isn’t mixed with code, you don’t need to preface every line with %%
. The overview.edoc file for this project might look like Example 2-10.
@author Simon St.Laurent <simonstl@simonstl.com> [http://simonstl.com] @doc Functions for calculating and converting velocities. @reference from <a href= "http://shop.oreilly.com/product/0636920056690.do" >Introducing Erlang</a>, O'Reilly Media, Inc., 2017. @copyright 2017 by Simon St.Laurent @version 0.1
Now, if you regenerate documentation and click on the Overview link, you’ll see something like Figure 2-4.
If you create similar documentation in each of the Erlang files and run edoc:files(["drop.erl", "convert.erl", "combined.erl"]).
in the Erlang shell, EDoc will build a neat if somewhat plain set of frame-based documentation for your application, as shown in Figure 2-5.
This is just an introduction to EDoc. For more, see Chapter 18 of Erlang Programming, where you can learn about fun things like the @todo
tag.
You can learn more about working with functions and modules in Chapters 2, 3, and 9 of Erlang Programming (O’Reilly); Chapter 4 of Programming Erlang, 2nd Edition (Pragmatic); Sections 2.3, 2.5, and 2.7 of Erlang and OTP in Action (Manning); and Chapters 2 and 3 of Learn You Some Erlang For Great Good! (No Starch Press). There’s more on documentation in Chapter 18 of Erlang Programming and types in Chapter 30 of Learn You Some Erlang For Great Good!.