means data can't change
1 + 2 # 1 is still 1
true || false # true is still true
Nothing controversial there
Also immutable in elixir
my_list = [:foo, :bar]
my_list.unshift(:baz) # NOT A THING. Won't work
Do this instead
my_list1 = [:foo, bar]
my_list2 = [:baz | my_list1]
# my_list1 == [:foo, :bar] (still)
# my_list2 == [:baz, :foo, :bar]
Yup. Immutable
my_tuple1 = {:person, "Kevin"}
my_tuple2 = put_elem(my_tuple, 1, "Bob")
# my_tuple1 == {:person, "Kevin") (still)
# my_tuple2 == {:person, "Bob")
my_map1 = %{name: "Stuart", colour: "yellow"}
my_map2 = %{my_map1 | name: "Bob"}
# my_map1 == %{name: "Stuart", colour: "yellow"}
# my_map2 == %{name: "Bob", colour: "yellow"}
State is the enemy. So make state explicit.
Easier to reason. Less to worry and think about.
Errors can be recovered. Just go back in time.
Can only prepend (easily)
# [ | ] syntax to prepend to a list
new_list = [new_element | old_list]
# [ | ] syntax to also pattern match to a list
[head | tail] = new_list
# head == new_element
# tail == old_list
Single linked lists. Head contains element, and pointer to another list
[42 | [69 | [613] ] == [42, 69, 613]
(image from wikipedia)
prepend, then reverse instead
# SLOW. DON'T DO THIS
list = []
list = List.insert_at(list, -1, "Kevin")
list = List.insert_at(list, -1, "Stuart")
list = List.insert_at(list, -1, "Bob")
# Do this instead - fast. syntax sugar guides you
list = []
list = ["Kevin" | list]
list = ["Stuart" | list]
list = ["Bob" | list]
list = Enum.reverse(list)
Don't write inline code with [ | ]
over and over
Can't do traditional loops (for needs to mutate something)
So we use recursive functions work on lists
def sum(list) do
sum(list, 0)
end
def sum([], acc) do
acc
end
def sum([h|t], acc) do
sum(t, h + acc)
end
Enum.reduce(my_list, 0, fn(n, acc) -> n + acc end)
# OR
Enum.sum(my_list)
Implemented as a persistent data structure. Shallow copy on modification. Nodes point to data as before
Single level changes are easy
my_map = %{name: "Kevin", colour: "Yellow"}
my_map2 = %{my_map | name: "Evil Kevin", colour: "Purple"}
use put_in / get_in / update_in
etc
put_in(my_complex_map[:minions][:bob][:name], "Kevin")
limitation - doesn't work for mixed maps/lists/tuples. only maps
Hopefully you've seen by now that elixir makes it obvious and explicit when you are going to mutate state.
You need to think about this, instead of it "just happening"
You can roll your own state management (like we've seen)
Or use built in functionality:
Actor pattern is commonly used
Receive block allows explicit control by actor over how state changes get applied
About a million tutorials on that already ;)