
Good evening, dear Khabrovchane. We continue to study Erlang for the little ones.
In the
last chapter, we looked at the basic data types, lists, and tuples. And also learned how to use the comparison with the sample and the generator lists.
In this chapter, we will go up to the next step and look at the modules and functions.
')
The source codes for the chapter are
here .
In Erlang, all functions are divided into modules. No function can be an exception. Standard language functions that we call as “global” (for example,
length
,
hd
,
tl
) are actually also inside the module. These are “built-in functions” (They are called
BIF - Built-In Functions) and they belong to the
erlang
module. This module is imported by default, so you can work with them as with separate functions (for importing modules, see below).
Modules
A module is a group of logically related functions that are combined under one name. Roughly speaking, modules in Erlang are analogous to namespaces from imperative languages. They are used to combine functions that have a similar purpose. For example, functions for working with lists are in the
lists
module, and input-output functions in the
io
module.
In order to call a function, you must use the following construction:
ModuleName:FunctionName(Arg1, Arg2, ..., ArgN)
. For example, let's call a function that returns the element of the passed tuple with the specified number. This function is called
element
and is located in the
erlang
module.
1> erlang:element(3, {23,54,34,95}). 34
It is also possible to call functions without explicitly specifying the module. This is written a little further.
Modules contain functions and attributes.
Module Attributes
Attributes are not variables, as in imperative languages. In Erlang, the attributes of a module are its metadata, such as name, version, author, list of imported functions, etc. Attributes are used by the compiler. Also one of them can get useful information about a module for itself without having to understand the source code (for example, version and author).
Attributes are specified at the very beginning of the file with the module and have the following form:
-Name(Arg).
. The module name must be an atom. Each attribute is indicated on a separate line.
You can assign any attributes to the module that you want, for example, to describe your mood that you had during its creation. There are also a number of predefined attributes. And now we can consider the most frequently used. For clarity, we will create a module that will contain functions that perform the most basic mathematical operations: addition, subtraction, multiplication and division.
-module (Name).The name of the module is the only required attribute and it must be specified first. Without it, your module simply won't compile. The argument takes an atom - the name of the module. Let's call our module
mySuperModule
.
-module(mySuperModule).
Now we have a fully functional module. We declared the only required attribute and now our module can be compiled. True, it is absolutely useless, because there is not a single function in it. But in fact - it is a finished module.
-export ([Fnct1 / Arity, Fnct2 / Arity, ..., FnctN / Arity])The list of exported functions is a list of module functions that will be accessible from the outside. The accepted attribute is a list of functions. Here
Fnct
is the name of the function, and
Arity
is the number of arguments it takes (arity). Our module will export four functions:
add
,
subtr
,
mult
,
divis
(addition, subtraction, multiplication, division). Each function will take two arguments.
-export([add/2, subtr/2, mult/2, divis/2]).
Remember that functions that you do not specify in the export list will not be possible to call from outside the module. You can work with them only inside the module.
Export is a means of achieving encapsulation in a module. As you might have guessed, the exported functions are analogous to the public methods of the class from imperative languages, and the rest are analogous to the closed ones.
-import (ModuleName, [Fnct1 / Arite, Fnct2 / Arity, ..., FnckN / Arity]).This attribute indicates that we want to import from the
ModuleName
functions listed in the list, which is passed to the second argument. Each imported module is specified in a
separate attribute.
Why import features? As mentioned above, to access a function from another module, you must specify its full name of the form
ModuleName:FunctionName()
. If you do not want to specify the name of the module each time, you need to import it. This attribute is an analogue of the
#using
directive from the C ++ language. But do not abuse the import. The full name of the function is much clearer. After seeing it, you can immediately tell which module the called function belongs to. In the case of a short name, you will have to memorize from which module this function was imported.
We will use the full names of the functions, but if we wanted to use short names, we could write something like the following:
-import(io, [format/2]).
Well, for example, let's specify some arbitrary attribute. Let it be the name of the author.
-author("Haru Atari").
A complete list of predefined attributes can be found in the
official documentation .
If you now try to compile our module, you will get an error:
1> c(mySuperModule). ./mySuperModule.erl:2: function add/2 undefined ./mySuperModule.erl:2: function divis/2 undefined ./mySuperModule.erl:2: function mult/2 undefined ./mySuperModule.erl:2: function subtr/2 undefined
As is clear from the text of the error, the compiler cannot find in our file the functions that we specified in the import list. And this is logical, because we have not added them yet. Let's correct this error and create our functions.
Functions
In the base case, the functions in Erlang are as follows:
FnctName(Arg1, Arg2, ..., ArgN) -> FunctionBody.
The name of a function is an atom, and its body is one or more expressions separated by
commas . At the end of the function body, a dot is put. If the function contains only one expression, it will be clearer to write it in one line.
add(X, Y) -> X + Y.
Our function takes two arguments and returns their sum. Note the absence of the word
return
. The fact is that in Erlang, the function always returns the result of the last expression. In our case, this is the result of addition. Therefore, the word
return
is simply not necessary.
But not always the function consists of a single expression. In such a case, the function body is indented to the left of the rest of the code. In this case, our function will look like this:
add(X, Y) -> doSomthing(), X + Y.
Now add the remaining three functions yourself. Let's compile our module to test what we wrote.
Compilation
Programs written in Erlang are compiled into an intermediate byte code, which is then executed in a virtual machine. Thanks to this, applications written in Erlang are cross-platform.
There are several virtual machines for Erlang. But the most common is BEAM (Bogdan / Björn's Erlang Abstract Machine). There are a number of virtual machines (JAM and WAM), but they are almost never used and we will not consider them.
There are two ways to compile: from a terminal or the Erlang command line. Let's consider both options.
To compile from the terminal, you must go to the directory with the file and call the command
erlc FileName.erl
. For our module, it will look like this (you will have your own path).
cd ~/Erlang-for-the-little-ones/02/sources erlc mySuperModule.erl
In order to do this from the Erlang command line, you must also go to the required directory with the command
cd("DirName")
, and then call the command
c(ModuleName)
. Please note we pass the name of the module, not the file. The extension does not need to be specified.
1> cd("~/Erlang-for-the-little-ones/02/sources/"). /home/haru/Erlang-for-the-little-ones/02/sources ok 2> c(mySuperModule). {ok,mySuperModule}
As a result of the compilation, the file
mySuperModule.erl
to the file
mySuperModule.beam
. This is the compiled module. Now you can use it. Let's try:
1> mySuperModule:add(2, 4). 6 2> mySuperModule:divis(6,4). 1.5
It is worth mentioning that it is possible to pass the compiler flags to the compiler. To do this, the second argument must be passed to the
c()
function - a list of flags. For example, let's compile our module in debug mode:
c(mySuperModule, [debug_info]).
We will not focus on this now. A separate chapter will be devoted to this topic. But if you're interested, then with a list of keys, you can find on
the documentation page .
Conclusion
In this chapter, we introduced the modules and functions. We also learned how to compile our code so that it can be used.
In the
next chapter, we will look at the syntax of functions in more detail, as well as learn how to use pattern matching in functions.
Thank you for reading. Have a good mood and weak connectedness.
PS About typos and errors please report to the PM. Thank you for understanding.