5. Overview
Quick Introduction
Motivation
Language Overview
Pattern
Matching
Guards
Pipe Operator
Extras
OTP and Concurrency
Actor Example
Concurrency Modules
Supervision
Nodes
Summary
Questions
● Strong, Dynamically Typed
● Functional
● Extensible with macros
● Massively Parallel and
Fault Tolerant
● Hot swappable code
Elixir language overview
6. Overview
Quick Introduction
Motivation
Language Overview
Pattern
Matching
Guards
Pipe Operator
Extras
OTP and Concurrency
Actor Example
Concurrency Modules
Supervision
Nodes
Summary
Questions
iex(1)> a = 1
1
iex(2)> 1 = a
1
iex(3)> 2 = a
** (MatchError) no match of right hand side value: 1
iex(3)> [a,b,c] = [1,2,3]
[1, 2, 3]
iex(4)> c
3
iex(5)> [a,b,a] = [1,2,3]
** (MatchError) no match of right hand side value: [1,
2, 3]
iex(5)> [a,b,a] = [1,2,1]
[1, 2, 1]
Pattern Matching
7. Overview
Quick Introduction
Motivation
Language Overview
Pattern
Matching
Guards
Pipe Operator
Extras
OTP and Concurrency
Actor Example
Concurrency Modules
Supervision
Nodes
Summary
Questions
iex(6)> foo = fn([a,b,c]) -> IO.inspect(c) end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(7)> foo.([1,2,5])
5
iex(8)> foo.([1,2,5,6])
** (FunctionClauseError) no function clause matching in
:erl_eval."-inside-an-interpreted-fun-"/1
iex(8)> bar = fn([head|tail]) -> IO.inspect(head) end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(9)> bar.([47,23,124,49])
47
Pattern Matching (cont’d)
8. Overview
Quick Introduction
Motivation
Language Overview
Pattern
Matching
Guards
Pipe Operator
Extras
OTP and Concurrency
Actor Example
Concurrency Modules
Supervision
Nodes
Summary
Questions
defmodule Foo do
def handle_open({:ok, file}) do
IO.write(“file opened successfully”)
end
def handle_open({:error, _}) do # ignore posix
code
IO.write(“file could not be opened”)
end
end
Foo.handle_open(File.open(“test.txt”))
Pattern Matching (cont’d)
9. Overview
Quick Introduction
Motivation
Language Overview
Pattern
Matching
Guards
Pipe Operator
Extras
OTP and Concurrency
Actor Example
Concurrency Modules
Supervision
Nodes
Summary
Questions
defmodule Bar do
def safe_round(value) when is_integer(value)
value
end
def safe_round(value) when is_float(value) do
round(value)
end
end
Bar.safe_round(2.3)
Bar.safe_round(2)
Guards
13. Overview
Quick Introduction
Motivation
Language Overview
Pattern
Matching
Guards
Pipe Operator
Extras
OTP and Concurrency
Actor Example
Concurrency Modules
Supervision
Nodes
Summary
Questions
defmodule Greeting do
def greet do
receive msg do
msg -> IO.puts(“Hello #{msg}!”)
end
end
end
pid = spawn(Greeting, :greet, [])
send(pid, “world”)
Hello world!
Simple Actor Example
This tech talk will be focused on a “new” language (6 years, 3 years since the first major release) built upon a very old platform (30+ years)
Why...
In this presentation I will quickly start with why I would even bother with presenting this topic to data scientists
Will give a brief talk about what it is all about
Elixir Overview, the language syntax, a very brief overview and highlights of the syntax
OTP Overview
A very *brief* overview. In terms of scale it would something similar to giving a presentation on Java Language and Platform.
So Elixir is language built on top of a very old language called Erlang. Erlang’s syntax is derived from Prolog and is a lot of programmers are turned off from the syntax.
So like Java, Elixir and Erlang and both languages that compile down and are interpreted by a virtual machine.
The virtual machine, platform, and Erlang itself has been around for around 33 years or so. While Elixir has only been released for about 6 years or so which is very young by language standards. It came about from a Rails (ruby) core member was fed up with trying to make rails concurrent.
Oddly enough the latest point release came out yesterday with a bunch of improvements to performance and the library.
Focusing on Elixir as a language in this presentation since it’s the only language in use that uses OTP that is somewhat prevalent aside from Erlang. People tend prefer the syntax to Elixir than Erlang, even the one of the creators of the Erlang language (Joe Armstrong) admitted to so.
BEAM = Bogdan/Bjorn’s Erlang Abstract Machine
So first motivation is *not* to have you re-write your programs in Elixir or Erlang immediately after this presentation despite how convincingly awesome I present them.
Instead, in general this is supposed to expose the lab as a whole and you individually on different programming paradigms and make you a better programmer, write better programs and introduce new concepts when it comes to job control and supervision.
Everyone here writes in an object-oriented language. The man who coined the term (ask?) Alan Kay, had this to say about the term:
“I was too blythe(sp) about the term back in the 60s and should have chosen something like "message oriented"”
“Significant parts of Erlang are more like a real OOP language the the current Smalltalk, and certainly the C based languages that have been painted with “OOP paint””.
In terms of “Most Loved” Elixir trails only .3% respondents behind Python from 2017. Oddly enough Smalltalk ranked 2nd.
Strongly typed as in there will be errors if you try to do something with a particular type there is no operation for. Dynamic as-in bindings can be anything to start with, you don’t need to specify a type beforehand.
Almost without exception, all types and data are immutable. There are no references to anything.
Whatsapp (went from 1 to 2 million connections on a single machine from 2011 to 2012, 35 engineers 450 million users, and 50 engineers to 900 million, 24 billion messages in a single day)
Facebook messenger
Discord
Massively Parallel is not an exaggeration, hundreds of thousands of processes on a single machine
Demonstration more for parallelism than speed.
iex(1)> 1..100000 |>
...(1)> Enum.map(fn(x) -> Task.async(fn -> x*x end) end) |>
...(1)> Enum.map(fn(x) -> Task.await(x) end)
This clearly is more bottlenecked by the creation and teardown of a process rather than the squaring of a number.
Very similar syntax in Python 3 - but it’s not technically possibly due to the Global Interpreter Lock. In general it works in Python if you’re blocked on a system call.
At the heart of Elixir is writing functions with Pattern matching.
There is no “assignment” operator in Elixir. Instead the equals operator is more like an assertion\ checking that the left hand side can match the right side.
This is not as crazy as it may seem and you can get some useful properties.
Here we can write a function that expects a list with 3 elements and print only the 3rd element through pattern matching.
The function will fail if it cannot match a 3 element list.
Also there’s syntax for pattern matching a head of a list with the rest of the list (called a tail). In this case we simply print the first element of a list.
Here we are defining a module which is necessary for creating named functions. This is to ensure that all named functions always live in some namespace to avoid clashing. For example, the handle_open name for our function here may be defined in another module without a naming clash. They are also used often for messaging passing between processes.
I understand this a whirlwind tour as well but the “colon” names are called atoms. They are simply unique identifiers to use however you wish. Often in Elixir programs they are used for return status codes instead of say an integer from C or whatever you choose for Python (true, false, etc).
Here the File.open call returns a tuple with either an :ok file handle tuple or an :error, posix error code tuple
There are other builtin types such as maps, and you can define your own types “structures” which are syntatic sugar for maps as well. These can obviously all be pattern matched on as well.
Notably, in general although conditionals are available for Elixir, they are often not used in lieu of just more pattern matching. Notably in a switch case conditional you can pattern match on the case statement.
Here we are defining a module which is necessary for creating named functions. This is to ensure that all named functions always live in some namespace to avoid clashing. For example, the handle_open name for our function here may be defined in another module without a naming clash. They are also used often for messaging passing between processes.
I understand this a whirlwind tour as well but the “colon” names are called atoms. They are simply unique identifiers to use however you wish. Often in Elixir programs they are used for return status codes instead of say an integer from C or whatever you choose for Python (true, false, etc).
Here the File.open call returns a tuple with either an :ok file handle tuple or an :error, posix error code tuple
There are other builtin types such as maps, and you can define your own types “structures” which are syntatic sugar for maps as well. These can obviously all be pattern matched on as well.
Notably, in general although conditionals are available for Elixir, they are often not used in lieu of just more pattern matching. Notably in a switch case conditional you can pattern match on the case statement.
F#, Swift, elm,
There ways to define your own operators. The Elixir library is filled with them, including the if statement.
A huge portion of Elixir is manipulating collections of data
There are a bunch of built in types that are useful that I’ll also be glossing over
For modules there are also language supported contracts for what a module supports. Sort of like the interface keyword from Java or abstract classes from C++ where you promise to implement certain functions etc.
This is closer to what Alan Kay had in mind apparently when it came to OOP
It’s a very simple model, no one can modify each other’s state.
Actor can update their own state if they want.
Spawn send and receieve are the smallest primitives available to send messages amongst processes
The greet blocks until it recieves a message that was sent to it.
Notably you can also get your own process id with self(), so you can send your own pid to a server if you want to program a response.
Sending again doesn’t do anything. The spawned process terminates after the function is complete, in this case after the receive block is evaluated
In reality no one really uses send, receive and spawn. They are there in case you want to create any of your own concurrency models
More commonly:
Tasks are for firing off asynchronous computation. You async a computation, do whatever else and then when you need the result later on you await it.
Agents are simple servers that you can use if you need to maintain some form of state or cache. You give it a function that sets up an initial value for whatever state you want it to hold. Then you call functions referencing the returned process id to update the given state, etc.
GenServer is a behaviour you implement for your module. You implement certain functions depending on how you want to handle asynchronous messages, synchronous messages,
How does things hold state? Tail call for infinite loops to hold state.
Any module that implements the previous modules can be supervised
Automatic restart if a child process exits abnormally
Children specify how they should be setup and run, restart strategies, what to do in abnormal exit situations etc.
Strategies:
One for one (only restart the one child), one for all (restart all children)
Connections are usually done through a local network
Programs in Elixir typically grow and scale as you need it
Generally functions are very small when it comes with guard clauses and pattern matching
This typically makes modules and libraries fairly small as well.
As a consequence it’s not uncommon to be OK with rewriting a process/actor/object. When new features come in that don’t quite fit your current model there’s a far more realistic consideration to delete and/or rewrite that section to have the feature fit the model better instead of working around it.