├── .gitignore ├── COPYRIGHT ├── otp └── src │ └── tweet_aggregator │ ├── test │ ├── test_helper.exs │ ├── aggregator_test.exs │ └── gate_keeper_test.exs │ ├── .gitignore │ ├── lib │ ├── tweet_aggregator │ │ ├── reporter.ex │ │ ├── supervisor.ex │ │ ├── node_monitor.ex │ │ ├── search │ │ │ ├── supervisor.ex │ │ │ ├── server.ex │ │ │ └── client.ex │ │ ├── aggregator.ex │ │ └── gate_keeper.ex │ └── tweet_aggregator.ex │ ├── mix.lock │ ├── mix.exs │ └── README.md ├── advanced ├── src │ ├── genserver_stack_example │ │ ├── test │ │ │ ├── test_helper.exs │ │ │ └── stack_test.exs │ │ ├── .gitignore │ │ ├── lib │ │ │ ├── stack.ex │ │ │ └── stack │ │ │ │ ├── supervisor.ex │ │ │ │ └── server.ex │ │ ├── mix.exs │ │ └── README.md │ ├── counter.exs │ ├── pmap.exs │ └── custom_stack.exs ├── protocols.md ├── maps.md ├── holding_state.md └── processes.md ├── basics ├── src │ └── rocket.exs ├── 04_pattern_matching.md ├── 01_system_setup.md ├── 02_getting_started.md ├── 06_pipeline_operator.md ├── 08_macros.md ├── 05_control_flow.md ├── 07_modules.md └── 03_basics.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.beam 3 | .env 4 | _build 5 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | © Chris McCord, 2014 All Rights Reserved. 2 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start 2 | -------------------------------------------------------------------------------- /advanced/src/genserver_stack_example/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start 2 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | -------------------------------------------------------------------------------- /advanced/src/genserver_stack_example/.gitignore: -------------------------------------------------------------------------------- 1 | /ebin 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator/reporter.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.Reporter do 2 | 3 | end 4 | -------------------------------------------------------------------------------- /advanced/src/genserver_stack_example/lib/stack.ex: -------------------------------------------------------------------------------- 1 | defmodule Stack do 2 | use Application.Behaviour 3 | 4 | # See http://elixir-lang.org/docs/stable/Application.Behaviour.html 5 | # for more information on OTP Applications 6 | def start(_type, _args) do 7 | Stack.Supervisor.start_link 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator do 2 | use Application 3 | 4 | # See http://elixir-lang.org/docs/stable/Application.Behaviour.html 5 | # for more information on OTP Applications 6 | def start(_type, _args) do 7 | :ssl.start 8 | :inets.start 9 | TweetAggregator.Supervisor.start_link 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator/supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.Supervisor do 2 | use Supervisor 3 | 4 | def start_link do 5 | :supervisor.start_link(__MODULE__, []) 6 | end 7 | 8 | def init([]) do 9 | children = [ 10 | worker(TweetAggregator.NodeMonitor, []) 11 | ] 12 | supervise(children, strategy: :one_for_one) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/test/aggregator_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AggregatorTest do 2 | use ExUnit.Case 3 | alias TweetAggregator.Aggregator 4 | 5 | setup do 6 | if Aggregator.has_leader?, do: Process.exit(Aggregator.leader_pid, :kill) 7 | :ok 8 | end 9 | 10 | test "#become_leader starts Aggregator process" do 11 | refute Aggregator.has_leader? 12 | Aggregator.become_leader 13 | assert Aggregator.has_leader? 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /basics/src/rocket.exs: -------------------------------------------------------------------------------- 1 | defmodule Rocket do 2 | 3 | def start_launch_sequence(from \\ 10) when from >= 0 and from < 20 do 4 | IO.puts "Liftoff in #{from}..." 5 | countdown(from) 6 | end 7 | 8 | defp countdown(0), do: blastoff 9 | defp countdown(seconds) do 10 | IO.puts seconds 11 | countdown(seconds - 1) 12 | end 13 | 14 | def blastoff do 15 | IO.puts "Liftoff!" 16 | end 17 | end 18 | 19 | Rocket.start_launch_sequence 20 | 21 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator/node_monitor.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.NodeMonitor do 2 | 3 | def start_link do 4 | {:ok, spawn_link fn -> 5 | :global_group.monitor_nodes true 6 | monitor 7 | end} 8 | end 9 | 10 | def monitor do 11 | receive do 12 | {:nodeup, node} -> IO.puts "NodeMonitor: #{node} joined" 13 | {:nodedown, node} -> IO.puts "NodeMonitor: #{node} left" 14 | end 15 | monitor 16 | end 17 | end 18 | 19 | -------------------------------------------------------------------------------- /advanced/src/genserver_stack_example/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Stack.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ app: :stack, 6 | version: "0.0.1", 7 | elixir: "~> 0.12 or ~> 0.13-dev", 8 | deps: deps ] 9 | end 10 | 11 | # Configuration for the OTP application 12 | def application do 13 | [mod: { Stack, [] }] 14 | end 15 | 16 | # Returns the list of dependencies in the format: 17 | # { :foobar, "~> 0.1", git: "https://github.com/elixir-lang/foobar.git" } 18 | defp deps do 19 | [] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /advanced/src/genserver_stack_example/test/stack_test.exs: -------------------------------------------------------------------------------- 1 | defmodule StackTest do 2 | use ExUnit.Case 3 | alias Stack.Client 4 | alias Stack.Server 5 | 6 | setup do 7 | Client.start 8 | :ok 9 | end 10 | 11 | test "pushing adds items to the top of the stack" do 12 | Client.push(1) 13 | Client.push(2) 14 | Client.push(3) 15 | 16 | assert Client.pop == 3 17 | assert Client.pop == 2 18 | assert Client.pop == 1 19 | end 20 | 21 | test "popping from and empty stack returns nil" do 22 | assert Client.pop == nil 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/mix.lock: -------------------------------------------------------------------------------- 1 | %{"erlang-oauth": {:git, "git://github.com/tim/erlang-oauth.git", "3ad509d487a72f9437fef31a379bf3cfeeb166a2", []}, 2 | "httpotion": {:git, "git://github.com/myfreeweb/httpotion.git", "f41ad7b86cf0de5ce2830323290b30f55df17f17", []}, 3 | "ibrowse": {:git, "git://github.com/cmullaparthi/ibrowse.git", "d824491d37449763ac44398f8494731e0c1418c1", []}, 4 | "jsx": {:git, "git://github.com/talentdeficit/jsx.git", "d011411c23e9f8c29d98aa1b7a50ddf9709b6a60", []}, 5 | "oauth": {:git, "git://github.com/tim/erlang-oauth.git", "3ad509d487a72f9437fef31a379bf3cfeeb166a2", []}} 6 | -------------------------------------------------------------------------------- /advanced/src/genserver_stack_example/lib/stack/supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule Stack.Supervisor do 2 | use Supervisor.Behaviour 3 | 4 | def start_link do 5 | :supervisor.start_link(__MODULE__, []) 6 | end 7 | 8 | def init(initial_stack) do 9 | children = [ 10 | # Define workers and child supervisors to be supervised 11 | worker(Stack.Server, [initial_stack]) 12 | ] 13 | 14 | # See http://elixir-lang.org/docs/stable/Supervisor.Behaviour.html 15 | # for other strategies and supported options 16 | supervise(children, strategy: :one_for_one) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /advanced/src/counter.exs: -------------------------------------------------------------------------------- 1 | defmodule Counter.Server do 2 | 3 | def start(initial_count \\ 0) do 4 | spawn fn -> listen(initial_count) end 5 | end 6 | 7 | defp listen(count) do 8 | receive do 9 | :inc -> listen(count + 1) 10 | :dec -> listen(count - 1) 11 | {sender, :val} -> 12 | send sender, count 13 | listen(count) 14 | end 15 | end 16 | 17 | end 18 | 19 | defmodule Counter.Client do 20 | 21 | def inc(pid), do: send(pid, :inc) 22 | def dec(pid), do: send(pid, :dec) 23 | def val(pid) do 24 | send pid, {self, :val} 25 | receive do 26 | val -> val 27 | end 28 | end 29 | end 30 | 31 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator/search/supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.Search.Supervisor do 2 | use Supervisor 3 | 4 | def stop(server_name) do 5 | Process.exit Process.whereis(name(server_name)), :shutdown 6 | end 7 | 8 | def start_link(server_name, query) do 9 | :supervisor.start_link({:local, name(server_name)}, __MODULE__, [server_name, query]) 10 | end 11 | 12 | def init([server_name, query]) do 13 | tree = [worker(TweetAggregator.Search.Server, [server_name, query])] 14 | supervise tree, strategy: :one_for_one 15 | end 16 | 17 | defp name(server_name) do 18 | :"supervisor_#{server_name}" 19 | end 20 | end 21 | 22 | -------------------------------------------------------------------------------- /advanced/src/pmap.exs: -------------------------------------------------------------------------------- 1 | defmodule Mapper do 2 | 3 | def map(list, func) do 4 | do_map(list, func, []) 5 | end 6 | 7 | defp do_map([], func, results), do: Enum.reverse(results) 8 | defp do_map([head | tail], func, acc) do 9 | result = func.(head) 10 | do_map(tail, func, [result | acc]) 11 | end 12 | 13 | def pmap(list, func) do 14 | list 15 | |> spawn_children(func, self) 16 | |> collect_results 17 | end 18 | 19 | defp spawn_children(list, func, parent) do 20 | map list, fn el -> 21 | spawn fn -> 22 | send parent, {self, func.(el)} 23 | end 24 | end 25 | end 26 | 27 | defp collect_results(pids) do 28 | map pids, fn pid -> 29 | receive do 30 | {^pid, result} -> result 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ app: :tweet_aggregator, 6 | version: "0.0.1", 7 | elixir: "~> 0.12 or ~> 0.13-dev", 8 | deps: deps ] 9 | end 10 | 11 | # Configuration for the OTP application 12 | def application do 13 | [ 14 | mod: { TweetAggregator, [] }, 15 | ] 16 | end 17 | 18 | # Returns the list of dependencies in the format: 19 | # { :foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1" } 20 | # 21 | # To specify particular versions, regardless of the tag, do: 22 | # { :barbat, "~> 0.1", github: "elixir-lang/barbat" } 23 | defp deps do 24 | [ 25 | {:oauth, github: "tim/erlang-oauth"}, 26 | {:jsx, github: "talentdeficit/jsx"} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /advanced/src/custom_stack.exs: -------------------------------------------------------------------------------- 1 | defmodule Stack.CustomServer do 2 | 3 | def start(initial_stack) do 4 | spawn_link fn -> 5 | Process.register self, :custom_server 6 | listen initial_stack 7 | end 8 | end 9 | 10 | def listen(stack) do 11 | receive do 12 | {sender, :pop} -> handle_pop(sender, stack) 13 | {:push, value} -> listen([value|stack]) 14 | after 5000 -> 15 | IO.puts "Nothing to do" 16 | listen stack 17 | end 18 | end 19 | 20 | def handle_pop(sender, [head|tail]) do 21 | send sender, head 22 | listen tail 23 | end 24 | end 25 | 26 | defmodule Stack.CustomClient do 27 | def push(value) do 28 | send server_pid, {:push, value} 29 | end 30 | 31 | def pop do 32 | send server_pid, {self, :pop} 33 | receive do 34 | value -> value 35 | end 36 | end 37 | 38 | defp server_pid do 39 | Process.whereis :custom_server 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /basics/04_pattern_matching.md: -------------------------------------------------------------------------------- 1 | # Pattern Matching 2 | Pattern matching lives at the heart of the Erlang Virtual Machine. When binding or invoking a function, the VM is pattern matching on the provided expression. For example, when Elixir binds a variable on the left hand side of `=` with the expression on the right, it always does so via pattern matching. 3 | 4 | ```elixir 5 | iex(1)> a = 1 6 | 1 7 | iex(2)> 1 = a 8 | 1 9 | iex(3)> b = 2 10 | 2 11 | iex(4)> ^a = b 12 | ** (MatchError) no match of right hand side value: 2 13 | iex(5)> ^a = 1 14 | 1 15 | iex(6)> [first, 2, last] = [1, 2, 3] 16 | [1, 2, 3] 17 | iex(7)> first 18 | 1 19 | iex(8)> last 20 | 3 21 | ``` 22 | 23 | `^a = b` shows the syntax for pattern matching against a variable's value instead of performing assignment. Pattern matching is used throughout Elixir programs for destructuring assignment, control flow, function invocation, and simple failure modes where a program is expected to crash unless a specific pattern is returned. 24 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator/aggregator.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.Aggregator do 2 | use GenServer 3 | alias TweetAggregator.Search.Client.Status 4 | 5 | def become_leader do 6 | start_link 7 | end 8 | 9 | def leader_pid do 10 | :global.whereis_name :aggregator 11 | end 12 | 13 | def has_leader?, do: leader_pid !== :undefined 14 | 15 | def start_link do 16 | :gen_server.start_link({:global, :aggregator}, __MODULE__, [], []) 17 | end 18 | 19 | def init(_) do 20 | {:ok, []} 21 | end 22 | 23 | def handle_cast({:push, server_name, status}, statuses) do 24 | log(server_name, status) 25 | {:noreply, [status | statuses]} 26 | end 27 | 28 | def push(server_name, status) do 29 | :gen_server.cast {:global, :aggregator}, {:push, server_name, status} 30 | end 31 | 32 | defp log(server_name, %Status{text: text, username: username}) do 33 | IO.puts """ 34 | >> #{server_name} 35 | @#{username}: #{text} 36 | """ 37 | end 38 | end 39 | 40 | -------------------------------------------------------------------------------- /advanced/src/genserver_stack_example/lib/stack/server.ex: -------------------------------------------------------------------------------- 1 | defmodule Stack.Server do 2 | use GenServer.Behaviour 3 | 4 | def start_link(initial_stack) do 5 | :gen_server.start_link {:global, :stack_server}, __MODULE__, initial_stack, [] 6 | end 7 | 8 | def init(initial_stack) do 9 | {:ok, initial_stack} 10 | end 11 | 12 | def handle_call(:pop, _from, []) do 13 | IO.puts "Pop empty" 14 | {:reply, nil, []} 15 | end 16 | 17 | def handle_call(:pop, from, [head | tail]) do 18 | IO.puts "Pop from #{inspect from} with #{head}" 19 | {:reply, head, tail} 20 | end 21 | 22 | def handle_cast({:push, value}, stack) do 23 | IO.puts "Pushing #{value}" 24 | {:noreply, [value|stack]} 25 | end 26 | end 27 | 28 | 29 | defmodule Stack.Client do 30 | 31 | def start(initial_stack \\ []) do 32 | Stack.Server.start_link(initial_stack) 33 | end 34 | 35 | def push(value) do 36 | :gen_server.cast {:global, :stack_server}, {:push, value} 37 | end 38 | 39 | def pop do 40 | :gen_server.call {:global, :stack_server}, :pop 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/test/gate_keeper_test.exs: -------------------------------------------------------------------------------- 1 | defmodule GateKeeperTest do 2 | use ExUnit.Case 3 | alias TweetAggregator.GateKeeper 4 | 5 | setup do 6 | if GateKeeper.has_leader?, do: Process.exit(GateKeeper.leader_pid, :kill) 7 | GateKeeper.become_leader 8 | :ok 9 | end 10 | 11 | test "#leader_pid" do 12 | assert GateKeeper.has_leader? 13 | end 14 | 15 | test "#access_token" do 16 | System.put_env "TWEET_ACCESS_TOKEN", "the access_token" 17 | assert GateKeeper.access_token == "the access_token" 18 | end 19 | 20 | test "#access_token_secret" do 21 | System.put_env "TWEET_ACCESS_TOKEN_SECRET", "the access_token_secret" 22 | assert GateKeeper.access_token_secret == "the access_token_secret" 23 | end 24 | 25 | test "#consumer_key" do 26 | System.put_env "TWEET_CONSUMER_KEY", "the consumer_key" 27 | assert GateKeeper.consumer_key == "the consumer_key" 28 | end 29 | 30 | test "#consumer_secret" do 31 | System.put_env "TWEET_CONSUMER_SECRET", "the consumer_secret" 32 | assert GateKeeper.consumer_secret == "the consumer_secret" 33 | end 34 | end 35 | 36 | -------------------------------------------------------------------------------- /advanced/protocols.md: -------------------------------------------------------------------------------- 1 | # Protocols 2 | 3 | Protocols provide ad-hoc polymorphism. This pattern allows library consumers 4 | to implement protocols against third party modules. 5 | 6 | ```elixir 7 | defimpl String.Chars, for: Status do 8 | def to_string(status = %Status{mentions: []}) do 9 | "#{status.username}: #{status.text}" 10 | end 11 | def to_string(status) do 12 | """ 13 | #{status.username}': #{status.text} 14 | mentions: #{inspect status.mentions} 15 | """ 16 | end 17 | end 18 | 19 | 20 | 21 | iex(2)> status = %Status{username: "chris_mccord", text: "my status"} 22 | %Status{id: nil, text: "my status", username: nil, hash_tags: [], 23 | mentions: []} 24 | 25 | iex(4)> IO.puts status 26 | chrismccord: my status 27 | :ok 28 | iex(5)> 29 | ``` 30 | 31 | ### Presence Protocol 32 | 33 | ```elixir 34 | defprotocol Present do 35 | def present?(data) 36 | end 37 | 38 | defimpl Present, for: [Integer, Float] do 39 | def present?(_), do: true 40 | end 41 | 42 | defimpl Present, for: List do 43 | def present?([]), do: false 44 | def present?(_), do: true 45 | end 46 | 47 | defimpl Present, for: Atom do 48 | def present?(false), do: false 49 | def present?(nil), do: false 50 | def present?(_), do: true 51 | end 52 | 53 | defimpl Present, for: BitString do 54 | def present?(string), do: String.length(string) > 0 55 | end 56 | ``` 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator/search/server.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.Search.Server do 2 | use GenServer 3 | alias TweetAggregator.Search.Client 4 | alias TweetAggregator.Search.Client.Query 5 | 6 | @poll_every_ms 10000 7 | 8 | def start_link(server_name, query) do 9 | :gen_server.start_link({:local, server_name}, __MODULE__, [query], []) 10 | end 11 | 12 | def init([query]) do 13 | Process.flag(:trap_exit, true) 14 | {:ok, timer} = :timer.send_interval(@poll_every_ms, :poll) 15 | {:ok, {timer, query}} 16 | end 17 | 18 | def handle_info(:poll, {timer, query}) do 19 | {:ok, statuses} = Client.search(query.keywords, query.options) 20 | if new_results?(statuses, query) do 21 | send query.subscriber, {:results, statuses} 22 | {:noreply, {timer, record_seen_ids(statuses, query)}} 23 | else 24 | IO.puts "Client: No new results" 25 | {:noreply, {timer, query}} 26 | end 27 | end 28 | 29 | def record_seen_ids(statuses, query) do 30 | %{query | seen_ids: (query.seen_ids ++ seen_ids(statuses))} 31 | end 32 | 33 | def new_results?(statuses, query) do 34 | !Enum.find(seen_ids(statuses), fn id -> id in query.seen_ids end) 35 | end 36 | 37 | def seen_ids(statuses) do 38 | statuses |> Enum.map(&(&1.id)) 39 | end 40 | 41 | def terminate(:shutdown, {timer, query}) do 42 | :timer.cancel(timer) 43 | Process.exit query.subscriber, :kill 44 | :ok 45 | end 46 | end 47 | 48 | -------------------------------------------------------------------------------- /advanced/maps.md: -------------------------------------------------------------------------------- 1 | # Maps & Structs 2 | 3 | Maps are key/value stores and synonymous with hashes or dictionaries in other languages. Maps support powerful pattern matching and upsert operations and are defined with the `%{}` syntax. 4 | 5 | ```elixir 6 | iex(4)> map = %{name: "elixir", age: 3, parent: "erlang"} 7 | %{age: 3, name: "elixir", parent: "erlang"} 8 | 9 | iex(5)> map[:name] 10 | "elixir" 11 | 12 | iex(6)> %{name: name} = map 13 | %{age: 3, name: "elixir", parent: "erlang"} 14 | 15 | iex(8)> %{age: age} = map 16 | %{age: 3, name: "elixir", parent: "erlang"} 17 | iex(9)> age 18 | 3 19 | 20 | iex(10)> map = %{map | age: 4} 21 | %{age: 4, name: "elixir", parent: "erlang"} 22 | 23 | iex(11)> map = %{map | age: 4, new_key: "new val"} 24 | ** (ArgumentError) argument error 25 | (stdlib) :maps.update(:new_key, "new val", %{age: 4, name: "elixir", parent: "erlang"}) 26 | ``` 27 | 28 | Structs are tagged maps used for polymorphic dispatch, pattern matching, and replace the now deprecated Records. 29 | 30 | ```elixir 31 | iex(1)> 32 | defmodule Status do 33 | defstruct id: nil, text: "", username: nil, hash_tags: [], mentions: [] 34 | end 35 | 36 | iex(2)> status = %Status{text: "All Aboard!"} 37 | %Status{id: nil, text: "All Aboard!", username: nil, hash_tags: [], 38 | mentions: []} 39 | 40 | iex(3)> status.text 41 | "All Aboard!" 42 | 43 | iex(4)> status = %Status{status | text: "RT All Aboard!"} 44 | %Status{id: nil, text: "RT All Aboard!", username: nil, hash_tags: [], 45 | mentions: []} 46 | iex(5)> status.text 47 | "RT All Aboard!" 48 | 49 | iex(6)> %Status{status | text: "@elixir-lang rocks!", username: "chris_mccord"} 50 | Status{id: nil, text: "@elixir-lang rocks!", username: "chris_mccord", 51 | hash_tags: [], mentions: []} 52 | ``` 53 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/README.md: -------------------------------------------------------------------------------- 1 | # TweetAggregator 2 | 3 | Distributed Tweet Aggregator 4 | 5 | 6 | ## Nodes 7 | 8 | - GateKeeper 9 | - Aggregator 10 | - Search.Client 11 | 12 | *Note:* Erlang/Elixir communicates over port 4369 by default. On locked down networks like public wifi run 13 | ``` 14 | epmd -port 80 -daemon 15 | ``` 16 | 17 | to tell Erlang/Elixir to communicate over port 80. 18 | 19 | ## Example Usage 20 | 21 | ### 1. Export required environment vairables for GateKeeper to make OAuth requests 22 | 23 | ```bash 24 | $ touch .env 25 | 26 | # .env 27 | export TWEET_CONSUMER_KEY=... 28 | export TWEET_CONSUMER_SECRET=... 29 | export TWEET_ACCESS_TOKEN=... 30 | export TWEET_ACCESS_TOKEN_SECRET=... 31 | 32 | $ source .env 33 | ``` 34 | 35 | 36 | ### 2. Start GateKeeper node 37 | 38 | ```bash 39 | $ iex --name gatekeeper@127.0.0.1 --cookie foo -S mix 40 | iex(gatekeeper@127.0.0.1)1> TweetAggregator.GateKeeper.become_leader 41 | :yes 42 | ``` 43 | 44 | ### 2. Start Aggregator node 45 | 46 | ```bash 47 | $ iex --name aggregator@127.0.0.1 --cookie foo -S mix 48 | iex(aggregator@127.0.0.1)1> TweetAggregator.Aggregator.become_leader 49 | :yes 50 | iex(aggregator@127.0.0.1)1> Node.connect :"gatekeeper@127.0.0.1" 51 | true 52 | ``` 53 | 54 | ### 3. Start client search node(s) 55 | 56 | ```bash 57 | $ iex --name client1@127.0.0.1 --cookie foo -S mix 58 | iex(client1@127.0.0.1)1> Node.connect :"aggregator@127.0.0.1" 59 | true 60 | NodeMonitor: aggregator@127.0.0.1 joined 61 | 62 | iex(client1@127.0.0.1)2> TweetAggregator.Search.Client.poll ["elixir"] 63 | New results 64 | Client: Got 1 result(s) 65 | ``` 66 | 67 | ### 4. Watch Aggregator notifications on Aggregator node 68 | 69 | ``` 70 | >> client1 71 | @noctarius2k: Elixir - The Ruby like Erlang VM Language http://t.co/FzCeytAv5t 72 | ``` 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator/gate_keeper.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.GateKeeper do 2 | @moduledoc """ 3 | GateKeeper allows Nodes to retrieve credentials from a 4 | leader node containing environment variables for oauth signing 5 | 6 | The follow Twitter OAuth environment variables are held by the GateKeeper: 7 | 8 | - TWEET_CONSUMER_KEY 9 | - TWEET_CONSUMER_SECRET 10 | - TWEET_ACCESS_TOKEN 11 | - TWEET_ACCESS_TOKEN_SECRET 12 | 13 | A single node must register as the leader to make API calls: 14 | 15 | ## Examples 16 | 17 | Leader Node: 18 | $ export TWEET_ACCESS_TOKEN="foo" 19 | iex> TweetAggregator.GateKeeper.register_as_leader 20 | 21 | Remote Client Node 22 | iex> TweetAggregator.GateKeeper.access_token 23 | "foo" 24 | 25 | """ 26 | 27 | @env_vars [:access_token, :access_token_secret, :consumer_key, :consumer_secret] 28 | 29 | def become_leader do 30 | :global.register_name :gate_keeper, spawn(fn -> listen end) 31 | end 32 | 33 | def leader_pid do 34 | :global.whereis_name(:gate_keeper) 35 | end 36 | 37 | def has_leader?, do: :global.whereis_name(:gate_keeper) !== :undefined 38 | 39 | def access_token, do: get(:access_token) 40 | def access_token_secret, do: get(:access_token_secret) 41 | def consumer_key, do: get(:consumer_key) 42 | def consumer_secret, do: get(:consumer_secret) 43 | 44 | defp listen do 45 | receive do 46 | {sender, env_var} when env_var in @env_vars -> 47 | send sender, System.get_env("TWEET_#{String.upcase(to_string(env_var))}") 48 | {sender, _} -> send(sender, nil) 49 | end 50 | listen 51 | end 52 | 53 | defp get(env_var) do 54 | send leader_pid, {self, env_var} 55 | receive do 56 | env_var -> env_var 57 | end 58 | end 59 | end 60 | 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # All Aboard The Elixir Express! 2 | Elixir provides the joy and productivity of Ruby with the concurrency and fault-tolerance of Erlang. Together, we'll take a guided tour through the language, going from the very basics to macros and distributed programming. Along the way, we'll see how Elixir embraces concurrency and how we can construct self-healing programs that restart automatically on failure. Attendees should leave with a great head-start into Elixir, some minor language envy, and a strong desire to continue exploration. 3 | 4 | ## What is Erlang? 5 | 6 | Erlang is a functional language with focuses on concurrency and fault tolerance. It first appeared in 1986 as part of Ericsson's quest to build fault tolerant, scalable telecommunication systems. It was open-sourced in 1998 and has gone on to power large portions of the worlds telecom systems, in addition to some of the most popular online services. 7 | 8 | ## Why Elixir? 9 | 10 | Elixir is a language built on top of the Erlang VM. Elixir exists with the goal to build concurrent, distributed, fault-tolerant systems with productive and powerful language features. Mainstream languages were constructed around limited CPUs, threading, and shared-everything state. This concurrency model can become fragile and can make concurrent programs difficult to reason about. Other languages skirt these issues by running on a single CPU with multiple processes to achieve concurrency; however, as Moore's Law pushes us towards increasing multicore CPUs, this approach becomes unmanageable. Elixir's immutable state, Actors, and processes produce a concurrency model that is easy to reason about and allows code to be written distributively without extra fanfare. Additionally, by building on top of Erlang, Elixir can take advantage of all the libraries and advantages that the Erlang ecosystem has to offer. 11 | -------------------------------------------------------------------------------- /basics/01_system_setup.md: -------------------------------------------------------------------------------- 1 | # 1 System Setup 2 | 3 | There is no substitute for learning by tinkering. Getting Elixir set up on most platforms is a snap, and we should be quickly on our way. 4 | 5 | The only prerequisite for Elixir is Erlang, version 17.0 or later. Official [precompiled packages](https://www.erlang-solutions.com/downloads/download-erlang-otp) 6 | are available for most platforms. 7 | 8 | To check your installed erlang version: 9 | 10 | ```bash 11 | $ erl 12 | Erlang/OTP 17 [erts-6.0] ... 13 | ``` 14 | 15 | ## Mac OSX 16 | 17 | Install via [homebrew](http://brew.sh/) 18 | 19 | ```bash 20 | $ brew update 21 | $ brew install erlang --devel 22 | $ brew install elixir 23 | ``` 24 | 25 | 26 | ## Linux 27 | Install Elixir with your favorite package manager 28 | 29 | ### Fedora 17+ and Fedora Rawhide 30 | ```bash 31 | $ sudo yum -y install elixir 32 | ``` 33 | 34 | ### Arch Linux (on AUR) 35 | ```bash 36 | $ yaourt -S elixir 37 | ``` 38 | 39 | ### openSUSE (and SLES 11 SP3+) 40 | ```bash 41 | $ zypper ar -f obs://devel:languages:erlang/ erlang 42 | $ zypper in elixir 43 | ``` 44 | 45 | ### Gentoo 46 | ```bash 47 | $ emerge --ask dev-lang/elixir 48 | ``` 49 | 50 | ## Windows 51 | 52 | Install Erlang (R16B02) from the official [precompiled packages](https://www.erlang-solutions.com/downloads/download-erlang-otp). 53 | 54 | Install Elixir through [Chocolatey](http://chocolatey.org/) 55 | 56 | ``` 57 | > cinst elixir 58 | ``` 59 | 60 | ## Test your setup 61 | 62 | Elixir ships with three executables, `iex`, `elixir`, and `elixirc`. 63 | Fire up `iex` to run the Elixir shell. In iex, or "Iteractive Elixir," we can 64 | execute any valid Elixir expression and see the evaluated result. 65 | 66 | ```elixir 67 | $ iex 68 | iex(1)> IO.puts "Hello Elixir!" 69 | Hello Elixir! 70 | :ok 71 | iex(2)> 72 | ``` 73 | 74 | 75 | Now you have everything you need to get started programming Elixir. 76 | -------------------------------------------------------------------------------- /basics/02_getting_started.md: -------------------------------------------------------------------------------- 1 | # 2 Getting Started 2 | The easiest way to get started is firing up `iex`, or *Interactive Elixir*, to experiment with code live in the Elixir shell. The code examples in this chapter were entered directly in iex and you are encouraged to follow along in your own session. 3 | 4 | ### Terminology 5 | 6 | - *term* - An element of any data type and is commonly referred to as such in documentation and code examples 7 | - *literal* - A value within source code representing a type 8 | - *bitstring* - Used to store an area of untyped memory 9 | - *binary* - Bitstrings containing a number of bits evenly divisible by eight and commonly used to store UTF-8 encoded strings 10 | - *arity* - The number of arguments a function accepts 11 | 12 | ### Everything is an expression 13 | 14 | ```elixir 15 | iex(1)> result = if 1 == 1 do 16 | ...(1)> "correct" 17 | ...(1)> else 18 | ...(1)> "incorrect" 19 | ...(1)> end 20 | "correct" 21 | iex(2)> result 22 | "correct" 23 | ``` 24 | 25 | ### Immutability 26 | Variables are immutable. Once assigned, they cannot be changed. Instead of operating on information hiding and mutating shared state, Elixir programs are constructed around data transformation and message passing among isolated processes. This follows the [*Actor Model*](http://en.wikipedia.org/wiki/Actor_model) of concurrency. 27 | 28 | ### Parenthesis are optional 29 | Parenthesis are optional as long as their absence does not introduce ambiguity. 30 | 31 | ### Documentation is first class 32 | Documentation in Elixir is written in [Markdown](http://en.wikipedia.org/wiki/Markdown) and compiled into the program as metadata. This allows formatted documentation to be brought up on-demand through `iex`. 33 | 34 | ### Coexistence with the Erlang ecosystem 35 | Elixir programs compile to the Erlang Abstract Format, or byte-code for Erlang's BEAM Virtual Machine. Erlang modules can be called from Elixir and vice-versa. Calling Erlang modules from Elixir simply requires using an *atom* by prefixing the module name with a semicolon. 36 | 37 | ```elixir 38 | iex(7)> IO.puts "Print from Elixir" 39 | Print from Elixir 40 | :ok 41 | iex(8)> :io.fwrite "Print from Erlang~n" 42 | Print from Erlang 43 | :ok 44 | iex(1)> :math.pi 45 | 3.141592653589793 46 | iex(2)> :erlang.time 47 | {19, 41, 20} 48 | ``` 49 | -------------------------------------------------------------------------------- /advanced/holding_state.md: -------------------------------------------------------------------------------- 1 | # Managing state in an immutable language 2 | 3 | This application shows how to manage and hold state via "homegrown" processes and how OTP conventions have been 4 | built up around these ideas. 5 | 6 | While Elixir is immutable, state can be held in processes that continuously recurse on themselves. Processes can then accept messages from other processes to return or change their current state. 7 | 8 | Example: 9 | 10 | ```elixir 11 | defmodule Stack.CustomServer do 12 | 13 | def start(initial_stack) do 14 | spawn_link fn -> 15 | :global.register_name :custom_server, self 16 | listen initial_stack 17 | end 18 | end 19 | 20 | def listen(stack) do 21 | receive do 22 | {sender, :pop} -> handle_pop(sender, stack) 23 | {sender, :push, value} -> listen([value|stack]) 24 | end 25 | end 26 | 27 | def handle_pop(sender, []) do 28 | send sender, nil 29 | listen [] 30 | end 31 | def handle_pop(sender, stack) do 32 | send sender, hd(stack) 33 | listen tl(stack) 34 | end 35 | end 36 | ``` 37 | 38 | "Starting" the Custom stack server involves spawning a process that continually recurses on `listen` with the stack's current state. To push a value onto the stack, the process listens for a message containing the sender's pid, and a value `{sender, :push, value}` and then recurses back on itself with the value placed in the head of the stack. Similarly, to pop a value off the stack, the process listens for `{sender, :pop}` and sends the top of the stack as a message back to the sender, then recurses back on itself with the popped value removed. 39 | 40 | ## Erlang/OTP Conventions 41 | The OTP library brings tried and true conventions to holding state, process supervision, and message passing. For almost all cases where state needs to be held, it should be placed in an OTP gen_server. 42 | 43 | ## Homegrown Server Example 44 | ```elixir 45 | $ iex -S mix 46 | iex(1)> Stack.CustomServer.start [1, 2, 3] 47 | #PID<0.101.0> 48 | iex(2)> Stack.CustomClient.pop 49 | 1 50 | iex(3)> Stack.CustomClient.pop 51 | 2 52 | iex(4)> Stack.CustomClient.pop 53 | 3 54 | ``` 55 | 56 | ## Using OTP gen_server 57 | 58 | ```elixir 59 | $ iex -S mix 60 | iex(1)> Stack.Client.start [1, 2, 3] 61 | {:ok, #PID<0.74.0>} 62 | iex(2)> Stack.Client.pop 63 | 1 64 | iex(3)> Stack.Client.pop 65 | 2 66 | iex(4)> Stack.Client.pop 67 | 3 68 | iex(5)> Stack.Client.pop 69 | nil 70 | 71 | 72 | ``` 73 | -------------------------------------------------------------------------------- /advanced/src/genserver_stack_example/README.md: -------------------------------------------------------------------------------- 1 | # Managing state in an immutable language 2 | 3 | This application shows how to manage and hold state via "homegrown" processes and how OTP conventions have been 4 | built up around these ideas. 5 | 6 | While Elixir is immutable, state can be held in processes that continously recurse on themselves. Processes can then accept messages from other processes to return or change their current state. 7 | 8 | Example: 9 | 10 | ```elixir 11 | defmodule Stack.CustomServer do 12 | 13 | def start(initial_stack) do 14 | spawn_link fn -> 15 | :global.register_name :custom_server, self 16 | listen initial_stack 17 | end 18 | end 19 | 20 | def listen(stack) do 21 | receive do 22 | {sender, :pop} -> handle_pop(sender, stack) 23 | {sender, :push, value} -> listen([value|stack]) 24 | end 25 | end 26 | 27 | def handle_pop(sender, []) do 28 | send sender, nil 29 | listen [] 30 | end 31 | def handle_pop(sender, stack) do 32 | send sender, hd(stack) 33 | listen tl(stack) 34 | end 35 | end 36 | ``` 37 | 38 | "Starting" the Custom stack server involves spawning a process that continually recurses on `listen` with the stack's current state. To push a value onto the stack, the process listens for a message containing the sender's pid, and a value `{sender, :push, value}` and then recurses back on itself with the value placed in the head of the stack. Similarly, to pop a value off the stack, the process listens for `{sender, :pop}` and sends the top of the stack as a message gack to the sender, then recurses back on itself with the popped value removed. 39 | 40 | ## Erlang/OTP Conventions 41 | The OTP library brings tried and true conventions to holding state, process supervisionm, and message passing. For almost all cases where state needs to be held, it should be placed in an OTP gen_server. 42 | 43 | ## Homegrown Server Example 44 | ```elixir 45 | $ iex -S mix 46 | iex(1)> Stack.CustomServer.start [1, 2, 3] 47 | #PID<0.101.0> 48 | iex(2)> Stack.CustomClient.pop 49 | 1 50 | iex(3)> Stack.CustomClient.pop 51 | 2 52 | iex(4)> Stack.CustomClient.pop 53 | 3 54 | ``` 55 | 56 | ## Using OTP gen_server 57 | 58 | ```elixir 59 | $ iex -S mix 60 | iex(1)> Stack.Client.start [1, 2, 3] 61 | {:ok, #PID<0.74.0>} 62 | iex(2)> Stack.Client.pop 63 | 1 64 | iex(3)> Stack.Client.pop 65 | 2 66 | iex(4)> Stack.Client.pop 67 | 3 68 | iex(5)> Stack.Client.pop 69 | nil 70 | 71 | 72 | ``` 73 | -------------------------------------------------------------------------------- /basics/06_pipeline_operator.md: -------------------------------------------------------------------------------- 1 | ## Pipeline Operator 2 | One of the most simple, yet effective features in Elixir is the *pipeline operator*. The pipeline operator solves the issue many functional languages face when composing a series of transformations where the output from one function needs passed as the input to another. This requires solutions to be read in reverse to understand the actions being performed, hampering readability and obscuring the true intent of the code. Elixir elegantly solves this problem by allowing the output of a function to be *piped* as the first parameter to the input of another. At compile time, the functional hierarchy is transformed into the nested, "backward" variant that would otherwise be required. 3 | 4 | ```elixir 5 | iex(1)> "Hello" |> IO.puts 6 | Hello 7 | :ok 8 | iex(2)> [3, 6, 9] |> Enum.map(fn x -> x * 2 end) |> Enum.at(2) 9 | 18 10 | ``` 11 | 12 | To grasp the full utility the pipeline provides, consider a module that fetches new messages from an API and saves the results to a database. The sequence of steps would be: 13 | 14 | - Find the account by authorized user token 15 | - Fetch new messages from API with authorized account 16 | - Convert JSON response to keyword list of messages 17 | - Save all new messages to the database 18 | 19 | Without Pipeline: 20 | ```elixir 21 | defmodule MessageService do 22 | ... 23 | def import_new_messages(user_token) do 24 | Enum.each( 25 | parse_json_to_message_list( 26 | fetch(find_user_by_token(user_token), "/messages/unread") 27 | ), &save_message(&1)) 28 | end 29 | ... 30 | end 31 | ``` 32 | 33 | Proper naming and indentation help the readability of the previous block, but its intent is not immediately obvious without first taking a moment to decompose the steps from the inside out to grasp an understanding of the data flow. 34 | 35 | Now consider this series of steps with the pipeline operator: 36 | 37 | With Pipeline 38 | ```elixir 39 | defmodule MessageService do 40 | ... 41 | def import_new_messages(user_token) do 42 | user_token 43 | |> find_user_by_token 44 | |> fetch("/messages/unread") 45 | |> parse_json_to_message_list 46 | |> Enum.each(&save_message(&1)) 47 | end 48 | ... 49 | end 50 | ``` 51 | 52 | Piping the result of each step as the first argument to the next allows allows programs to be written as a series of transformations that any reader would immediately be able to read and comprehend without expending extra effort to unwrap the functions, as in the first solution. 53 | 54 | The Elixir standard library focuses on placing the subject of the function as the first argument, aiding and encouraging the natural use of pipelines. 55 | -------------------------------------------------------------------------------- /otp/src/tweet_aggregator/lib/tweet_aggregator/search/client.ex: -------------------------------------------------------------------------------- 1 | defmodule TweetAggregator.Search.Client.Status do 2 | defstruct id: nil, text: "", username: nil, hash_tags: [], mentions: [] 3 | 4 | end 5 | defmodule TweetAggregator.Search.Client.Query do 6 | defstruct subscriber: nil, keywords: [], options: [], seen_ids: [] 7 | end 8 | defmodule TweetAggregator.Search.Client do 9 | alias TweetAggregator.Search.Client.Status 10 | alias TweetAggregator.Search.Client.Query 11 | alias TweetAggregator.GateKeeper 12 | alias TweetAggregator.Aggregator 13 | alias TweetAggregator.Search.Supervisor 14 | 15 | @base_url "https://api.twitter.com/1.1/" 16 | @limit 1 17 | 18 | def server_name, do: :"#{node}_search_server" 19 | def server_pid, do: Process.whereis(server_name) 20 | 21 | def poll(keywords, options \\ []) do 22 | Supervisor.start_link(server_name, %Query{ 23 | subscriber: spawn(fn -> do_poll end), 24 | keywords: keywords, 25 | options: options, 26 | }) 27 | send server_pid, :poll 28 | end 29 | defp do_poll do 30 | receive do 31 | {:results, results} -> 32 | IO.puts "Client: Notifying Aggregator of #{Enum.count results} result(s)" 33 | Enum.each results, &Aggregator.push(server_name, &1) 34 | end 35 | do_poll 36 | end 37 | 38 | def stop do 39 | Supervisor.stop(server_name) 40 | end 41 | 42 | def search(keywords, options \\ []) do 43 | count = Keyword.get options, :count, @limit 44 | 45 | get "search/tweets.json", count: count, 46 | q: keywords_to_query_param(keywords), 47 | result_type: 'recent' 48 | end 49 | 50 | def get(path, params) do 51 | url = String.to_char_list process_url("search/tweets.json") 52 | access_token = String.to_char_list GateKeeper.access_token 53 | access_secret = String.to_char_list GateKeeper.access_token_secret 54 | consumer_key = String.to_char_list GateKeeper.consumer_key 55 | consumer_secret = String.to_char_list GateKeeper.consumer_secret 56 | consumer = {consumer_key, consumer_secret, :hmac_sha1} 57 | 58 | case :oauth.get(url, params, consumer, access_token, access_secret) do 59 | {:ok, response} -> {:ok, parse_response(response)} 60 | {:error, reason} -> {:error, reason} 61 | end 62 | end 63 | 64 | def parse_response(response) do 65 | Enum.map to_json(response)["statuses"], fn status -> 66 | status = status |> Enum.into HashDict.new 67 | user = status["user"] |> Enum.into HashDict.new 68 | %Status{id: status["id"], 69 | text: status["text"], 70 | username: user["screen_name"]} 71 | end 72 | end 73 | 74 | defp to_json(response) do 75 | response 76 | |> elem(2) 77 | |> to_string 78 | |> :jsx.decode 79 | |> Enum.into HashDict.new 80 | end 81 | 82 | defp keywords_to_query_param(keywords) do 83 | keywords |> Enum.join(" OR ") |> String.to_char_list 84 | end 85 | 86 | defp process_url(url), do: @base_url <> url 87 | end 88 | 89 | -------------------------------------------------------------------------------- /basics/08_macros.md: -------------------------------------------------------------------------------- 1 | # Macros 2 | 3 | Macros give programmers the power to write code that writes code. This power eliminates boilerplate, allows Domain Specific Language abstractions, and provides the freedom to extend the language. Much of Elixir itself is implemented as macros. Internally, `if` is just a macro that accepts two arguments. The first argument is a condition followed by a keyword list of options containing `do:`, and optionally, `else:`. You can see this in action by defining your own `unless` macro, named `lest`. Macros must be defined within a module, so we'll create our first module, named `Condition`. 4 | 5 | ```elixir 6 | defmodule Condition do 7 | defmacro lest(expression, do: block) do 8 | quote do 9 | if !unquote(expression), do: unquote(block) 10 | end 11 | end 12 | end 13 | 14 | {:module, Condition,... 15 | iex(12)> require Condition 16 | nil 17 | 18 | iex(13)> Condition.lest 2 == 5, do: "not equal" 19 | "not equal" 20 | :ok 21 | 22 | iex(14)> Condition.lest 5 == 5, do: "not equal" 23 | nil 24 | ``` 25 | 26 | The `lest` macro accepts an expression as the first argument, followed by the keyword list of options. It is important to realize that when `2 == 5` was passed to `lest`, the macro received the *representation* of `2 == 5`, not the result. This allows the macro to manipulate and augment the representation of the code before it is compiled and evaluated. `unquote` is used to evaluate the arguments passed in before returning the augmented representation back to the call point. You can use `quote` in `iex` to see the internal representation of any expression: 27 | 28 | ```elixir 29 | iex(1)> quote do: 2 == 5 30 | {:==, [context: Elixir, import: Kernel], [2, 5]} 31 | 32 | iex(2)> quote do: (5 * 2) + 7 33 | {:+, [context: Elixir, import: Kernel], 34 | [{:*, [context: Elixir, import: Kernel], [5, 2]}, 7]} 35 | 36 | iex(3)> quote do: total = 88.00 37 | {:=, [], [{:total, [], Elixir}, 88.0]} 38 | ``` 39 | 40 | As you can see, Elixir code is represented by a hierarchy of three element tuples, containing an atom, metadata, and arguments. Having access to this simple structure in Elixir's own data-types allows for introspection and code generation techniques that are easy to write and a joy to consume. 41 | 42 | 43 | ## Examples 44 | [Phoenix Web Framework](https://github.com/phoenixframework/phoenix) - Router 45 | ```elixir 46 | defmodule MyApp.Router do 47 | use Phoenix.Router 48 | 49 | plug Plug.Static, at: "/static", from: :your_app 50 | 51 | get "/pages/:page", Controllers.Pages, :show, as: :page 52 | get "/files/*path", Controllers.Files, :show 53 | 54 | resources "users", Controllers.Users do 55 | resources "comments", Controllers.Comments 56 | end 57 | 58 | scope path: "admin", alias: Controllers.Admin, helper: "admin" do 59 | resources "users", Users 60 | end 61 | end 62 | ``` 63 | 64 | ExUnit Test Framework 65 | ```elixir 66 | defmodule Phoenix.Router.RoutingTest do 67 | use ExUnit.Case 68 | 69 | test "limit resource by passing :except option" do 70 | conn = simulate_request(Router, :delete, "posts/2") 71 | assert conn.status == 404 72 | conn = simulate_request(Router, :get, "posts/new") 73 | assert conn.status == 200 74 | end 75 | 76 | test "named route builds _path url helper" do 77 | assert Router.user_path(id: 88) == "/users/88" 78 | end 79 | end 80 | ``` 81 | -------------------------------------------------------------------------------- /advanced/processes.md: -------------------------------------------------------------------------------- 1 | # Processes - Elixir's Unit of Concurrency 2 | Elixir processes are fast and lightweight units of concurrency. Not to be confused with OS processes, millions of them can be spawned on a single machine, and each are managed entirely by the Erlang VM. Processes live at the core of Elixir application architectures and can send and receive messages to other processes located locally, or remotely on another connected Node. 3 | 4 | ### spawn 5 | Spawn creates a new process and returns the Pid, or Process ID of the new process. Messages are sent to the processes using the `send/2` function. 6 | 7 | ### Mailboxes 8 | Processes all contain a *mailbox* where messages are passively kept until consumed via a `receive` block. `receive` processes message in the order received and allows messages to be pattern matched. A common pattern is to send a message to a process with a tuple containing `self` as the first element. This allows the receiving process to have a reference to message's "sender" and respond back to the sender Pid with its own response messages. 9 | 10 | ```elixir 11 | pid = spawn fn -> 12 | receive do 13 | {sender, :ping} -> 14 | IO.puts "Got ping" 15 | send sender, :pong 16 | end 17 | end 18 | 19 | send pid, {self, :ping} 20 | 21 | # Got ping 22 | 23 | receive do 24 | message -> IO.puts "Got #{message} back" 25 | end 26 | 27 | # Got pong back 28 | ``` 29 | 30 | `receive` blocks the current process until a message is received that matches a message clause. An `after` clause can optionally be provided to exit the receive loop if no messages are receive after a set amount of time. 31 | 32 | ```elixir 33 | receive do 34 | message -> IO.inspect message 35 | after 5000 -> 36 | IO.puts "Timeout, giving up" 37 | end 38 | ``` 39 | 40 | Results in... 41 | ```elixir 42 | # Timeout, giving up 43 | ``` 44 | 45 | ## spawn_link 46 | Similar to `spawn`, `spawn_link` creates a new process, but links the current and new process so that if one crashes, both processes terminate. Linking processes is essential to the Elixir and Erlang philosophy of letting programs crash instead of trying to rescue from errors. Since Elixir programs exist as a hierarchy of many processes, linking allows a predictable process dependency tree where failures in one process cascade down to all other dependent processes. 47 | 48 | ```elixir 49 | pid = spawn_link fn -> 50 | receive do 51 | :boom -> raise "boom!" 52 | end 53 | end 54 | 55 | send pid, :boom 56 | ``` 57 | Results in... 58 | ```elixir 59 | =ERROR REPORT==== 27-Dec-2013::16:49:14 === 60 | Error in process <0.64.0> with exit value: {{'Elixir.RuntimeError','__exception__',<<5 bytes>>},[{erlang,apply,2,[]}]} 61 | 62 | ** (EXIT from #PID<0.64.0>) {RuntimeError[message: "boom!"], [{:erlang, :apply, 2, []}]} 63 | ``` 64 | 65 | ```elixir 66 | pid = spawn fn -> 67 | receive do 68 | :boom -> raise "boom!" 69 | end 70 | end 71 | 72 | send pid, :boom 73 | ``` 74 | 75 | Results in... 76 | ```elixir 77 | =ERROR REPORT==== 27-Dec-2013::16:49:50 === 78 | Error in process <0.71.0> with exit value: {{'Elixir.RuntimeError','__exception__',<<5 bytes>>},[{erlang,apply,2,[]}]} 79 | 80 | iex(5)> 81 | ``` 82 | 83 | The first example above using `spawn_link`, we see the process termination cascade to our own iex session from the `** (EXIT from #PID<0.64.0>)` error. Our iex session stays alive because it is internally restarted by a process Supervisor. Supervisors are covered in the next section on OTP. 84 | 85 | 86 | ## Holding State 87 | Since Elixir is immutable, you may be wondering how state is held. Holding and mutating state can be performed by spawning a process that exposes its state via messages and infinitely recurses on itself with its current state. For example: 88 | 89 | ```elixir 90 | defmodule Counter do 91 | def start(initial_count) do 92 | spawn fn -> listen(initial_count) end 93 | end 94 | 95 | def listen(count) do 96 | receive do 97 | :inc -> listen(count + 1) 98 | {sender, :val} -> 99 | send sender, count 100 | listen(count) 101 | end 102 | end 103 | end 104 | {:module, Counter,... 105 | 106 | iex(8)> counter_pid = Counter.start(10) 107 | #PID<0.140.0> 108 | 109 | iex(9)> send counter_pid, :inc 110 | :inc 111 | iex(10)> send counter_pid, :inc 112 | :inc 113 | iex(11)> send counter_pid, :inc 114 | :inc 115 | iex(12)> send counter_pid, {self, :val} 116 | {#PID<0.40.0>, :val} 117 | 118 | iex(13)> receive do 119 | ...(13)> value -> value 120 | ...(13)> end 121 | 13 122 | ``` 123 | 124 | ## Registered Processes 125 | Pids can be registered under a name for easy lookup by other processes 126 | 127 | ```elixir 128 | iex(26)> pid = Counter.start 10 129 | iex(27)> Process.register pid, :count 130 | true 131 | iex(28)> Process.whereis(:count) == pid 132 | true 133 | iex(29)> send :count, :inc 134 | :inc 135 | iex(30)> receive do 136 | ...(30)> value -> value 137 | ...(30)> end 138 | 11 139 | ``` 140 | -------------------------------------------------------------------------------- /basics/05_control_flow.md: -------------------------------------------------------------------------------- 1 | # Control Flow 2 | 3 | ### Truthiness 4 | Only `false` and `nil` are falsey. All other values are considered truthy. 5 | 6 | ### if / unless 7 | Elixir sports the traditional `if` and `unless` keywords for control flow branching, but as we'll see, their use will be limited in favor of superior approaches. 8 | 9 | ```elixir 10 | iex(1)> saved = true 11 | true 12 | iex(2)> if saved, do: IO.puts("saved"), else: IO.puts("failed") 13 | saved 14 | :ok 15 | 16 | iex(3)> if saved do 17 | ...(3)> IO.puts "saved" 18 | ...(3)> else 19 | ...(3)> IO.puts "failed" 20 | ...(3)> end 21 | saved 22 | :ok 23 | 24 | iex(4)> unless saved do 25 | ...(4)> IO.puts "save failed" 26 | ...(4)> end 27 | nil 28 | ``` 29 | 30 | The first two `if` examples demonstrate Elixir's inline and expanded expression syntax. In fact, the expanded, multiline example is simply sugar for the the inline `do:` / `else:` syntax. 31 | 32 | ### cond 33 | For cases where nesting or chaining ifs would be required, `cond` can be used instead to list multiple expressions and evaluate the first truthy match. 34 | 35 | ```elixir 36 | iex(1)> temperature = 30 37 | 30 38 | iex(2)> cond do 39 | ...(2)> temperature >= 212 -> "boiling" 40 | ...(2)> temperature <= 32 -> "freezing" 41 | ...(2)> temperature <= -459.67 -> "absolute zero" 42 | ...(2)> end 43 | "freezing" 44 | ``` 45 | 46 | ### case 47 | `case` provides control flow based on pattern matching. Given an expression, case will match against each clause until the first pattern is matched. At least one pattern must be matched or `CaseClauseError` will be raised. Let's write a mini calculation parser to perform a few basic operations: 48 | 49 | ```elixir 50 | iex> 51 | calculate = fn expression -> 52 | case expression do 53 | {:+, num1, num2} -> num1 + num2 54 | {:-, num1, num2} -> num1 - num2 55 | {:*, num1, 0} -> 0 56 | {:*, num1, num2} -> num1 * num2 57 | {:/, num1, num2} -> num1 / num2 58 | end 59 | end 60 | #Function<6.17052888 in :erl_eval.expr/5> 61 | 62 | iex(2)> calculate.({:+, 8, 2}) 63 | 10 64 | iex(3)> calculate.({:*, 8, 0}) 65 | 0 66 | iex(4)> calculate.({:*, 8, 2}) 67 | 16 68 | iex(5)> calculate.({:^, 8, 2}) 69 | ** (CaseClauseError) no case clause matching: {:^, 8, 2} 70 | iex(5)> 71 | ``` 72 | 73 | The `calculate` function accepts a three element tuple containing an atom to represent the operation to perform followed by two numbers. `case` is used to pattern match against the operation, as well as bind the the `num1` and `num2` variables for the matched clause. 74 | 75 | An underscore in a match can serve as a "catch-all" clause: 76 | 77 | 78 | ```elixir 79 | iex> 80 | 81 | calculate = fn expression -> 82 | case expression do 83 | {:+, num1, num2} -> num1 + num2 84 | {:-, num1, num2} -> num1 - num2 85 | {:*, num1, num2} -> num1 * num2 86 | _ -> raise "Unable to parse #{inspect expression}" 87 | end 88 | end 89 | #Function<6.17052888 in :erl_eval.expr/5> 90 | 91 | iex(7)> calculate.({:/, 10, 2}) 92 | ** (RuntimeError) Unable to parse {:/, 10, 2} 93 | ``` 94 | 95 | We used an anonymous function to wrap the `case` expression for invocation convenience, but we could have accomplished a similar approach using only an anonymous function with multiple clauses: 96 | 97 | Pattern Matching - multiple function clauses 98 | ```elixir 99 | iex> 100 | calculate = fn 101 | {:+, num1, num2} -> num1 + num2 102 | {:-, num1, num2} -> num1 - num2 103 | {:*, num1, 0} -> 0 104 | {:*, num1, num2} -> num1 * num2 105 | end 106 | #Function<6.17052888 in :erl_eval.expr/5> 107 | 108 | iex(12)> calculate.({:*, 8, 0}) 109 | 0 110 | iex(13)> calculate.({:*, 2, 2}) 111 | 4 112 | ``` 113 | 114 | ## Guard Clauses 115 | *Guard Clauses* can be used to restrict a pattern from matching based on a condition or set of conditions. Consider an extension to our calculation parser where dividing by zero should never occur: 116 | 117 | ```elixir 118 | iex> 119 | calculate = fn expression -> 120 | case expression do 121 | {:+, num1, num2} -> num1 + num2 122 | {:-, num1, num2} -> num1 - num2 123 | {:*, num1, num2} -> num1 * num2 124 | {:/, num1, num2} when num2 != 0 -> num1 / num2 125 | end 126 | end 127 | #Function<6.17052888 in :erl_eval.expr/5> 128 | 129 | iex(8)> calculate.({:/, 10, 2}) 130 | 5.0 131 | iex(9)> calculate.({:/, 10, 0}) 132 | ** (CaseClauseError) no case clause matching: {:/, 10, 0} 133 | ``` 134 | 135 | The Virtual Machine supports a limited set of guard expressions: 136 | 137 | - comparison, boolean, and arithmetic operators 138 | - ==, !=, ===, !==, >, <, <=, >= 139 | - and, or, not, ! 140 | - +, -, *, / 141 | - examples 142 | - `def credit(balance, amt) when amt > 0` 143 | - `def debit(balance, amt) when amt > 0 and balance >= amt` 144 | 145 | 146 | - concatentation operators, providing the first term is a literal 147 | - `<>`, `++` 148 | 149 | 150 | - the `in` operator 151 | - examples 152 | - `def grade(letter) when letter in ["A", "B"]` 153 | 154 | 155 | - type checking functions: 156 | - is_atom/1 157 | - is_binary/1 158 | - is_bitstring/1 159 | - is_boolean/1 160 | - is_exception/1 161 | - is_float/1 162 | - is_function/1 163 | - is_function/2 164 | - is_integer/1 165 | - is_list/1 166 | - is_number/1 167 | - is_pid/1 168 | - is_port/1 169 | - is_record/1 170 | - is_record/2 171 | - is_reference/1 172 | - is_tuple/1 173 | 174 | 175 | - top-level functions: 176 | - abs(Number) 177 | - bit_size(Bitstring) 178 | - byte_size(Bitstring) 179 | - div(Number, Number) 180 | - elem(Tuple, n) 181 | - float(Term) 182 | - hd(List) 183 | - length(List) 184 | - node() 185 | - node(Pid|Ref|Port) 186 | - rem(Number, Number) 187 | - round(Number) 188 | - self() 189 | - size(Tuple|Bitstring) 190 | - tl(List) 191 | - trunc(Number) 192 | - tuple_size(Tuple) 193 | -------------------------------------------------------------------------------- /basics/07_modules.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | Modules are the main building blocks of Elixir programs. Modules can contain named functions, import functions from other modules, and use macros for powerful composition techniques. 4 | 5 | ## Liftoff! Your First Program 6 | Open up your favorite editor and create your first Elixir program: 7 | 8 | ```elixir 9 | defmodule Rocket do 10 | 11 | def start_launch_sequence do 12 | IO.puts "Liftoff in 10..." 13 | countdown(10) 14 | end 15 | 16 | defp countdown(0), do: blastoff 17 | defp countdown(seconds) do 18 | IO.puts seconds 19 | countdown(seconds - 1) 20 | end 21 | 22 | def blastoff do 23 | IO.puts "Liftoff!" 24 | end 25 | end 26 | 27 | Rocket.start_launch_sequence 28 | ``` 29 | 30 | We can compile and run our program in a single step using the `elixir` command: 31 | 32 | ```bash 33 | $ elixir rocket1.exs 34 | Liftoff in 10... 35 | 10 36 | 9 37 | 8 38 | 7 39 | 6 40 | 5 41 | 4 42 | 3 43 | 2 44 | 1 45 | Liftoff! 46 | ``` 47 | 48 | ### *Tip: Elixir files 49 | > Elixir files are named `.exs`, which stands for Elixir script, and `.ex` 50 | > are typically compiled to Erlang BEAM byte-code. For simple one off programs, sys-admin scripts etc. use `.exs`. 51 | 52 | You can also fire up iex in the same directory and use the `c` helper function 53 | 54 | ```elixir 55 | iex(1)> c "rocket1.exs" 56 | Liftoff in 10... 57 | ... 58 | ``` 59 | 60 | Publicly reachable functions are defined with the `def` keyword, while private functions use `defp`. It is common with Elixir code to group public functions with their private counterparts instead of lumping all public and private functions as separate groups in the source file. Attempting to call a `defp` function will result in an error: 61 | 62 | ```elixir 63 | iex(13)> Rocket.countdown(1) 64 | ** (UndefinedFunctionError) undefined function: Rocket.countdown/1 65 | Rocket.countdown(1) 66 | ``` 67 | 68 | ## Function Pattern Matching 69 | 70 | The `countdown` function gives the first glimpse of function pattern matching. Pattern matching allows multiple function clauses to be defined with the same name. The BEAM Virtual Machine will match against each definition by the order defined until the arguments match the defined pattern. `countdown(0)` serves as the termination of the recursive calls and has higher precedence over `countdown(seconds)` because it is defined first. Had `countdown(0)` been defined second, the program would never terminate because the first clause would always match. Pattern matching often removes the need for complex if-else branches and helps keep module functions short and clear. 71 | 72 | 73 | Let's up the sophistication of our Rocket module to allow the caller to specify a countdown when starting the launch: 74 | 75 | ```elixir 76 | defmodule Rocket do 77 | 78 | def start_launch_sequence(seconds \\ 10) do 79 | IO.puts "Liftoff in #{seconds}..." 80 | countdown(seconds) 81 | end 82 | 83 | defp countdown(0), do: blastoff 84 | defp countdown(seconds) do 85 | IO.puts seconds 86 | countdown(seconds - 1) 87 | end 88 | 89 | defp blastoff do 90 | IO.puts "Liftoff!" 91 | end 92 | end 93 | ``` 94 | 95 | Instead of compiling and invoking the program with `elixir`, we can use `iex` to compile, load, and experiment with our source files via the `c` helper function. 96 | 97 | ```elixir 98 | iex(1)> c "rocket2.ex" 99 | [Rocket] 100 | iex(2)> Rocket.start_launch_sequence(5) 101 | Liftoff in 5... 102 | 5 103 | 4 104 | 3 105 | 2 106 | 1 107 | Liftoff! 108 | :ok 109 | ``` 110 | 111 | Line 3 accepts an argument, with a default value of 10 denoted by the `\\` syntax. We can leave our `iex` session running and recompile and reload our program after each change by re-executing the `c` function. 112 | 113 | 114 | ## Guard Clauses 115 | There is a problem with the countdown implementation. If a caller passes a negative number, the recursive calls will never terminate. It could be tempting to simply wrap the function in an if clause or manually raising an error, but Elixir provides a better solution with *guard clauses*. 116 | 117 | ```elixir 118 | defmodule Rocket do 119 | 120 | def start_launch_sequence(seconds \\ 10) when seconds >= 0 do 121 | IO.puts "Liftoff in #{seconds}..." 122 | countdown(seconds) 123 | end 124 | 125 | defp countdown(0), do: blastoff 126 | defp countdown(seconds) do 127 | IO.puts seconds 128 | countdown(seconds - 1) 129 | end 130 | 131 | defp blastoff do 132 | IO.puts "Liftoff!" 133 | end 134 | end 135 | ``` 136 | 137 | Now attempting to invoke the countdown with a negative number raises a `FunctionClauseError` because the guard serves as a unique component of the function signature. 138 | 139 | ```elixir 140 | iex(2)> c "rocket3.ex" 141 | rocket3.ex:1: redefining module Rocket 142 | [Rocket] 143 | iex(3)> Rocket.start_launch_sequence(-1) 144 | ** (FunctionClauseError) no function clause matching in Rocket.start_launch_sequence/1 145 | rocket3.ex:3: Rocket.start_launch_sequence(-1) 146 | ``` 147 | 148 | ### Anonymous Function Guards 149 | 150 | In addition to named functions, guard clauses can also be used on anonymous functions and `case` expressions. 151 | 152 | ```elixir 153 | process_input = fn 154 | {:left, spaces} -> IO.puts "player moved left #{spaces} space(s)" 155 | {:right, spaces} -> IO.puts "player moved right #{spaces} space(s)" 156 | {:up, spaces} -> IO.puts "player moved up #{spaces} space(s)" 157 | {:down, spaces} -> IO.puts "player moved down #{spaces} space(s)" 158 | end 159 | Function<6.17052888 in :erl_eval.expr/5> 160 | 161 | iex(3)> process_input.({:left, 5}) 162 | player moved left 5 space(s) 163 | :ok 164 | 165 | iex(4)> process_input.({:up, 2}) 166 | player moved up 2 space(s) 167 | :ok 168 | 169 | iex(5)> process_input.({:jump, 2}) 170 | ** (FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/1 171 | ``` 172 | 173 | ## Alias, Import, Require 174 | A few keywords exist in Elixir that live at the heart of module composition: 175 | 176 | * `alias` used to register aliases for modules 177 | * `import` imports functions and macros from other modules 178 | * `require` ensures modules are compiled and loaded so that macros can be invoked 179 | 180 | In the example below `:math` refers to the Erlang math module and makes 181 | it accessible as `Math` (following the Elixir naming convention for 182 | Modules). 183 | 184 | Furthermore, we could have imported the entire Math module with `import 185 | Math`; however, since we only wish to call the `pi` function, we've limited the 186 | import to only that specific function. 187 | 188 | ```elixir 189 | defmodule Converter do 190 | alias :math, as: Math 191 | import Math, only: [pi: 0] 192 | 193 | def degrees_to_radians(degrees) do 194 | degrees * (pi / 180) 195 | end 196 | 197 | def sin_to_cos(x) do 198 | Math.cos(x - (pi/2)) 199 | end 200 | end 201 | {:module, Converter... 202 | 203 | iex(13)> Converter.degrees_to_radians(90) 204 | 1.5707963267948966 205 | 206 | iex(14)> Converter.sin_to_cos(120) 207 | 0.5806111842123187 208 | ``` 209 | 210 | Rather than calling `cos` via the Math module, we could have imported it 211 | as well 212 | 213 | ```elixir 214 | import Math, only: [pi: 0, cos: 1] 215 | 216 | def sin_to_cos(x) do 217 | cos(x - (pi/2)) 218 | end 219 | ``` 220 | -------------------------------------------------------------------------------- /basics/03_basics.md: -------------------------------------------------------------------------------- 1 | # The Basics 2 | 3 | ## Types 4 | Elixir is dynamically typed and contains a small, but powerful set of types including: 5 | 6 | - Integer 7 | - Float 8 | - Atom 9 | - Tuple 10 | - List 11 | - Bitstring 12 | - Pid 13 | 14 | ### Atoms 15 | *Atoms* are constants with a name and synonymous with symbols in languages such as Ruby. Atoms are prefixed by a semicolon, such as `:ok` and are a fundamental utility in Elixir. Atoms are used for powerful *pattern matching* techniques, as well as a simple, yet effective way to describe data and return values. Internally, Elixir programs are represented by an AST (Abstract Syntax Tree) comprised of atoms and metadata. 16 | 17 | 18 | ```elixir 19 | iex> is_atom :ok 20 | true 21 | ``` 22 | 23 | ### Tuples 24 | *Tuples* are arrays of fixed length, stored contiguously in memory, which can hold any combination of Elixir types. Unlike Erlang, tuples in Elixir are indexed starting at zero. 25 | 26 | 27 | ```elixir 28 | iex(3)> ids = {1, 2, 3} 29 | {1, 2, 3} 30 | iex(4)> is_tuple ids 31 | true 32 | iex(5)> elem ids, 0 33 | 1 34 | iex(6)> elem ids, 1 35 | 2 36 | ``` 37 | 38 | ### Lists 39 | *Lists* are linked-lists containing a variable number of terms. Like tuples, lists can hold any combination of types. Element lookup is `O(N)`, but like most functional languages, composing lists as a head and tail is highly optimized. The `head` of the list is the first element, with the `tail` containing the remaining set. This syntax is denoted by `[h|t]` and can be used to show a list entirely as a series of linked lists. For example: 40 | 41 | ```elixir 42 | iex(1)> list = [1, 2 ,3] 43 | [1, 2, 3] 44 | iex(2)> [ 1 | [2, 3] ] == list 45 | true 46 | iex(3)> [1 | [2 | [3]] ] == list 47 | true 48 | iex(4)> hd list 49 | 1 50 | iex(5)> tl list 51 | [2, 3] 52 | iex(6)> [head|tail] = list 53 | [1, 2, 3] 54 | iex(7)> head 55 | 1 56 | iex(8)> tail 57 | [2, 3] 58 | iex(9)> h Enum.at 59 | 60 | def at(collection, n, default // nil) 61 | 62 | Finds the element at the given index (zero-based). 63 | Returns default if index is out of bounds. 64 | 65 | Examples 66 | 67 | ┃ iex> Enum.at([2, 4, 6], 0) 68 | ┃ 2 69 | ┃ iex> Enum.at([2, 4, 6], 2) 70 | ┃ 6 71 | ┃ iex> Enum.at([2, 4, 6], 4) 72 | ┃ nil 73 | ┃ iex> Enum.at([2, 4, 6], 4, :none) 74 | ┃ :none 75 | 76 | iex(10)> Enum.at list, 2 77 | 3 78 | iex(11)> Enum.reverse list 79 | [3, 2, 1] 80 | ``` 81 | 82 | #### *Tip: iex "h" helper function* 83 | > Use `h` followed by a Module name or Module function name to call up markdown formatted documentation as seen in the ninth iex entry of the previous example. 84 | 85 | 86 | ### Keyword Lists 87 | *Keyword Lists* provide syntactic sugar for using a list to represent a series of key-value pairs. Internally, the key-value pairs are simply a list of tuples containing two terms, an atom and value. Keyword lists are convenient for small sets of data where true hash or map based lookup performance is not a concern. 88 | 89 | ```elixir 90 | iex(2)> types = [atom: "Atom", tuple: "Tuple"] 91 | [atom: "Atom", tuple: "Tuple"] 92 | iex(3)> types[:atom] 93 | "Atom" 94 | iex(4)> types[:not_exists] 95 | nil 96 | iex(5)> types == [{:atom, "Atom"}, {:tuple, "Tuple"}] 97 | true 98 | 99 | iex(6)> IO.inspect types 100 | [atom: "Atom", tuple: "Tuple"] 101 | 102 | iex(7)> IO.inspect types, raw: true 103 | [{:atom, "Atom"}, {:tuple, "Tuple"}] 104 | 105 | iex(9)> Keyword.keys(types) 106 | [:atom, :tuple] 107 | iex(10)> Keyword.values types 108 | ["Atom", "Tuple"] 109 | 110 | iex(10)> Keyword. 111 | delete/2 delete_first/2 drop/2 equal?/2 112 | fetch!/2 fetch/2 from_enum/1 get/3 113 | get_values/2 has_key?/2 keys/1 keyword?/1 114 | merge/2 merge/3 new/0 new/1 115 | new/2 pop/3 pop_first/3 put/3 116 | put_new/3 split/2 take/2 update!/3 117 | update/4 values/1 118 | ``` 119 | 120 | #### *Tip: tab-complete in `iex`* 121 | > Gratuitous use helps discover new functions and explore module APIs 122 | 123 | 124 | ## Variables & Immutability 125 | Elixir is an immutable programming language. Any variables defined cannot be changed. While this imposes some design considerations, it is a vital part of Elixir's ability to write concurrent and robust applications. Variable assignment is referred to as *binding*, where a term is bound to a value. Here's a taste of some simple bindings: 126 | 127 | Binding Variables: 128 | ```elixir 129 | iex(1)> sum = 1 + 1 130 | 2 131 | iex(2)> names = ["alice", "bob", "ted"] 132 | ["alice", "bob", "ted"] 133 | iex(3)> [first | rest ] = names 134 | ["alice", "bob", "ted"] 135 | iex(4)> first 136 | "alice" 137 | iex(5)> rest 138 | ["bob", "ted"] 139 | ``` 140 | 141 | While variables are immutable and can only be assigned once, Elixir allows us to rebind a variable to a new value. It is important to realize that this does *not* change the original variable. Any reference to the previous assignment maintains the original binding. 142 | 143 | Rebinding Variables: 144 | ```elixir 145 | iex(1)> sum = 1 + 2 146 | 3 147 | iex(2)> initial_sum = fn -> IO.puts sum end 148 | #Function<20.17052888 in :erl_eval.expr/5> 149 | 150 | iex(3)> sum = 3 + 4 151 | 7 152 | iex(4)> initial_sum.() 153 | 3 154 | :ok 155 | ``` 156 | 157 | ## Anonymous Functions 158 | Along with variable binding, we just got our first taste of the anonymous function syntax. Anonymous functions can be defined with the `fn arg1, arg2 -> end` syntax and invoked with the explicit "dot notation." As you would expect from a functional language, functions in Elixir are first class citizens and can be passed around and invoked from other functions. 159 | 160 | First Class Functions: 161 | ```elixir 162 | iex(1)> add = fn num1, num2 -> 163 | ...(1)> num1 + num2 164 | ...(1)> end 165 | #Function<12.17052888 in :erl_eval.expr/5> 166 | 167 | iex(2)> subtract = fn num1, num2 -> 168 | ...(2)> num1 - num2 169 | ...(2)> end 170 | #Function<12.17052888 in :erl_eval.expr/5> 171 | 172 | iex(3)> perform_calculation = fn num1, num2, func -> 173 | ...(3)> func.(num1, num2) 174 | ...(3)> end 175 | #Function<18.17052888 in :erl_eval.expr/5> 176 | 177 | iex(4)> add.(1, 2) 178 | 3 179 | iex(5)> perform_calculation.(5, 5, add) 180 | 10 181 | iex(6)> perform_calculation.(5, 5, subtract) 182 | 0 183 | iex(7)> perform_calculation.(5, 5, &(&1 * &2)) 184 | 25 185 | ``` 186 | 187 | The last example shows Elixir's *shorthand function* syntax. The `&(&1 * &2)` is simply syntactic sugar for: 188 | 189 | 190 | ```elixir 191 | iex(7)> perform_calculation.(5, 5, fn a, b -> a * b end) 192 | 25 193 | ``` 194 | 195 | The shorthand function syntax is useful when performing simple operations on one or two operands: 196 | 197 | ```elixir 198 | iex(1)> Enum.map [3, 7, 9], &(&1 * 2) 199 | [6, 14, 18] 200 | iex(2)> Enum.filter [1, "red", 2, "green"], &(is_number &1) 201 | [1, 2] 202 | ``` 203 | 204 | #### *Warning: Use sparingly* 205 | > The shorthand syntax is nice and succinct, but it should be used only in cases when its meaning is obvious and your arguments few. Your code should strive for clarity over brevity, always. 206 | 207 | ## Captured Functions 208 | 209 | The shorthand example also showcased the syntax for *capturing* functions. Capturing is used for functions defined within modules, or *named functions* (Covered in the next section), where a function reference is needed instead of invocation. Both name and arity are required for function identification when capturing. 210 | 211 | Capturing named functions: 212 | ```elixir 213 | iex(2)> add = &Kernel.+/2 214 | &Kernel.+/2 215 | iex(3)> add.(1,2) 216 | 3 217 | iex(2)> Enum.reduce [1, 2, 3], 0, &Kernel.+/2 218 | 6 219 | ``` 220 | 221 | When performing `1 + 2`, underneath Elixir is calling the named function `+`, defined and imported automatically from the `Kernel` module. Modules are the main building blocks of Elixir programs. 222 | 223 | 224 | ## Named Functions 225 | Named functions are functions defined within Modules. Named functions are similar to anonymous functions but the dot notation is not required for invocation. 226 | 227 | ```elixir 228 | iex> 229 | 230 | defmodule Weather do 231 | def celsius_to_fahrenheit(celsius) do 232 | (celsius * 1.8) + 32 233 | end 234 | 235 | def high, do: 50 236 | def low, do: 32 237 | end 238 | 239 | {:module, Weather, ... 240 | iex(5)> Weather.high 241 | 50 242 | iex(6)> Weather.celsius_to_fahrenheit(20) 243 | 68.0 244 | ``` 245 | We'll be covering modules extensively in the next section. 246 | 247 | 248 | --------------------------------------------------------------------------------