CSE 121e: Week 02

The Fundamentals

Some Similarities and Differences

Shared Operators

Remember, both Python and Erlang have Modula as one of their ancestors. Because of this, it is not shocking to find out that they share many of the same operators as Modula. These include +, -, /, *, ==, !=, <, >, etc. By looking at Table 1 below, you can compare the operators for the two languages. When you do so, you will find that Python has a few that Erlang does not. You will also see that the logical operators exist in both languages with Erlang having one more.

Erlang's missing operators

Because Python and Erlang are different languages and the creators of Erlang had different opinions than those who created Python, there are some Python operators that you should expect to be different and others will be missing in Erlang. They are:

Don't get the wrong idea. Each of these types of behaviors, except the assignment ones, can be done in Erlang, they will just be done a little differently. Take a look at Table 1 to see how a few of these are done.

Table 1: Erlang Equivalents of Python Operators
Python Operator Erlang Operator Description Erlang Example
== == Equal to 5 == 5
!= or <> /= Not equal to 7 /= 4
<= =< Less than or equal to 3 =< 4
< < Less than 2 < 3
>= >= Greater than or equal to 6 >= 2
> > Greater than 9 > 5
None =:= Exactly equal to. Compares values and types. 1 =:= 1.0.
false
1 == 1.0.
true
None =/= Exactly not equal to. Compares values and types. A =/= B
+ + Addition 3 + 4
- - Subtraction 8 - 2
* * Multiplication 5 * 6
/ / Floating point division 10 / 2
// div Integer division 13 div 3
% rem Integer remainder of X/Y 11 rem 3
** math:pow(A,B) Exponentiation math:pow(A,B)
+=, -=, *=, **= None Assignment N/A
not not Unary logical NOT not true
and and Logical AND true and false
or or Logical OR true or false
None xor Logical XOR true xor false

Erlang has other operators available as well.

Variables

In Python, a variable can hold any type of value and can change the type of value it holds. For example, this code is valid in Python.

age = 3
age = 5
age = 4.2
age = 'old'

Erlang variables behave quite differently. Just like in Python, they can hold any type but, not only can't the type change, but the value can't change either. Don't panic. Just because you can't change a variable's value doesn't limit Erlang at all. Not being able to change a variable's value actually makes many things much easier. 😁 Also, all Erlang variable names must begin with a Capital letter. Like this:

Age = 3.

🧑‍💻 Now Your Turn

Try typing the following example into your Erlang REPL. Remember, to open the REPL, open a terminal and type rebar3 shell.

Windows users: Remember to type wsl first to enter a WSL shell if you are not already in wsl.

    
  $ rebar3 shell
  ===> Verifying dependencies...
  Erlang/OTP 24 [erts-12.2.1] [source] [64-bit] [smp:16:16] [ds:16:16:10] 
  [async-threads:1] [jit]
  
  Eshell V12.2.1  (abort with ^G)
  1> Age = 3.
  3
  2>_

For the rest of the examples below, type them in your Erlang REPL. The more you try out these examples, the better you'll understand.

If you later forget that Erlang variables don't vary and try to reassign Age to have any other value due to your experience with other languages, it will fail to compile. For example, since Age is already set to 3 above, you can't do this:

Age = 4.

The compilation error message you will get is ** exception error: no match of right hand side value 4.

Try it by typing Age = 4. into your Erlang REPL.

This compilation error message will make more sense when you come to understand that the = operator in Erlang is a pattern-matching operator, not an assignment operator. This will make more sense when you learn about pattern matching in week 03.

Wrap Up

Which operator set to use in a language often depends on the languages that the language creators like and dislike. As with all things, the creators' choices have pros and cons. Both the creators of Python and Erlang preferred the syntax of Modula so they adopted its and, or, and not operators. Their preference is an opinion and, as such, it and the alternative operators used in C based languages, &&, ||, !, are just as valid. Don't get caught up in the pseudo-religious fervor that some programmers adopt. Every language is good at some things and bad at others. This includes both Python and Erlang.

Atoms

Erlang has a few types of things that Python does not. One of these is called an atom. Atoms in Erlang exist, but have no value. You saw one last week without knowing. It was the atom ok. The ok atom exists, but is not assigned a value. It is not a variable. You can tell when something is an atom as opposed to a variable. Variables always start with a capital letter. Atoms always start with a lowercase letter. There are a few atoms built in to the Erlang language. Enter the following code into the Erlang REPL.

Height = 4.5.

The result printed in the REPL is true. Notice the lowercase starting letter. Therefore, true is an atom. So is false. Neither true nor false have a value in Erlang. They just are. They are things in and of them selves. They are atoms. You can't do mathematics with them, since they are not numbers, nor are they strings.

Tuples

Like Python, the Erlang language embraces the concept of a tuple. The syntax is a little different when you declare one. In Erlang you do it like this.

Success = {ok,"Bob"}.

Success is now usable as the tuple {ok,"Bob"}.

Last week you saw a tuple, probably without recognizing it. When you complied the test module using c(test). you saw {ok,test} printed in the REPL. This tuple contained two atoms, ok and test. Whenever you execute something in the REPL, the result of that execution is printed in the REPL. When the compilation function c(module_name) is successful, it returns {ok,module_name}.

As with other languages that have tuples, tuples can contain any valid type, including other tuples. You are only limited by your imagination and, of course, readability.

Lists

Type each of the code examples below into the Erlang REPL and make sure you fully understand them.

Functional programming languages are list generation and manipulation languages. This is the result of these languages' strong link to mathematics. Don't let this background fool you. You can do everything in functional programming languages that you do in other types of languages. In Erlang, you can declare lists like this.


Customers = ["Bob","Sue","Jorge","Svetlana"].
	

This should look very familiar to you.

Unlike some other languages, there is no random access operator [] for Erlang lists. That's because you don't need them. Why don't you need them? Because you will never write any type of for loop in Erlang. There is no such thing. There is also no such thing as a while loop. Again, DON'T PANIC. In week 03 you will learn much more about Erlang functions. There you will discover that Erlang uses recursion instead of looping. It is then that you will learn how to access each element of a list.

Another way to declare lists in Erlang is list comprehensions. This concept should be familiar to you if you have Python experience, but the syntax is different due to Erlang's operators. Here is the code to produce all the combinations of the numbers 1, 2, and 3 as a list of tuples. Try it out in your REPL and look at the results.


Combinations = [{X, Y, Z} || X <- [1,2,3], Y <- [1,2,3], Z <- [1,2,3]].
  

Let's break this syntax down. The {X,Y,Z} on the left indicate that the resulting list will contain tuples where the first element of the tuple is one of the values of the first list, the second value is one of the values from the second list, and the third value of the tuple is one of the values from the third list. The || operator can be read as 'where' and the <- operator can be read as 'comes from.' Let me translate the Erlang to English for you. "Create a list of tuples each consisting of X, Y, and Z where X comes from the list 1,2,3, Y comes from the list 1,2,3, and Z comes from the list 1,2,3."

There is yet one more way to generate a list. To do so you use the lists:seq(From,To) function to generate a sequence of list elements. For example,


lists:seq(1,100).
  

generates the list of numbers 1 through 100. You can also use the lists:seq(From,To) function to generate a string that is a sequence of characters since strings are actually lists of numbers.


lists:seq($ ,$z+4).
  

The $ sign used in front of both the space character and the z is an operator stating that what is wanted is the numeric value of the following item, in this case a space or the z. This code produces all of the characters between space and ~. So does the code below. Try it out in the REPL.


lists:seq($ ,$~).
  

It is very common to use the lists:seq(From,To) function as part of a list comprehension. Here is an example.


Evens = [X || X <- lists:seq(1,100), X rem 2 == 0].
  

Let me translate the Erlang to English for you. "Create a list of X values where X comes from the list 1,2,3…100, and where the remainder of X divided by 2 is 0." Try it in the REPL.

List BIF's

Erlang has a lot of Built in Functions (BIF's) that you can use with lists. You can find them in the lists module. As an example, imagine you needed to use a list to keep track of customers lined up to see a movie. As people show up, they line up in the order they arrived. You need to know who is last in line so you can give them some sort of reward for waiting. In Erlang you do this using the last(List) function. Try each of these examples in the REPL.


Person = lists:last(Customers).
  

If Bob, Sue, Jorge, and Svetlana were already waiting in line for the show in that order, and then Grace showed up too, you would use append(List1,List2) to add Grace at the end of the list (the back of the line).


More = lists:append(Customers,[grace]).
	

Notice that Grace must be in a list. That's because the append(List1,List2) function is designed to append the contents of the second list to the contents of the first. That means that if William and Asha showed up together, they can be added with just one call to list:append(List1,List2).

There is another way to append the contents of one list to another list.


Mucho = Customers ++ [grace].
        

Notice that the result of Mucho = Customers ++ [grace] is Mucho. This means that both the list:append(List1,List2) function and the [List1 ++ List2] operators are non-destructive functions. They do NOT destroy the list Customers by modifying it. You may be tempted to think that More and Mucho are copies of Customers with grace added on. This is not the case in Erlang. Such a computational result would be very wasteful of memory. You may also think that return-by-reference and return-by-value are the only two options available to language designers due to limited exposure to computational languages. Those two are not the only options. The same is true of pass-by-reference and pass-by-value. There are other options. It is beyond the scope of this course to dive deeply into such things. You can learn how lists are passed and returned if you take CSE 382, Functional Patterns and Data Structures, or you can read the first part of Purely Functional Data Structures by Chris Okasaki.

If you don't want either of those options, just believe me. The creators of Erlang were not silly. Erlang doesn't create copies when lists are passed to functions nor when they are returned. The creators were nice to us so they made it look like that was what is happening, but it's not.

Efficient use of Lists

Appending to a list is great, but it is definitely not efficient. Because of the way lists are handled in functional languages, prepending to a list is VERY efficient. It is actually more computationally efficient to build a list using prepend and then sort it to reverse the order of the list than it is to append to the list. The efficiency difference between building a list by prepending and appending is so large, that Erlang has an operator, rather than a function, that you use to do prepending. Including an operator rather than a function strongly encourages the use of prepending since you don't have to type much to make prepending work. This is why appending elements to a list is very 'un-Erlangy.' In order to prepend to a list use the | operator, which is named the cons operator (search inside the page opened by this link for 'cons operator' if you want more detail). It is so common to prepend to a list in Erlang, that, unlike append, the cons operator has no matching function lists:cons(List1,List2). Don't look for it. It isn't there. Even though you could create one, DON'T. Doing so would be very un-Erlangy.

Now lets use the cons operator. Consider the situation where Aurelie needs to be prepended to the More list. The code to do so looks like this.


Persons = [aurelie|More].
  

That's it. That's all the code you have to write. So much going on for such a small cost! So, not only is prepending to a list more computationally efficient, it is also much more efficient when you write your code since you don't have to type so much. It is also very clear. It reflects the concept of cons directly. Another great thing about the cons operator is that it can be used to get both the head of the list and all the rest of the list, called the tail, with one use. For example, If we wanted to have two variables H and T where H was aurelie and T was [bob,sue,jorge,svetlana,grace], we can do that in one line of code.


[H|T] = Persons.
  

It is traditional, in this situation, to use the variable name H for the head and T for the tail. Please remember that Persons has not been changed. It still contains all of its elements. Also, H and T are NOT copies. They refer to an element and a sub-list within Persons. That's why this approach to lists and list handling is fundamental to all of Erlang and all functional programming languages. Prepend, don't append! Also, avoid inserting or removing elements in the middle of lists. There is almost always a much better way to accomplish what you intend when you do insertions.

Since prepending to build a list and then reversing it is so fundamental to Erlang applications, let's take a look at lists:reverse(List). Here is a Code snippet that reverses the Persons list.


lists:reverse(Persons).
  

Wrap Up

The cons operator and lists:reverse(List) are the operator and function you should use to build lists if you can't do it using lists:seq(From,To), list comprehensions, or defining the list. Also, you should use the cons operator to split the list into the first element, traditionally called H for head, and the rest of the list traditionally called T.

Erlang lists and their manipulation have been optimized heavily. They are fast and use very little memory. Use them heavily.

Optional Video Examples

This video is optional to provide additional examples and explanation of the reading material.