C/CPS 506
Comparative Programming Languages Prof. Alex Ufkes
Topic 5: Finishing up Elixir
Notice!
Obligatory copyright notice in the age of digital delivery and online classrooms:
The copyright to this original work is held by Alex Ufkes. Students registered in course CCPS 506 can use this material for the purposes of this course but no other use is permitted, and there can be no sale or transfer or use of the work for any other purpose without explicit permission of Alex Ufkes.
© Alex Ufkes, 2020, 2021 2
Course Administration
© Alex Ufkes, 2020, 2021
3
Midterm next week!
• •
Takes place on D2L during the 2h lecture period. Test is ONLY available during these two hours.
This Week
© Alex Ufkes, 2020, 2021
4
Finish up Elixir
• Pattern Matching
• Control flow, keyword lists
• Enum VS Stream
• List comprehensions
• Elixir processes
© Alex Ufkes, 2020, 2021
5
Let’s Get Started!
Any Questions?
© Alex Ufkes, 2020, 2021
6
Pattern Matching
© Alex Ufkes, 2020, 2021 7
© Alex Ufkes, 2020, 2021
8
=
This is not the assignment operator. It is the match operator. Pattern matching is a fundamental part of Elixir
x= 1
When a name is on the left-hand side of the match operator, we bind or rebind the name.
© Alex Ufkes, 2020, 2021
9
iex> x = 2 2
iex> 2 = x 2
This is a valid expression!
(Variable) Name on the Right?
iex> 3 = x
** (MatchError) no match of right hand side value: 2
iex> 3 = x + 1 3
If a match is successful, it returns the value of the right-hand side of the expression. If not, a MatchError.
(Variable) Name on the Right?
iex> x = 2 2
iex> 2 = x 2
This is a valid expression!
iex> 3 = x
** (MatchError) no match of right hand side value: 2
iex> 3 = x + 1 3
Names on the left? Bind or rebind to value on the right. Names on the right? Pattern match with value on the left.
© Alex Ufkes, 2020, 2021 10
Matching Lists Let’s see matching with lists:
iex> list = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> [1, 2, 3, 4, 5] = list [1, 2, 3, 4, 5]
© Alex Ufkes, 2020, 2021
11
Matching Lists
iex> list = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> [1 | tail] = list
[1, 2, 3, 4, 5]
iex> tail
[2, 3, 4, 5]
iex> [2 | tail] = list
** (MatchError) no match of right hand side
value: [1, 2, 3, 4, 5]
A pattern match will error if the sides can’t be matched
• Separates list into head and tail.
• In this case, the head must be 1!
© Alex Ufkes, 2020, 2021 12
• Not equating the tail (of tail) with anything
• ‘_’ can never be read from. Value discarded.
iex> tail
[2, 3, 4, 5]
iex> [2 | _] = tail [2, 3, 4, 5]
iex> [2, 3 | test ] = tail
[2, 3, 4, 5]
iex> test
[4, 5]
iex> [_ | test ] = tail
[2, 3, 4, 5]
Match first two values
Match test with tail of tail
© Alex Ufkes, 2020, 2021 13
iex> tup = {:OK, “Hello”} {:OK, “Hello”}
iex> {:OK, value} = tup
{:OK, “Hello”}
iex> value
“Hello”
Matching Tuples
• When matching tuples, the comma is used as a separator.
• Tuples don’t deal in head/tail
• They aren’t linked lists.
• Comma for tuples, | for lists.
© Alex Ufkes, 2020, 2021 14
iex> {a | b} = {1, 2, 3, 4, 5}
** (CompileError) iex: misplaced operator |/2
The | operator is typically used between brackets as the cons operator: [head | tail]
where head is a single element and the tail is the remaining of a list. It is also used to update maps and structs, via the %{map | key: value} notation, and in typespecs, such as @type and @spec, to express the union of two types
© Alex Ufkes, 2020, 2021 15
Matching Tuples
iex> {a, b, c} = {:hello, “World”, 42}
{:hello, “World”, 42}
iex> {a, b} = {:hello, “World”, 42}
** (MatchError) no match of right hand side
value: {:hello, “World”, 42}
This is called destructuring. a, b, c are now bound to individual elements of the tuple.
© Alex Ufkes, 2020, 2021 16
Pin Operator
If we use the match operator with a variable on the left side of the expression, that variable is simply re-bound to that value. For example:
iex> x = 3 3
iex> x = 2 2
iex> x = 3 3
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
This is often undesirable!
© Alex Ufkes, 2020, 2021
17
Use ^ operator to force x to hold its binding
iex> x = 2 2
Pin Operator: Lists & Tuples
iex> [^x, y] = [1, 3]
** (MatchError) no match of right hand side value: [1, 3]
iex> y
** (CompileError) iex:2: undefined function y/0
iex> [^x, y] = [2, 3] [2, 3]
iex> y 3
© Alex Ufkes, 2020, 2021 18
Functions & Patterns
© Alex Ufkes, 2020, 2021 19
Pattern Matching: Function Signatures Function “overloading” is just pattern matching on the signature
© Alex Ufkes, 2020, 2021 20
Ideas? What can we do?
© Alex Ufkes, 2020, 2021 21
© Alex Ufkes, 2020, 2021 22
What about…
?
© Alex Ufkes, 2020, 2021
23
Single argument, a tuple
© Alex Ufkes, 2020, 2021 24
© Alex Ufkes, 2020, 2021 25
© Alex Ufkes, 2020, 2021 26
Recursion in Elixir
Who needs looping anyway?
defmodule Length do
def of([]), do: 0
def of([_ | t]), do: 1 + of(t)
end
When there’s one value left in the list, t will be [ ]
© Alex Ufkes, 2020, 2021 27
Argument pattern matching makes recursion straightforward:
Base cases
Recursive case
© Alex Ufkes, 2020, 2021
28
Tail Recursion?
Consider UserMath.fac()
defmodule UserMath do
def fac(0), do: 1
def fac(n), do: n*fac(n-1)
end
defmodule UserMath do
do: fac(num, 1)
def fac(0, prod), do: prod
def fac(num, prod), do: fac(num-1, num*prod) end
Wrapper function so user can invoke without initializing the running product
def fac(num),
Pass running product as argument
© Alex Ufkes, 2020, 2021 29
Private Functions, Default Arguments
defmodule UserMath do
def fac(num), do: fac(num, 1)
defp fac(0, prod), do: prod
defp fac(num, prod), do: fac(num-1, num*prod)
end
Hide the tail helper functions from the outside world
© Alex Ufkes, 2020, 2021 30
Control “Structures”
Implemented using function calls and pattern matching
© Alex Ufkes, 2020, 2021 31
Selection: if/else
© Alex Ufkes, 2020, 2021
32
© Alex Ufkes, 2020, 2021 33
As long as this expressions evaluates to true or false
Why?
© Alex Ufkes, 2020, 2021 34
Boolean:
true, false
Boolean Expressions
With these operators:
• non-false and non-nil are true.
• nil and false are false.
• 0 is considered true!
iex> “gh” && false Except…
© Alex Ufkes, 2020, 2021
35
false
iex> “gh” || false
“gh”
• •
The result isn’t true or false It’s the value that decided the result of true or false
&&, ||, !
What we actually get is the value that determined the truthiness of the expression
“No loop/if-else/case constructs”
In Elixir, we have several control structures that are implemented as macros. They are not actually constructs of the programming language.
Their implementation exists in the Elixir Kernel module.
They allow us to write if/else-style constructs in a familiar way. However, these are function calls behind the scenes.
© Alex Ufkes, 2020, 2021
36
?
© Alex Ufkes, 2020, 2021
37
if 1 < 2 do
“Hello”
end
Is the same as: Is the same as:
if 1 < 2, do: “Hello”
if(1 < 2, do: “Hello”)
do/end VS Keyword List
This form is a syntactic convenience allowed by Elixir to make the language more accessible.
© Alex Ufkes, 2020, 2021
38
if 1 < 2 do
“Hello”
else “World”
end
do/end VS Keyword List Is the same as:
if 1 < 2, do: “Hello”, else: “World”
Is the same as:
if(1 < 2, do: “Hello”, else: “World”)
iex> if 1 < 2, do: "Hello", else: "World" "Hello"
iex> if(1 < 2, do: "Hello", else: "World") "Hello"
© Alex Ufkes, 2020, 2021 39
if 1 < 2 do
“Hello”
else “World”
end
do/end VS Keyword List Is the same as:
if(1 < 2, do: “Hello”, else: “World”)
Is the same as:
if(1<2, [{:do, "Hello"}, {:else, "World"}])
© Alex Ufkes, 2020, 2021
40
if(1<2, [{:do, "Hello"}, {:else, "World"}])
© Alex Ufkes, 2020, 2021 41
if 1 < 2 do
“Hello”
else “World”
end
do/end VS Keyword List Is the same as:
if 1 < 2, do: “Hello”, else: “World”
Is the same as:
if(1<2, [{:do, "Hello"}, {:else, "World"}])
© Alex Ufkes, 2020, 2021
42
iex> if 1 < 2, do: "Hello", else: "World" "Hello"
iex> if(1 < 2, [{:do, "Hello”}, {:else, "World"}]) "Hello"
Can be any expression!
iex> if(1 < 2, [{:do, IO.puts "Hello"}, {:else, "World"}]) Hello
:ok
iex>
© Alex Ufkes, 2020, 2021 43
unless
unless is_integer(“hello”) do
“Not an Int”
end
iex> unless(is_integer(“hello”), do: “Not an Int”) “Not an Int”
iex> unless(is_integer(“hello”), [{:do, “Not an Int”}]) “Not an Int”
© Alex Ufkes, 2020, 2021 44
unless: With an else
unless is_integer(0b10101) do “Not an Int”
else
“An Int”
end
© Alex Ufkes, 2020, 2021
45
“An Int”
case
if and unless can’t handle pattern matching gracefully:
We can never get here!
• Matching returns the right-hand side…
• UNLESS no match is found, then it yields
a MatchError.
• If a match is found we’d be OK – [1, 2, 3]
is true (non-nil, non-false)
© Alex Ufkes, 2020, 2021 46
case
Match this tuple successively
with each case:
tup = {:ok, “Hello World”} case tup do
{:ok, result} -> result {:error} -> “Uh oh!”
_ -> “Catch all”
end
Pattern match!
{:ok, result} = {:ok, “Hello World”}
{:error} = {:ok, “Hello World”}
_ = {:ok, “Hello World”}
Without a catch-all, we’d get an error if no match was found.
© Alex Ufkes, 2020, 2021
47
© Alex Ufkes, 2020, 2021 48
© Alex Ufkes, 2020, 2021 49
© Alex Ufkes, 2020, 2021 50
Comment out catch all case
© Alex Ufkes, 2020, 2021 51
case: Matching Variables pi = 3.14
IO.puts pi What prints?
case do
pi -> IO.puts “Tasty ” <> pi
_ -> IO.puts “#{pi} is not tasty”
end
Attempts to match: pi = “apple pie”
• What’s the problem here?
© Alex Ufkes, 2020, 2021
52
“apple pie”
Pin pi using ^
© Alex Ufkes, 2020, 2021 53
Guard Clauses
Guard reference: https://hexdocs.pm/elixir/master/guards.html
© Alex Ufkes, 2020, 2021 54
Place a condition on the match:
• In this case, match is only successful if x < 0
Guard Clauses
Guard reference: https://hexdocs.pm/elixir/master/guards.html
© Alex Ufkes, 2020, 2021 55
cond
case is for pattern matching, cond is for conditions:
Evaluating cond:
• We say y=cond ...
• Cond evaluates to the final expression
under the first true condition.
• Similar to a block in Smalltalk
© Alex Ufkes, 2020, 2021
56
© Alex Ufkes, 2020, 2021 57
cond: Always have a catch-all
© Alex Ufkes, 2020, 2021
58
© Alex Ufkes, 2020, 2021
59
Enum
Enum
A set of algorithms for enumeration over enumerables! (lists, tuples, and more) Enum applies functions to lists in various ways. We will see a few:
Enum.all?
# Entire collection must evaluate to true for a given condition Enum.any?
# Any value in the collection must evaluate true Enum.map
# Apply a function to every element in the collection
More: https://elixirschool.com/en/lessons/basics/enum/
© Alex Ufkes, 2020, 2021 60
Enum.all?
Entire collection must evaluate to true for a given condition
Pass list as first arg
Anon function as second arg
© Alex Ufkes, 2020, 2021 61
Enum.all? We can do it!
• and the function result with the running Boolean result.
• If we hit an element for which f.(h) is false, the entire running Boolean becomes false.
© Alex Ufkes, 2020, 2021
62
Tail recursive!
Enum.all? We can do it!
© Alex Ufkes, 2020, 2021
63
Enum.any?
Any value in collection must evaluate to true for a given condition
© Alex Ufkes, 2020, 2021 64
Enum.any? We can do it!
Very similar to MyEnum.all
• Initialize res to false
• Any true value from function f
will turn result true.
• We are ORing instead of ANDing
© Alex Ufkes, 2020, 2021
65
Enum.any? We can do it!
© Alex Ufkes, 2020, 2021
66
Enum.map
Very useful! Apply a function to every element
© Alex Ufkes, 2020, 2021
67
Enum.map: We can do it!
• Result initialized as an empty list
• Concatenate [f.(h)] to the
running result list
© Alex Ufkes, 2020, 2021
68
Enum.map: We can do it!
© Alex Ufkes, 2020, 2021
69
Enum.map: We can do it!
© Alex Ufkes, 2020, 2021
Huh?
70
Interlude: IO.puts VS IO.inspect IO.puts can’t handle arbitrary lists:
iex> x = [1, 2.0, “Hello”, :world] [1, 2.0, “Hello”, :world]
iex> IO.puts x
** (ArgumentError) argument error
(stdlib) :io.put_chars(:standard_io, :unicode,
[[1, 2.0, “Hello”, :world], 10])
IO.puts wants a list containing things it can convert to Unicode.
© Alex Ufkes, 2020, 2021 71
Interlude: IO.puts VS IO.inspect We can use IO.inspect:
iex> x = [1, 2.0, “Hello”, :world] [1, 2.0, “Hello”, :world]
iex> IO.inspect x
[1, 2.0, “Hello”, :world]
[1, 2.0, “Hello”, :world]
© Alex Ufkes, 2020, 2021 72
IO.inspect prints and returns the list.
Interlude: IO.puts VS IO.inspect We can use IO.inspect:
iex> x = [104, 101, 108, 108, 111] ‘hello’
iex> IO.inspect x
‘hello’
‘hello’
IO.inspect still prints as Unicode!
© Alex Ufkes, 2020, 2021 73
Interlude: IO.puts VS IO.inspect We can use IO.inspect:
iex> x = [104, 101, 108, 108, 111] ‘hello’
iex> IO.inspect x
‘hello’
‘hello’
iex> IO.inspect x, charlists: :as_lists
[104, 101, 108, 108, 111] ‘hello’
Prints list as a list, rather than converting to Unicode.
© Alex Ufkes, 2020, 2021 74
Invoke IO.inspect thusly:
Interlude: IO.puts VS IO.inspect IO.inspect x, charlists: :as_lists
Recall keyword list form:
IO.inspect(x, [{:charlists, :as_lists}])
© Alex Ufkes, 2020, 2021 75
Enum.reduce
Distill collection to single value based on some function
• acc is the running value
• By default, initialized to first element in list
© Alex Ufkes, 2020, 2021
76
Enum.reduce
Distill collection to single value based on some function
(9 – (8 – (7 – (6 – (5 – (4 – (3 – (2 – (1 – (0 – acc)…)
VS
(…(acc – 1) – 2) – 3) – 4) – 5) – 6) – 7) – 8) – 9)
© Alex Ufkes, 2020, 2021 77
Enum.reduce: We can do it!
• Result initialized as head of list
• Pass head of list and current
result into f
© Alex Ufkes, 2020, 2021
78
Enum.reduce: We can do it!
© Alex Ufkes, 2020, 2021
79
• acc is the running value
• By default, initialized to first element in list
We can add an optional 3rd argument to initialize acc:
iex> Enum.reduce([1, 2, 3], 10, fn(x, acc) -> x+acc end) 16
iex> Enum.reduce([1, 2, 3], fn(x, acc) -> x+acc end)
6
10 + 1 + 2 + 3
VS
1+ 2 + 3
© Alex Ufkes, 2020, 2021 80
Stream
© Alex Ufkes, 2020, 2021 81
Streams
Like Enum, but Streams are lazy! • Enum functions are strict/eager.
• The result of an Enum is the list that results from applying it:
iex> list = [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
iex> Enum.map(list, &(&1 + 1)) [2, 3, 4, 5, 6]
© Alex Ufkes, 2020, 2021
82
Streams
Like Enum, but Streams are lazy!
• Stream and Enum share many functions.
• What is the result of evaluating Stream.map?
iex> list = [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
iex> Stream.map(list, &(&1 + 1)) #Stream<[
enum: [1, 2, 3, 4, 5],
funs: [#Function<48.103564624/1 in Stream.map/2>] ]>
© Alex Ufkes, 2020, 2021 83
iex> list = [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
iex> Stream.map(list, &(&1 + 1)) #Stream<[
enum: [1, 2, 3, 4, 5],
funs: [#Function<48.103564624/1 in Stream.map/2>] ]>
• That’s not a list! Stream is its own type.
• Think of a stream as a recipe for producing the transformed list.
• Here, our stream is a recipe for adding 1 to every element.
• We haven’t actually done the cooking!
• Why is this useful?
© Alex Ufkes, 2020, 2021 84
Streams
Consider the following script:
list = [1, 2, 3, 4, 5]
r1 = Enum.map(list, &(&1 + 1)) |>
Enum.map(&(&1 * 3)) |> Enum.map(&(&1 / 2))
An aside: Pipe is useful here!
• Output list from Enum piped
into next call as 1st arg.
• Thus, subsequent Enum calls
only have 1 arg.
© Alex Ufkes, 2020, 2021
85
How many new lists are created when we evaluate this? One for each Enum call! Very inefficient.
Streams
list = [1, 2, 3, 4, 5]
r1 = Stream.map(list, &(&1 + 1)) |>
Stream.map(&(&1 * 3)) |> Stream.map(&(&1 / 2))
list = [1, 2, 3, 4, 5]
r1 = Stream.map(list, &(&1 + 1)) |>
Stream.map(&(&1 * 3)) |> Enum.map(&(&1 / 2))
• r1is a recipe for a new list
• At this point, no new list(s)
have been created!
• If we finish with an Enum call, the stream is applied.
• Only one new list created
© Alex Ufkes, 2020, 2021
86
© Alex Ufkes, 2020, 2021 87
Apply the Stream?
Can also use Enum.to_list
list = [1, 2, 3, 4, 5]
r1 = Stream.map(list, &(&1 + 1)) |>
Stream.map(&(&1 * 3)) |>
Stream.map(&(&1 / 2))
Enum.to_list(r1)
© Alex Ufkes, 2020, 2021
88
Lots more: https://hexdocs.pm/elixir/Stream.html
© Alex Ufkes, 2020, 2021 89
List Comprehensions
for:
• Generating • Filtering
• Operating
Produces a list when it’s done!
Not the same as an imperative-style for loop! Not for general purpose iteration.
© Alex Ufkes, 2020, 2021
90
List Comprehensions
Very much like comprehensions in Python:
Three parts:
• Generator • Filter
• Collector
Comprehensions are syntactic sugar for things we could otherwise do with Enum or recursive functions
© Alex Ufkes, 2020, 2021
91
List Comprehensions
iex> Enum.map([1, 2, 3, 4], &(&1*&1)) [1, 4, 9, 16]
iex> for n <- [1, 2, 3, 4], do: n*n [1, 4, 9, 16]
Generator: Any enumerable • In this case, a plain old list
© Alex Ufkes, 2020, 2021
92
List Comprehensions
iex> Enum.map([1, 2, 3, 4], &(&1*&1)) [1, 4, 9, 16]
iex> for n <- [1, 2, 3, 4], do: n*n [1, 4, 9, 16]
iex> for n <- do: n*n [1, 4, 9, 16]
1..4,
• Used to produce list [1, 2, 3, 4]
• Can generate large lists this way
• Note: 1..4 is NOT itself a list!
• It is a Range
© Alex Ufkes, 2020, 2021
93
© Alex Ufkes, 2020, 2021 94
List Comprehensions
iex> for n <- 1..4, do: n*n [1, 4, 9, 16]
• List comprehensions produce lists
• Generators like the above are lazy (Range)
• Operate on elements one at a time,
discarding previous.
• That is, at no point do we produce the
complete list [1, 2, 3, 4] in memory. https://hexdocs.pm/elixir/Range.html
© Alex Ufkes, 2020, 2021
95
List Comprehensions: Pattern Matching iex> vals = [good: 1, good: 2, bad: 3, good: 4]
Keyword list!
iex> vals = [{:good, 1}, {:good, 2}, {:bad, 3}, {:good, 4}] [good: 1, good: 2, bad: 3, good: 4]
© Alex Ufkes, 2020, 2021 96
List Comprehensions: Pattern Matching iex> vals = [good: 1, good: 2, bad: 3, good: 4]
[good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- vals, do: n*n [1, 4, 16]
© Alex Ufkes, 2020, 2021
97
• •
Pattern matching is powerful
We can also filter in a Boolean fashion
List Comprehensions: Filtering
iex> fun = &(rem(&1, 3) == 0) #Function<6.99386804/1 in :erl_eval.expr/5>
iex> for n <- 1..20, do: n [3, 6, 9, 12, 15, 18]
fun.(n),
Filter is optional
• Include it after generator if desired
• Only elements that evaluate to true when
filtered will make it to the do: block
© Alex Ufkes, 2020, 2021
98
List Comprehensions: Filtering & Matching iex> list = [a: 1, b: “2”, a: 3.0, a: “4.0”, b: {5}, a: [“6.0”]]
[a: 1, b: “2”, a: 3.0, a: “4.0”, b: {5}, a: [“6.0”]]
iex> for {:a, n} <- list, is_number(n), do: n [1, 3.0]
Nothing semantically new here
• Anything we can do with comprehensions we can do with Enum or our own functions.
• It might require more syntax, but we can do it.
• Comprehensions can be used to create concise code
© Alex Ufkes, 2020, 2021
99
List Comprehensions: In 2D?
iex> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
We get a keyword list containing combinations of all elements from both generators
© Alex Ufkes, 2020, 2021
100
© Alex Ufkes, 2020, 2021
101
• •
Elixir Processes (In Brief):
Elixir is built on a process model. Recall:
Elixir code runs inside lightweight threads of execution. o Isolated, exchange information via message passing.
Not uncommon to have hundreds of thousands of processes running concurrently in same VM.
o Note: These are NOT operating system processes! o Extremely lightweight in terms of CPU and memory o A process need not be an expensive resource
Elixir Processes
Playing with processes:
• self() Returns PID of current process.
o In this case, it’s the PID of our interactive shell session
• Process.alive?() tests if a process is currently active. • We can spawn functions as processes!
© Alex Ufkes, 2020, 2021
102
Elixir Processes
• spawn takes a function as an argument and returns its PID once spawned.
• Function executes when spawned
Process is not active, the function is not currently executing
© Alex Ufkes, 2020, 2021
103
Elixir Processes: Send & Receive iex> send(self(), {:Hello, “World”})
{:Hello, “World”}
• send/2 can be used to send a message (!!!) to a process (by PID)
• This message goes into a mailbox and can be received using the
receive/1 function
• When invoking receive, it will go through the messages in the mailbox
and attempt to match the messages with the provided patterns
© Alex Ufkes, 2020, 2021 104
Elixir Processes: Send & Receive
iex> send(self(), {:Hello, “World”}) {:Hello, “World”}
iex> receive do
…> {:Hello, msg} -> msg
…> {:World, msg} -> “won’t match”
…> end
“World”
iex>
• Once the message is received, it is consumed!
• We can’t receive the same message twice.
• Subsequent receive calls will be blocking
© Alex Ufkes, 2020, 2021
105
Elixir Processes: Send & Receive
© Alex Ufkes, 2020, 2021
106
Elixir Processes: Send & Receive
Receive is blocking!
• We sent one message, and received it.
• We then try and receive again, but the
mailbox is empty.
• Process sits and waits.
© Alex Ufkes, 2020, 2021
107
Elixir Processes: Send & Receive
© Alex Ufkes, 2020, 2021
108
Elixir Processes: Send & Receive
• Spawning a function as a process executes that function.
• A blocking receive can be used to wait for messages.
• Once the function receives a message, it will pattern match.
• Receive only blocks once! We have to spawn the function three times.
• Different order?
• Execution is interleaved.
• Up to scheduler.
© Alex Ufkes, 2020, 2021
109
Elixir Processes: Send & Receive
• Spawn all three, send each a message.
• Which child process gets chosen to
execute is up to the scheduler.
© Alex Ufkes, 2020, 2021
110
Elixir Processes
• This has been a taste. There’s lots more.
• Elixir is famous for powerful concurrent processing.
• Processes can be used to emulate the object message
passing model in languages like Smalltalk.
• If you understand a bit about concurrency from 209 or
590, check it out.
https://elixir-lang.org/getting-started/processes.html
© Alex Ufkes, 2020, 2021 111
© Alex Ufkes, 2020, 2021 112
Functional Programming & Elixir
We saw:
• Functionsasfirst-classentities
o How to create and pass anonymous functions as arguments o How to return anonymous functions
• •
Immutable data – variables (names) are bound and matched using = o Collections are not modified.
o Enum.map returns a new collection
Recursion – Loops are tail-recursive (ideally) functions calls. o Enum functions work this way behind the scenes
Elixir provides many syntax conveniences that make code more familiar to programmers accustomed to the imperative style.
© Alex Ufkes, 2020, 2021 113
Functional Programming & Elixir
Flow control is not built into the language as syntax constructs
• This suggests there’s no iteration in the typical sense of the word.
• Looping is accomplished with control structures in imperative languages.
• If control structures are functions, that means looping is always recursive.
• However
• This refers to the high level implementation only.
• Machine instructions are optimized into iteration via tail recursion.
© Alex Ufkes, 2020, 2021 114
Elixir Syntax
• Dynamically typed
o Type inferred at run-time
o Need not explicitly specify type upon declaration
• Provides syntax conveniences to make it more intuitive to programmers accustomed to imperative languages
• Interactive shell provides help/search functionality
https://media.pragprog.com/titles/elixir/ElixirCheat.pdf
© Alex Ufkes, 2020, 2021 115
Elixir Syntax
Reserved words
• true,false,nil o Used as atoms
• when,and,or,not,in o Used as operators
• fn
o Used for anonymous function definitions
• do,end,catch,rescue,after,else o Used in do/end blocks
https://github.com/elixir-lang/elixir/blob/master/lib/elixir/pages/Syntax%20Reference.md
© Alex Ufkes, 2020, 2021 116
Further Reading
https://elixir-lang.org/getting-started/introduction.html https://elixirschool.com/en/lessons/basics/basics/
© Alex Ufkes, 2020, 2021 117
Elixir Popularity
© Alex Ufkes, 2020, 2021
118
Elixir Popularity
https://techbeacon.com/5-emerging-programming-languages-bright-future
This list also includes Rust!
© Alex Ufkes, 2020, 2021
119
© Alex Ufkes, 2020, 2021 120
© Alex Ufkes, 2020, 2021 121