- Programming Elixir
- Part1 Conventional Programming
- Part2 Concurrent Programming
Programming Elixir
Part1 Conventional Programming
Pattern Matching
Match operator =
is not assignment. It's an assertion. It's true if Elixir can find a way of making the left-hand side equal the right-hand side. If the left-hand side is variable, Elixir will bind the right value to the left. ^
can force Elixir compare left-hand side and right-hand side using the existing value.
iex> a = 1
1
iex> 1 = a
1
iex> 2 = a
** (MatchError) no match of right hand side value: 1
iex> ^a = 2
** (MatchError) no match of right hand side value: 2
_
is the value being ignored
iex> [1, _, _] = [1, 2, 3]
Immutability
All values are immutable. If you want to add 100 to [1,2,3], Elixir produces a copy of the original, containing the new values.
In the following example, Elixir knows list1 will never chang, so it simply create a new list with head of 4
and a tail of list1
.
iex> list1 = [ 3, 2, 1 ] [3, 2, 1]
iex> list2 = [ 4 | list1 ] [4, 3, 2, 1]
Elixir Basics
All around
Elixir 可以比較任意兩種型別
number < atom < reference < function < port < pid < tuple < map < list < bitstring
Atoms
Atom (symbol in Ruby): :foo
Boolean is atom
iex> true |> is_boolean
true
iex> true |> is_atom
true
Module name is atom
iex> is_atom(MyApp.MyModule)
true
Tuples
Similar to lists, continuous in memory, fast in accessing its length, slow in modificating it (most similar to array)
{}
Tuples usually are additional function returning messages
iex> { :ok, file } = File.open("mix.exs")
{:ok, #PID<0.39.0>}
iex> { :ok, file } = File.open("non-existent-file")
** (MatchError) no match of right hand side value: {:error, :enoent}
Lists
Counting length is O(n), prepend is faster than append
[]
List concatenation
iex> list1 = [1, :foo, "bar"]
iex> list2 = [2]
iex> list2 ++ list1
[2, 1, :foo, "bar"]
List subtraction (difference), for each element on the right, the same element that exists in the first will be eliminated
iex> [1,2,3,1] -- [1,2]
[3,1]
List membership
iex> 1 in [1,2,3]
true
head and tail
iex> hd [3.14, :pie, "Apple"]
3.14
iex> tl [3.14, :pie, "Apple"]
[:pie, "Apple"]
# Pattern matching and | operator
iex> [head | tail] = [3.14, :pie, "Apple"]
iex> [1,2,3] ++ 4
[1, 2, 3 | 4]
Keyword lists
A special list of two-element tuples, the first element in the tuple is atom, performance is as same as lists
# {} of tuple can be omitted
iex> [foo: "bar", hello: "world"]
iex> [{:foo, "bar"}, {:hello, "world"}]
iex> [{:foo, "bar"}, {:hello, "world"}, {:hello, "kitty"}]
- Keys are atoms
- Keys are ordered
- Keys may not be unique
Keyword lists are most commonly used to pass options to functions
Maps
A key-value pair, no restriction on data type and un-orderd
%{}
iex> map = %{:foo => "bar", "hello" => :world}
iex> map.foo
"bar"
iex> map[:foo]
"bar"
iex> map["hello]
:world
iex> %{map | foo: "baz"}
%{:foo => "baz", "hello" => :world}
- Key is unique
- Efficient
- Used for pattern matching
In general, use keyword lists for things such as command-line parameters and for passing around options, and use maps when you want an associative array.
Pattern matching cannot bind keys. Pattern matching can match variable keys (using ^
operator).
Variable Scope
Elixir is lexically scoped.
with
expression
- Defines a local scope for variables
- Give control over pattern matching
The variables inside with
are local.
If any pattern matching inside with
fails, it returns the value that couldn't be matched.
# The value of the with is the value of its do parameter.
lp = with some_pattern_matching,
another_pattern_matching,
yet_another_pattern_matching
do
...
end
Anonymous Functions
fn
paramter-list -> body
paramter-list -> body
end
iex> sum = fn (a, b) -> a + b end
# The dot indicates the function call
iex> sum.(1,2)
3
When making a function call, it do pattern matching to the input arguments with parameter list. And only the body that has input matched parameter list will be returned.
Function can also return function. A closure.
iex> greeter = fn name -> (fn -> "Hello #{name}" end) end
iex> dave_greeter = greeter.("Dave")
iex> dave_greeter.()
"Hello Dave"
iex> add_one = &(&1 + 1) # same as add_one = fn (n) -> n + 1 end
If the body of anonymous function is just a call to named function, Elixir replaces it with a direct reference to that function. (Arguements must in order)
iex> rnd = &(Float.round(&1, &2))
&Float.round/2
Modules and Named Functions
All of the following are the same, do...end
is just syntactic sugur of do:
, and do:
is just a term in a keyword list
def xxx(), do: ooo
def xxx, do: (
ooo
)
def xxx do
ooo
end
Elixir tries functions from the top down, executing the first match.
defmodule BadFactorial do
def of(0), do: 1
def of(n), do: n * of(n-1)
end
defmodule BadFactorial do
def of(n), do: n * of(n-1)
def of(0), do: 1
end
Guard Clauses
defmodule Guard do
def what_is(x) when is_number(x) do
IO.puts "#{x} is a number"
end
def what_is(x) when is_list(x) do
IO.puts "#{inspect(x)} is a list"
end
def what_is(x) when is_atom(x) do
IO.puts "#{x} is an atom"
end
end
Default Parameters
Simply add a function head with no body that contains the default parameters, and use regular parameters for the rest. The defaults will apply to all calls to the function.
defmodule Params do
def func(p1, p2 \\ 123)
def func(p1, p2) when is_list(p1) do
"You said #{p2} with a list"
end
def func(p1, p2) do
"You passed in #{p1} and #{p2}"
end
end
The |> Operator
The |>
operator takes the result of the expression to its left and inserts it as the first parameter of the function invocation to its right.
Always use ()
in |>
.
Modules
Directive
Lexically scoped.
import Module [, only:|except: ]
defmodule Example do
def func1 do
List.flatten [1,[2,3],4]
end
def func2 do
import List, only: [flatten: 1]
flatten [5,[6,7],8]
end
end
alias
defmodule Example do
def compile_and_go(source) do
alias My.Other.Module.Parser, as: Parser
alias My.Other.Module.Runner, as: Runner
source
|> Parser.parse()
|> Runner.execute()
end
end
require
You require a module if you want to use any macros it defines.
Module Attributes
Works only at the top level of a module.
defmodule Example do
@example "My example"
end
These attributes are not variables in the conventional sense. Use them for configuration and metadata only. (Similar to Ruby constants.)
Module Names
Module names are atoms. Elixir auto converts upper case IO
to atom Elixir.IO
.
Calling a Function in an Erlang Library
Erlang variables start with an uppercase letter and atoms are lowercase names.
Such as :io
, :timer
.
Lists and Recursion
List is a head and tail, the tail itself is a list.
# Single element list
iex> [3]
iex> [3 | []]
# Two elements list
iex> [1,2]
iex> [1 | [2 | []]]
Maps, Keyword Lists, Sets, and Structs
Enum and Stream
Processing Collections
Strings and Binaries
Organizing a Project
Part2 Concurrent Programming
Working with Multiple Processes
Elixir use actor model of concurrency. An actor is an independent process that shares nothing whith any other process. Elixir uses processes support in Erlang. They have very little overhead.
# Run named function in a module, with a list of arguments
defmodule SpawnBasic do
def greet do
IO.puts "Hello"
end
end
iex> spawn(SomeModule, :some_method, [])
#PID<0.108.0>
# Run anonymous function
iex> spawn(fn -> IO.puts "hello" end)
#PID<0.110.0>
spawn itself returns a PID, and the process will run the code we specified. You don't know when will it be executed.
Send message to process using send
, the messge are often atoms or tuples. Wait a message using 'receive`.
defmodule Spawn1 do
def greet do
receive do
{sender, msg} ->
send sender, { :ok, "Hello, #{msg}" }
end
end
end