Elixir Notes (1) – Basic Issues
Published:
Basic notes for learning Elixir.
1. Returning values
(1) Function
Elixir has no keyword “return”. It regards value of the last expression in a function as its returning value.
def addOne(arg) do
arg + 1
end
(2) Block
In Elixir, a block (do .. end) also has returning value. It is the same case as a function, which means the returning value of a block is value of the last expression in this block.
So this code is correct, you will assign value of var “ans” correctly.
Note: This is a common way to assign or change values of existing variables according to different cases, though it seems strange. We will explain it later in this article.
ans =
if list == [nil] do
IO.puts XXXX
ans # the last expression in the first case
else
IO.puts XXXX
ans ++ list # the last expression in the second case
end
But this code is wrong because the last expression is not the value to be assigned to “ans”. This will cause “ans” to become nil.
# returning value of this block would be the value of "IO.puts XXXX" instead of what we want.
ans =
if list == [nil] do
ans
IO.puts XXXX
else
ans ++ list
IO.puts XXXX
end
2. Rebinding of variables
(1) Elixir will always rebind a variable when using assignment expression.
For example,
x = [1]
x = x ++ [2]
x would be [1, 2] finally. That is obvious. But the left-hand-side ‘x’ in the second line is not the one in the first line actually. The left-hand-side ‘x’ in the second line is a rebound variable, that is, we did not change the original ‘x’ but rebound the name ‘x’ to another list [1, 2].
This will be a disaster when you want to change existing variables in a sub-block.
For example,
x = [1]
if 2 > 1 do
x = x ++ [2]
else
x = x -- [1]
end
After this block, x is still [1]. It is because in the block, x will be rebound. So the x inside is not the same one as the x outside. So after that block, x is unchanged.
In short, in Elixir, variables inside a block will not overwrite variables outside with same names.
(2) Then what if we want to change an existing variable inside a block? Let’s recall that blocks in Elixir have their returning values, which means we can let the existing variable receive the returning value like below:
x = [1]
x =
if 2 > 1 do
x ++ [2]
else
x -- [1]
end
Then x would be [1, 2] finally.
3. Module attributes
Module attributes (the variables starting with “@”) are more like constants, which would be initialized during compilation. So they cannot be changed in runtime. That means you may not use them as state variables to store some intermediate results.
4. State memorization
For Elixir and most other functional programming languages, the common way to store states is to pass current states as arguments into next function call instead of using global state variables.
For example, suppose you want to compare the difference between current process list and previous list 5 seconds ago periodically, you can do like below:
def compare(previous_list) do
cur_list = Process.list()
if cur_list != previous_list do
IO.puts "Processes changed!"
end
:time.sleep(5000)
compare(cur_list) # pass current state as argument to next function call
end
5. Loop
Elixir has no “while” keyword. So you cannot write loop like many other languages.
There are normally two ways to do iteration.
(1) Use “for” keyword.
Fortunately, Elixir has “for” keyword to iterate over a list, just like Python and C#/C/C++.
list = Enum.to_list(1..10)
# list contains all integers from 1 to 10, including 1 and 10.
for i <- list do # iterate over list
IO.puts i
end
But do not abuse it. It is not recommended.
(2) Recursion (Recommended)
Functional languages normally prefer to use recursion to implement loop. Let’s rewrite the example above using recursion:
def loop(i) do
if i < 10 do
IO.puts i
loop(i+1)
end
end
loop(1)
6. Daemon
Elixir will not set the main process as daemon process by default. For example,
# this is our main code file "project.exs"
p = spawn(some_module, :loop_and_do_something, [])
If we run “project.exs”, it would NOT act as a daemon process, that is, when project.exs run to its end, it finishes, and kills all processes spawned inside it. That is weird, but true.
So we have to set our own daemon process in the end of project.exs to check the status of our program and to determine when to end everything.
Actually, our own “daemon process” is not necessary to be a real process which is created by spawn(). It can be a recursive function which loops until we decide to end our program, like below:
# this is our main code file "project.exs"
p = spawn(some_module, :loop_and_do_something, [])
checkStatus(...)
# checkStatus() will check status periodically until some conditions are satisfied.
7. Input arguments from command line
Only need one line to do this.
[arg1, arg2, ..., argn] = System.argv()
There is one point you need to care about. All args read by System.argv() are strings. So you may have to convert them to the types you want.
8. Anonymous recursive function
We can use recursion in anonymous functions, even if they do not have names to be called.
print = fn f, i -> # the first parameter stands for this function itself
if i < 10 do
IO.puts i
f.(f) # call self like this, do not forget the "."
end
end
print.(print, 1)
# call print with "." since it is defined as an anonymous function
# the first parameter is print itself
9. Identify a process by name
If you want to find a process, you can use its name or PID.
(1) PID:
There are many ways to get a process’s PID, like self(), or returning values of spawn(), …
(2) Process name:
Before using name to find a process, you have to register its name like below:
Process.register(pid, name)
This will create mapping between the specific process’s PID and name such that you can use name to identify the process.
Note: It is not necessary to always register a process’s name. To avoid name conflict, you’d better only use PID to identify a process, especially when you need to spawn multi processes from one module.