📜 ⬆️ ⬇️

Elixir

Hello, today I will tell you about the modern programming language under BeamVM (or ErlangVM).
The first part is an incomplete introduction to the basics, and the second part of the article shows with simple examples the main features of the language, which are new for the erlang developer.

Two years ago, the 0.1 version of elixir was released, which was presented to the habrasoobschestvuyu earlier.

Quote:
')
“Erlang is a platform unique in its capabilities, and in spite of this, the language is still exotic. There are several reasons. For example, tight arithmetic, unusual syntax, functionality. These are not flaws. These are just things that most programmers don’t or don’t want to work with. ”

At the moment, elixir has become the most popular programming language (of course, besides erlang-a), built on top of BeamVM. To the point that the author of erlang Joe Armstrong devoted an article, and Dave Thomas wrote a book. In two years, much has changed, the language has stabilized greatly and has found a more or less final version for version 1.0. During this time, the object model disappeared from elixir, Ruby-like syntax remained, but metaprogramming and polymorphism were added, which organically, unlike the object-oriented paradigm, fit into the Beam VM.

New in Elixir:


At the same time, it is compiled as erlang beam; elixir also allows you to call erlang modules without the need to convert data types, so there is no performance loss when calling erlang code.

To try it out for yourself, you can download it from the githab:

$ git clone https://github.com/elixir-lang/elixir.git $ cd elixir $ make test 


Or install the precompiled version .

You can also install elixir for Fedora, Mac OS or Arch Linux owners via the package manager:


There is an interactive iex console in elixir, where you can immediately try everything. Unlike erlang, modules can be created in the elixir console, as will be shown below.

Comment:
 # This is a commented line 


Next, “# =>” shows the value of the expression:
 1 + 1 # => 2 


Console example:

 $ bin/iex defmodule Hello do def world do IO.puts "Hello World" end end Hello.world 

The data types in elixir are the same as in erlang:
 1 # integer 0x1F # integer 1.0 # float :atom # atom / symbol {1,2,3} # tuple [1,2,3] # list <<1,2,3>> # binary 


Strings in elixir, as in erlang-e, can be represented through lists or through binary:

 'I am a list' "I am a binary or a string" name = "World" "Hello, #{name}" # => string interpolation 

Unlike erlang, elixir uses binary everywhere, as the standard implementation of strings because of their speed and compactness in front of lists of letters.

And there are also multi-line strings:
 """ This is a binary spawning several lines. """ 


The function call, we have already seen above for the module, but it is possible and so, omitting the brackets:
 div(10, 2) div 10, 2 

A good programming style for elixir recommends that if you omit parentheses, then when using macro.
Coding Style in the standard library says that brackets should be used to call functions.

The variables in elixir are still immutable, but you can do reassigment:
 x = 1 x = 2 


You can change variables only between expressions, and within the same expression it will still match. At the same time, all pattern matching from erlang has been preserved and it is possible with the help of ^ to make them unchanged as in erlang:
 {x, x} = {1,2} # => ** (MatchError) no match of right hand side value: {1,2} {a, b, [c | _]} = {1,2,["a", "b", "c"]} # => a = 1 b = 2 c = "a" a = 1 # => 1 a = 2 # => 2 ^a = 3 # => ** (MatchError) no match of right hand side value: 3 


You can learn more about the syntax, features and features of elixir here:
Official tutorial
Crash Course for erlang developers
Week with elixir. Joe Armstrong article on elixir
The book Programming Elixir by Dave Thomas, there are two video tutorials and some excerpts from the book
Official documentation

After I started programming on elixir myself, look at the erlang code, which is often created through copy-paste with a change in one value (and there is such a need in almost every project that I have encountered) or constant repetitions of a certain pattern, which increase the code, I just want to rewrite them correctly on elixir.

And now I would like to show on simple examples of innovations for the erlang developer, namely, metaprogramming, polymorphism, and also syntactic sugar, which greatly simplify the code.

Let's start with metaprogramming. In elixir, everything is an expression, at least as far as possible (“Everything is an expression”).
First example, we take the most common module with a single function, like our experiment.
 defmodule Hello do def world do IO.puts "Hello World" end end 


Let's save it to the file and compile it like this:

 $ elixirc hello.ex 

Or we copy in the console, and our module is compiled there. In any case, carefully look at what happens at the time of compilation. At the moment, nothing special.

Let's change our example a bit:
 defmodule Hello do IO.puts "Hello Compiler" def world do IO.puts "Hello World" end end 


Now, at compile time, we can see “Hello compiler”.

Now we will try to change something in our module, depending on the compilation:
 defmodule Hello do if System.get_env("MY_ENV") == "1" do def world do IO.puts "Hello World with my Variable = 1" end else def world do IO.puts "Hello World" end end end 

Now, if we compile the code, then depending on how we compile it, we can see:
 $ elixirc hello.ex $ iex iex> Hello.world # => "Hello World" 


Or, if we compile our module like this, we will get another action of our function:
 $ MY_ENV=1 elixirc hello.ex $ iex iex> Hello.world # => "Hello World with my Variable = 1" 


And now, let's try to do something more interesting, for example, generate code.
In erlang code you can often come across such or similar code:
 my_function(bad_type) -> 1; my_function(bad_stat) -> 2; ....... my_function(1) -> bad_type; my_function(2) -> bad_stat; ..... 


For example, we want to get a function that we will use as follows:
 Hello.mapper(:bad_type) # => 1 Hello.mapper(:bad_stat) # => 2 Hello.mapper(1) # => :bad_type ..... 


In elixir, we can get the same speed of the function, without repeating, if we generate the same functions at compile time:
 list = [{:bad_type, 1}, {:bad_stat, 2}] lc {type, num} inlist list do def my_function(unquote(type)), do: unquote(num) def my_function(unquote(num)), do: unquote(type) end) 


lc inlist do is a list compression in the elixir language, an example of use:
 lc a inlist [1,2,3], do: a * 2 # => [2,4,6] 


Now with the help of list compression, we have generated two functions (or, more precisely, a match for a function).

An example is taken from the real code:
one way and the other way
And in this and the other direction, on elixir

In elixir itself, you can also see, for example, here:
github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/string.ex#L478-L486

Macro in elixir-e act like in clojure (lisp programmers will feel at home), you can see its AST in any code:
 quote do: 1+1 # => {:+,[context: Elixir, import: Kernel],[1,1]} quote do: {1,2,3,4} # => {:"{}",[],[1,2,3,4]} quote do: sum(1,2) # => {:sum,[],[1,2]} quote do: sum(1, 2 + 3, 4) # => {:sum,[],[1,{:+,[context: Elixir, import: Kernel],[2,3]},4]} 

As you can see from the examples, AST consists of tuples with three elements: {name, meta, arguments}
Now, let's try to write our first macro:
 defmodule MyMacro do defmacro unless(clause, options) do quote do: if(!unquote(clause), unquote(options)) end end 


Now use our macro:
 require MyMacro MyMacro.unless 2 < 1, do: 1 + 2 


The following example will show how you can use this knowledge, for example for optimization.
If we use somewhere regular expressions, it looks like this:
 defmodule TestRegex do def myregex_test do :re.run("abc", "([az]+)", [{:capture, :all_but_first, :list}]) end end 

And now, using our knowledge above, we can take out the compilation of a regular expression, thereby making our runtime code faster:
 defmodule TestRegexOptimized do {:ok, regex} = :re.compile("([az]+)") escaped_regex = Macro.escape(regex) def myregex_test do :re.run("abc", unquote(escaped_regex), [{:capture, :all_but_first, :list}]) end end 

In this example, we have compiled a regular expression outside the function. Using Macro.escape (there are many other useful functions in the Macro module) we inserted into our function the already compiled regular expression, having the readable version still in the code. Actually, in the elixir with regular expressions, you do not need to do this, since the% r macro does this for you, depending on if you can compile the regular expression right away.

Thus, we can compare the speed of our function:
 Enum.map(1..1000, fn(_) -> elem(:timer.tc(TestRegex, :myregex_test, []), 0) end) |> List.foldl(0, &1 + &2) # => 4613 Enum.map(1..1000, fn(_) -> elem(:timer.tc(TestRegexOptimized, :myregex_test, []), 0) end) |> List.foldl(0, &1 + &2) # => 3199 


Polymorphism:
 list = [{:a, 1}, {:b, 2}, {:c, 3}, {:d, 4}, {:e, 5}, {:f, 6}, {:g, 7}, {:h, 8}, {:k, 9}] Enum.map(list, fn({a, x}) -> {a, x * 2} end) dict = HashDict.New(list) Enum.map(dict, fn({a, x}) -> {a, x * 2} end) file = File.iterator!("README.md") lines = Enum.map(file, fn(line) -> Regex.replace(%r/"/, line, "'") end) File.write("README.md", lines) 

The example shows that we can use the Enum library on any data type that implements the Enumerable protocol.

The implementation for the protocol can be located anywhere, regardless of the protocol itself: the main thing is that the compiled code is located where BeamVM can find it (that is, in: source.get_path). Those. for example, you can extend existing libraries without changing their code for your data types.

Another interesting built-in protocol is the access protocol — take the symbol-value list as an example of the top list:
 list = [{:a, 1}, {:b, 2}, {:c, 3}, {:d, 4}, {:e, 5}, {:f, 6}, {:g, 7}, {:h, 8}, {:k, 9}] list[:a] # => 1 


We will make a very simple example with a binary tree that will be in the record (record) Tree and for our tree we will also implement the Access protocol.
 defmodule TreeM do def has_value(nil, val), do: nil def has_value({{key, val}, _, _}, key), do: val def has_value({_, left, right}, key) do has_value(left, key) || has_value(right, key) end end defrecord Tree, first_node: nil defimpl Access, for: Tree do def access(tree, key), do: TreeM.has_value(tree.first_node, key) end 


Now, in the same way, we can find our values ​​through Access Protocol
 tree = Tree.new(first_node: {{:a, 1}, {{:b, 2}, nil, nil}, {{:c, 3}, nil, nil}}) tree[:a] # => 1 

Protocols give polymorphism.

And now, a little bit of syntax sugar, which makes writing and reading code easier in certain situations.
[{: a, 1}] can be written like this: [a: 1]

In the same way, it is often necessary to write such constructions as:
func3 (func2 (func1 (list))), despite the fact that the function call func1 occurs first, we write func3 first or we must enter variables, as in this case:
 file = File.iterator!("README.md") lines = Enum.map(file, fn(line) -> Regex.replace(%r/"/, line, "'") end) File.write("README.md", lines) 


With the help of the pipeline operator (|>) we can rewrite our example like this:
 lines = File.iterator!("README.md") |> Enum.map(fn(line) -> Regex.replace(%r/"/, line, "'") end) File.write("README.md", lines) 

In the elixir library, the standardized subject goes with the first argument. And this makes it possible with the help of the |> operator, which substitutes the result of the previous action as the first argument of the function in the next call, to write more understandable, compact and consistent code.

Also, we can simplify this example using curry or partials in simple cases:
 lines = File.iterator!("README.md") |> Enum.map( Regex.replace(%r/"/, &1, "'") ) File.write("README.md", lines) 


I think Elixir will be interesting to erlang-developers who want to improve the quality of their code, productivity, and try out metaprogramming in action. Similarly, developers from other languages ​​and platforms will also show interest in it. For example, those who would like to try out BeamVM, but did not dare because of the erlang syntax or welter in its libraries. Here an important advantage of elixir is the standardized and compact standard library (Standard Library).

Source: https://habr.com/ru/post/184222/


All Articles