erlang prozesse und elixir - babeș-bolyai universityrusu/cloud/v3.pdfelixir funktional...
TRANSCRIPT
Erlang Prozesse und ElixirFunktionale Programmierung
die Philosophie hinter dem Prozessmodell
● The world is concurrent● Things in the world don’t share data● Things communicate with messages● Things fail
Asynchrone Programmierung
● Computer begannen synchron● CPU schnell, Ein/Ausgabe langsam● Muli-Process● Multithreading
Asynchrone Programmierung
Asynchrone Programmierung
● Unabhängige Prozesse mit je eigenem Speicher● In einem Prozess laufen mehrere Threads parallel● Eigener Registersatz und Call-Stack● Viel sparsamer als unabhängige Prozesse● Synchronisation Bedarf wegen gemeinsam genutzter Ressourcen
Wie ist es implementiert?
● Thread = sequentielle Abläufe● parallele Ausführung in mehreren Threads● gemeinsame Daten müssen mit einem synchronized-Mechanismus
beschützt werden
● Typische Probleme:○ Inkonsistenzen○ Verlangsamung○ Deadlocks○ Starvation
Actor Model
Ein Aktor: ● Läuft parallel zu anderen Aktoren● Reagiert auf Nachrichten sequentiell● Kann Nachrichten versenden● Verpackt eigenen Zustand● Alle zusammen benötigen nur 1 Thread je CPU-Kern
● Der Absender wartet nicht auf den Empfang einer Nachricht● Es gibt keinen gemeinsamen Zustand aller Akteure. Wenn ein
Akteur Informationen über den internen Zustand eines anderen Akteur erhalten möchte, muss es diese Informationen mit Hilfe von Nachrichten anfordern.
Die Bausteine
● Erlang-Prozess mit Spawn () erzeugen● Eine Nachricht an einen Prozess mit ! senden● Eine Nachricht mit receive bearbeiten
was macht dieser Code?
-module(hello_server).
-export([hello/0]).
hello() ->
receive
{FromPID, Who} ->
case Who of
robert -> FromPID ! "Hello Robert.";
mike -> FromPID ! "Hello Mike.";
joe -> FromPID ! "Hello Joe.";
_ -> FromPID ! "I don't know you."
end,
hello()
end.
Prozesse in Erlang
● spawn(Module, Function, Arguments)● erzeugt einen neuen Prozess, der die aus Modul Module
exportierte Funktion Function mit den Elementen der Liste Arguments als Parametern ausführt
● Rückgabewert von spawn/3 ist der Prozess-Identifikator (pid) des neuen Prozesses
Prozesse in Erlang
● der Aufruf von spawn/3 schlägt nie fehl● dies gilt sogar, wenn eine nicht exportierte oder sogar eine
nicht existente Funktion als Argument verwendet wird● in diesem Fall endet allerdings der neu erzeugte Prozess
mit einem Laufzeitfehler● neue Prozesse arbeiten und existieren solange, bis sie terminiert
werden● ein Prozess terminiert normal, wenn kein Code mehr auszuführen
ist● andernfalls: ein Prozess terminiert abnormal, wenn Laufzeitfehler
auftreten
Prozesse in Erlang
● processes() liefert eine Liste mit den Pids aller aktuell laufenden Prozesse
● die Kommunikation zwischen Prozessen in Erlang erfolgt durch Nachrichten
● das Senden von Nachrichten erfolgt mit dem Konstrukt Pid ! Message
● dabei ist Pid ein gültiger Prozess-Identifikator und Message ist ein Wert eines beliebigen Datentyps aus Erlang
Prozesse in Erlang
● jeder Prozess in Erlang hat seine eigene Mailbox● die Mailbox wird mit flush() geleert● wenn eine Nachricht gesendet wird, wird sie vom sendenden
Prozess in die Mailbox des Empfängers kopiert● die Mailbox enthält eingegangene Nachrichten in der Reihenfolge
ihres Eintreffens● für von einem Prozess ausgegangene Nachrichten gilt, dass sie in
der Reihenfolge des Sendens in der Mailbox enthalten sind● diese Garantie gilt nicht für Nachrichten vonunterschiedlichen
Prozessen
Prozesse in Erlang
● das Senden einer Nachricht schlägt nie fehl● dies gilt auch, wenn die Nachricht zu einem invalid Prozess
gesendet wird● in dem Fall wird die Nachricht ignoriert, ohne einen Fehler zu
erzeugen● Versenden von Nachrichten erfolgt asynchron in den Sinne, dass
der sendende Prozess nicht unterbrochen wird, sondern unmittelbar mit dem nächsten Ausdruck in seinem Code fortfährt
Prozesse in Erlang
● der Wert der Pid ! Message ist die gesendete Nachricht● soll dieselbe Nachricht zu verschiedenen Prozessen gesendet
werden, so kann dies geschehen● Pid1!Msg,Pid2!Msg,Pid3!Msg● Pid3!Pid2!Pid1!Msg
Prozesse in Erlang
● receive ist das Konstrukt zum Empfangen von Nachrichten● eine receive wird durch die Schlüsselwörter receive und end begrenzt und enthält
eine Anzahl von Klauseln● beim Ausführen eines receive wird zunächst die erste (und damit älteste) Nachricht
in der Mailbox mit den Mustern in den Köpfen des receive verglichen
receive
Pattern1 when Guard1 -> exp11, .., exp1n1;
Pattern2 when Guard2 -> exp21, .., exp2n2;
...
Other -> expk1, .., expknk
end
Prozesse in Erlang
● bei erfolgreichem Match wird○ die Nachricht aus der Mailbox entfernt○ die Variablen im Muster werden an die entsprechenden Teile des Musters gebunden ○ der Körper der Klausel wird (mit diesen Bindungen) ausgeführt
● wenn keine der Klauseln auf erste Nachricht passt, werden nacheinander die nachfolgenden Nachrichten überprüft, bis
○ entweder eine Nachricht passt auf eine Klausel ○ oder alle Nachrichten haben bei allen möglichen Matches fehlgeschlagen
Prozesse in Erlang
● Rückgabewert eines receive ist der letzte evaluierte Ausdruck im Körper der ausgewählten Klausel
● die Nachricht {reset, 151} sei gesendet an einen Prozess mit receive
{reset, Board} -> reset(Board),
_Other -> {error, unknown_msg}
end
● ?
Prozesse in Erlang
-module(counter).
-export([start/0,loop/1]).
start() ->
spawn(counter, loop, [0]).
loop(Val) ->
receive
increment -> loop(Val + 1)
end
1> c("counter0.erl"). {ok,counter} 2> C0 = counter0:start(). 3> C0!increment. increment 4> C0!increment. increment
Elixir
● Funktional Programmierungssprache für Erlang VM○ Low-latency○ Distributed○ Fault-tolerant
● Ruby-like syntax● Besser Orga des Codes● Clojure-like philosophy● Werkzeuge
○ iex○ mix
Elixir
● Polymorphism via protocols● Concurrent programming via message passing● Meta programming● Erlang Interoperabilität● Pattern matching● Lazy and async collections
mix
● Neues Projekt anlegen● Compile ● Tests ausführung● Dependencies Management● & viel mehr
Standard Library
● Kernel● IO● Enum● String
Elixir's "Specials"
● Pipe Operator● Structs● Protocols● Macros
Die Bausteine
● Data Types● Pattern Matching● Modules&Functions● Processes
Data Types
● Integer/Float○ 1, 1.3, 1..5
● String, Binary & Char list● Atom
○ maximum number of atoms in the Erlang VM is 1048576○ :my_atom
● Boolean● Tuple (Immutable)● List (Immutable)● Map (Immutable)● Anonymous Function
Strings
● string= “abc”● IO.puts(string)● String.length(string) #3● s=”#{string}!” #abc!● s = string <> “ ” <> s #abc abc!● strings == s● reverse, at, capitalize, slice, split
Regular Expressions
~r/foo/
~r/foo/iu
> Regex.match?(~r/foo/, "foo")
true
> Regex.match?(~r/foo/, "bar")
false
Tuples
● {:error, 404}● typle = {:this, :is, :not, :an}● Tuple.append(tuple, :array)
{:this, :is, :not, :an, :array}
● insert_at● Schneller Zugriff auf Elemente● Langsames Einfügen, Update● speichert als ein kontinuierlicher Memory-Block
Linked Lists
● list = [1, 2, 3, 4]● hd(list) #1● tl(list) #[2, 3, 4]● length([1, 2, :true, "str"]) #4● [1, 2, 3] ++ [4, 5, 6] #[1, 2, 3, 4, 5, 6]● [0 | list] #[0, 1, 2, 3, 4]● [1, true, 2, false, 3, true] -- [true, false]
#[1, 2, 3, true]
● flatten, insert_at, delete_at● Schnelles voranstellen● Langsames Einfügen
Maps
● %{key1 => value1, key2=> value2}● stud = %{:name => "Bob", :age => 20}● stud[:name] #”Bob”● stud.name
○ nur wenn die Schlüssel atoms sind● %{stud | age: 25} #{name:Bob, age:25}● ns= Dict.put_new(student, :city, "Cluj")
Funktionen
defmodule Hello do
def say_hello do
IO.puts "Hello World"
end
end
iex> Hello.say_hello
Hello World
Funktionen
iex> add = fn (a, b) -> a + b end
iex> add.(1, 2)
3
iex> sum = &(&1 + &2)
iex> sum.(1, 2)
3
Funktionen
defmodule Math do
def sum(a, b), do: a + b
end
IO.puts(Math.sum(1, 2))
defmodule Greeter do
def hello(name), do: phrase <> name
defp phrase, do: "Hello "
end
Greeter.hello("world")
Funktionen
defmodule Greeter do
def hello(%{name: person_name}) do
IO.puts "Hello, " <> person_name
end
end
bob = %{name:"Bob", age:21}
Greeter.hello(bob) #"Hello, Bob"
Funktionen
%{name: person_name} = %{name: "Bob", age: "20"}
defmodule Greeter do
def hello(%{name: person_name} = person) do
IO.puts "Hello, " <> person_name
IO.inspect person
end
end
person = %{name: "Bob", age: "20"}
Guards
defmodule Greeter do
def hello(names) when is_list(names) do
names
|> Enum.join(", ")
|> hello
end
end
iex> Greeter.hello ["Bob", "Dob"]
"Hello, Bob, Dob"
Enumerable
● Enum Module● Enumerable: list, tuple, map● Enum.all?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)● Enum.any?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)● Enum.chunk([1, 2, 3, 4, 5, 6], 2)● Enum.each(["Hello", "Every", "one"], fn(s) ->
IO.puts(s) end)● Enum.map([2, 5, 3, 6], fn(a) -> a*2 end)● Enum.reduce([1, 2, 3, 4], 5, fn(x, acc) -> x + acc end)● Enum.uniq([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
Pipe Operator
|> übernimmt die Ausgabe auf der linken Seite und übergibt es als erstes Argument an den Funktionsaufruf auf der rechten Seite
odd? = &(rem(&1, 2) != 0)
res = 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
Structs
● Erweiterungen von Maps○ compile-time checks○ default Werte
defmodule Student do
defstruct name: "Bob", age: 20
end
bob= %Student {}
dob= %Student {name: "bob", age: 19}
IO.puts(bob.name)
batman= %{bob | name: "batman"}
Pattern Matching
● i = 1● 1 = i #ok● 2 = i #error● [1, b, 4] = [1, 5, 4] #[1,5,4]
○ b #5
● [1, c, _] = [1, 4, 123]○ c #4
● [head | tail] = [1, 2, 3] #[1,2,3]○ head #1○ tail #[2,3]
If, case, cond
a = true
if a === true do
IO.puts "Variable a is true!"
else
IO.puts "Variable a is false!"
end
case {:ok, "Hello World"} do
{:ok, result} -> result
{:error} -> "Uh oh!"
_ -> "Catch all"
end
If, case, cond
x = 2
cond do
x == 2 > IO.puts "x is 2"
x+7 == 10 -> IO.puts "x is 3"
true -> IO.puts "Catch all"
end
Prozesse in Elixir
● spawn● send & receive● Link & Monitor● Task
Counter - Beispiel
def module Counter do
def loop (count) do
receive do
{:next} −>
IO.puts ("Current count: #{count}")
loop (count+1)
end
end
Counter - Beispiel
Counter - Beispiel
$iex Counter.ex
counter1 = spawn (Counter, : loop, [1])
counter2 = spawn (Counter, : loop , [100])
send (counter1 , {:next})
send (counter1 , {:next})
send (counter2 , {:next})
send (counter2 , {:next})
send (counter1 , {:next})
send (counter2 , {:next})
send (counter1 , {:next})
Counter - Beispiel
Current count: 1
Current count: 100
Current count: 101
Current count: 2
Current count: 102
Current count: 3
Current count: 4
Counter - Beispiel
def module Counter do
def firstCounter ( ) do
pid = spawn_link (__MODULE__ , :list1 , [1])
Process.register (pid , :first)
pid
end
def secondCounter ( ) do
pid = spawn_link (__MODULE__ , :list2 , [1000])
Process.register (pid , :second)
pid
end
def list1 (currVal) do
receive do
{:go} −>
IO.puts currVal
:timer.sleep (1000)
send (:second, {:go})
end
list1 (currVal + 1)
end
def list2 (currVal) do
receive do
{:go} −>
IO.puts currVal
:timer.sleep (100)
send (:frist, {:go})
end
list2 (currVal + 1)
end
TO DOshttps://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html
install Elixir
https://elixir-lang.org/install.html#windows
install cowboy
implementiere die Erlang Hausaufgaben in Elixir
NO FRAMEWORKS!