Not so long ago, I decided to change the way of thinking through learning a new programming language. From the very beginning of my career, I worked with Java, and now I began to feel the need for a completely different paradigm. So I met an incredible language called Elixir.
Rubists are well aware of the name of this language, as well as, perhaps, the name of the creator - Jose Walim. However, those who come from more verbose languages ​​have a low chance of being familiar with Elixir.
So I decided to write a few posts to help the javists quickly understand how the Elixir works. By comparing these two languages, it will be easier to comprehend a new world for you. Through these posts I will talk about the syntax, the work of the language and the key features that make the Elixir truly awesome!
For a moment, let's forget about objects, classes, and interfaces. Elixir is a compiled functional programming language based on the principle of immunity. It is enough to remember three simple rules to understand how everything is arranged in it.
Whatever language you learn, the beginning is always the same - “Hello world!”. Well, and we will not reinvent the wheel.
// Java public class HelloWorld { public static void main(String[] args){ System.out.println("Hello world!"); } }
# Elixir IO.puts "Hello world!"
The types of variables in the Elixir are determined dynamically during program execution. That is, there is no need to explicitly set the type of the variable when it is declared, but there are certain rules for each type:
// Java int i = 1; float f = 3.0f; boolean b = true; int[] array = {}; String s = "foo"; Map<String, Integer> map = new HashMap<>();
# Elixir i = 1 f = 3.0 b = true list = [1, 2] s = "foo" map = %{"one" => 1, "two" => 2} tuple = {1, 2} keyword_list = [one: 1, two: 2]
Now create a list and add a new item to it. This is how it will look like on Java:
// Java List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.size(); // 2
We recall the first rule - the immunity rule . To change the value of a variable, you need to re-bind it to another variable. In the example below, only the first three lines change the value of the elements of the list, then a copy is already created with the new values:
# Elixir list = [] # list = [1 | list] # 1 list = [2 | list] # 2 Enum.into(list, [3]) # 3 list ++ [3, 4] # , [2, 1, 3, 4] Enum.count(list) # 2
A small hint on the lists in the Elixir: they are stored in memory as linked lists . Since there is only a link to the head of the list, in order to add a new item to the list, you need to insert it into the beginning, replacing the head of the list. This ensures that the insert is done in O(1)
. If it is required to add elements to the end, then, as shown above, it is necessary to clutch the current list with the new element wrapped in the list. But with this more careful, the operation is not simple, because it requires a large amount of memory to copy the whole list and create a new one.
Another mystery is determining the length of the list. As you might have guessed, the only way to do this is to go through the entire list and count the number of elements (complexity O(n)
). Again, be careful.
Do not worry, just your mind is used to seeing everything in an imperative manner. In addition to this method of counting elements, there are others that will be discussed later. In the meantime ... let's listen to the slogan of a well-known company:
Think different!
Unlike the methods in Java, which can have one of the four access modifiers ( public
, default
, protected
or private
), the functions in the Elixir can be of only two types ( public
or private
). The definition of public and private functions begins with the words def
and defp
respectively. For example:
// Java public void addElement(List<Integer> list, Integer element){ list.add(element); } private int privateSum(int a, int b){ return a + b; }
# Elixir def say_hello(person) do "Hello #{person}" end def prepend_element(list, element) do [element | list] end def sum(a, b), do: a + b def sum a, b, c do a + b + c end defp sum(a, b, c, d) do a + b + c + d end
So, the function can be set in various ways, without the need to specify the keyword return
. And all because when accessing a function, the result of the last command inside it will always be returned.
Let's try again to add a new item to the list, using the above functions. Since in Java each object is a pointer, then when the object is passed to the method, the created copy of this pointer is used as an argument inside the method. This means that as long as the list values ​​are not reassigned to addElement
, any changes that occur in the list will be displayed outside the method. In Elixir, everything is completely different.
Remember the second rule? Functions idempotent! Everything that happens inside functions, with the exception of the returned result, remains there. This is one of the main features of the Elixir, which allows you to run the code in isolation and thereby achieve a completely new level of parallelism. And you don’t even have to worry about the side effects of your code.
# Elixir list = [1, 2] prepend_element(list, 3) prepend_element(list, 4) prepend_element(list, 5) IO.inspect(list) # [1, 2]
Not to mention the pipe operator |>
. With it, you can easily and simply combine functions into chains, passing the result of the previous function to the next as an argument. I will give the simplest example of its use:
// Java String fullName = "my full name"; String[] names = fullName.split(" "); String firstName = names[0]; firstName.toUpperCase();
# Elixir "my full name" |> String.split |> List.first |> String.upcase
To implement something in Java, you first need to create a class containing constructors, methods, and variables. In Elixir, the code consists of modules that combine functions into one. It looks like this:
// Java class Calculator { public int sum(int a, int b){ return a + b; } }
# Elixir defmodule Calculator do def sum(a, b), do: a + b end
You can draw a parallel between modules and functions and static methods within a class. There is no concept of an instance, so each module and each function is “static”. The differences in their use are illustrated in the following example:
// Java Calculator calculator = new Calculator(); calculator.sum(1, 2);
# Elixir Calculator.sum(1, 2) # Calculator.sum 1, 2
If you are interested in the language, then subscribe to the newsletter, starting from Monday there will be a week of all the most interesting - from comprehensive information support for beginners to vacancies and open source for experienced developers.
Thanks for attention!
Source: https://habr.com/ru/post/344658/
All Articles