Notes from “Programming Elixir”
Language Basic
Keyword Lists
Shortcut for lists of key/value pairs1
2> [id: 1, name: "abc"]
[{:id, 1}, {:name, "abc}]
Square brackets can be left off if a keyword list is the last argument of a function call1
2> DB.save record, id: 1, name: "abc"
# same as DB.save record, [id: 1, name: "abc"]
Square brackets can be left off when a keyword list appears as the last item where a list is expected1
2
3
4
5
6
7> [1, name: "abc", code: "001"]
# same as [1, {:name, "abc"}, {:code, "001"}]
> {1, name: "abc", code: "001"}
# same as {1, [name: "abc", code: "001"]}
# because keyword-list shortcut applies to list, so the above name and code will be grouped into a list
# although personally I encourage to put square brackets explicitly.
Maps
Maps support the same shortcut as keyword list if key is an atom1
2> %{ id: 1, name: "abc" }
# same as %{ :id => 1, :name => "abc" }
If keys are atoms, maps also support dot notation1
2
3
4
5
6
7
8
9> product = %{ id: 1, name: "abc" }
> product.id
# same as product[:id]
# Note KeyError if no matching key when using dot notation instead of nil
> product.not_existing
** (KeyError)
> product[:not_existing]
nil
Variable Scope
Local variables defined inside module is accessible to top level, not including inside functions1
2
3
4
5
6
7
8
9
10
11
12
13defmodule Foo do
info = "Foo module information"
def bar do
# info is NOT visible here
# IO.puts info
end
# info is visible here
IO.puts info
end
To make it accessible, use module attributes. Note that the attribute value will be defined in the order of its appearance.1
2
3
4
5
6
7defmodule Foo do
@attr "abc"
def function_1, do: @attr # return abc
@attr "123"
def function_2, do: @attr # return 123
end
Attributes are not variables, but for configuration and metadata.
with Expression works effectively for temporary variables1
2
3
4
5
6
7
8name = with {:ok, file} = File.open("lib/elixir_hello.ex"),
content = IO.read(file, :all),
:ok = File.close(file),
[_, module_name] = Regex.run(~r{defmodule .*?(\w+) do}, content)
do
"Module Name:#{module_name}"
end
# Note file, content and module_name are not available when with expression finishes
<- makes pattern matching return nil instead of KeyError1
2
3
4
5
6
7
8
9
10
11> with [a|_] = [1,2,3], do: a
1
> with [a|_] <- [1,2,3], do: a
1
> with [a|_] = nil, do: a
** (KeyError)
> with [a|_] <- nil, do: a
nil
Function
[] and {} are operators, so literal lists and tuples can be turned into functions1
2
3> divrem = &{ div(&1,&2), rem(&1,&2) }
> divrem.(13, 5)
{2, 3}
|| and && are not allowed in guard clause. Neither for functions/operators with side-effects1
2def gcd(x, y) when x >= y and y >= 0, do: gcd(y, rem(x, y))
# not def gcd(x, y) when x >= y && y >= 0, do: gcd(y, rem(x, y))
When function has multiple clauses and default parameters, then a function head with no body that contains the default parameters1
2
3
4
5defmodule Foo do
def bar(x, y \\ 123)
def bar(x, y) when y > 0, do: ...
def bar(x, y), do: ...
end
List
Join operator | supports multiple values to its left1
[1,2,3 | [4,5,6]]
Map
When using pattern matching for maps, the left part can be partially matched. But the key must be provided1
2
3
4
5
6> maps = %{id: 1, name: 'abc'}
> %{id: aID} = maps
aID = 1
> %{idKey: 1} = maps
** (MatchError)
Struct
The way to define a struct containing another struct. Note 1
2
3
4
5
6
7
8```
defmodule Name do
defstruct first_name: "", last_name: "Unknown"
end
defmodule Person do
defstruct id: 0, name: %Name{}
end