├── .agignore
├── .gitignore
├── .iex
├── LICENSE
├── README.md
├── advanced
├── map_reduce.md
├── pattern_matching.md
├── pipeline.md
├── processes.md
├── records_protocols.md
└── src
│ ├── countdown.ex
│ ├── pattern_match_records.exs
│ ├── pipeline.exs
│ ├── pipeline_bonus.exs
│ ├── records_protocols.exs
│ └── triangle_kata.exs
├── basics
└── README.md
├── elixir-logo.png
├── otp
├── README.md
├── images
│ ├── .DS_Store
│ ├── supervisor_tree.gif
│ ├── supervisor_tree.png
│ └── supervisor_tree.pxm
└── src
│ ├── customstack.exs
│ ├── customstack.md
│ ├── genserver_stack_example
│ ├── .gitignore
│ ├── README.md
│ ├── lib
│ │ ├── stack.ex
│ │ └── stack
│ │ │ ├── server.ex
│ │ │ └── supervisor.ex
│ ├── mix.exs
│ └── test
│ │ ├── stack_test.exs
│ │ └── test_helper.exs
│ └── tweet_aggregator
│ ├── .gitignore
│ ├── README.md
│ ├── lib
│ ├── tweet_aggregator.ex
│ └── tweet_aggregator
│ │ ├── aggregator.ex
│ │ ├── gate_keeper.ex
│ │ ├── node_monitor.ex
│ │ ├── reporter.ex
│ │ ├── search
│ │ ├── client.ex
│ │ ├── server.ex
│ │ └── supervisor.ex
│ │ └── supervisor.ex
│ ├── mix.exs
│ ├── mix.lock
│ └── test
│ ├── aggregator_test.exs
│ ├── gate_keeper_test.exs
│ └── test_helper.exs
├── presenter.exs
├── setup
└── README.md
└── tools
├── Guardfile
├── README.md
└── src
└── hello_elixir
├── .gitignore
├── README.md
├── lib
├── hello_elixir.ex
└── hello_elixir
│ └── supervisor.ex
├── mix.exs
└── test
├── hello_elixir_test.exs
└── test_helper.exs
/.agignore:
--------------------------------------------------------------------------------
1 | *.beam
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.beam
3 | .env
4 | _build
5 |
6 |
--------------------------------------------------------------------------------
/.iex:
--------------------------------------------------------------------------------
1 | c "presenter.exs"
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Chris McCord and Ryan Cromwell
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sweet Elixir!
2 | ## A Gentle Introduction to Erlang’s cute younger brother Elixir
3 |
4 | ![elixir-lang][elixir-logo]
5 |
6 | ## Getting Started
7 | 1. **What is Erlang?**
8 |
9 | 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.
10 |
11 | 1. **Why Elixir?**
12 |
13 | Elixir is a language is 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 models 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.
14 |
15 | 1. [Setup][setup]
16 | 1. [Tools][tools]
17 |
18 | ## [Basics][basics]
19 | 1. [Numbers, atoms, strings, lists, tuples, ...][types]
20 | 1. [Functions][functions]
21 | 1. [Operators][operators]
22 | 1. [Modules][modules]
23 |
24 | [Cheatsheet][cheetsheet]
25 |
26 | ## Advanced
27 | 1. [Records & Protocols][records_protocols]
28 | 1. [Pattern Matching][pattern_matching]
29 | 1. [Map/Reduce][map_reduce]
30 | 1. [Pipeline][pipeline]
31 | 1. [Processes][processes]
32 |
33 | ## [Common Patterns/OTP][otp]
34 |
35 | 1. [Stack Server (manual)][custom_stack_server]
36 | 1. GenServer, Supervisors
37 | 1. [Distributed Elixir, Tweet Aggregator][distributed_tweets]
38 |
39 | ## Web & DB
40 |
41 | [elixir-logo]: ./elixir-logo.png
42 | [setup]: ./setup/README.md
43 | [tools]: ./tools/README.md
44 | [basics]: ./basics/README.md
45 | [pattern_matching]: ./advanced/pattern_matching.md
46 | [records_protocols]: ./advanced/records_protocols.md
47 | [processes]: ./advanced/processes.md
48 | [map_reduce]: ./advanced/map_reduce.md
49 | [pipeline]: ./advanced/pipeline.md
50 | [cheetsheet]: http://media.pragprog.com/titles/elixir/ElixirCheat.pdf
51 | [operators]: ./basics/README.md#operators
52 | [functions]: ./basics/README.md#functions
53 | [types]: ./basics/README.md#types-int-float-atom-tuple-list-binary
54 | [modules]: ./basics/README.md#modules
55 | [otp]: ./otp/README.md
56 | [custom_stack_server]: ./otp/src/customstack.md
57 | [distributed_tweets]: ./otp/src/tweet_aggregator
58 |
--------------------------------------------------------------------------------
/advanced/map_reduce.md:
--------------------------------------------------------------------------------
1 | ## Map
2 |
3 | Two big modules:
4 |
5 | ### [Enum][enum]
6 |
7 | ```elixir
8 | ages = [35, 34, 6, 5, 1, 1]
9 | add_10 = fn (age) -> age + 10 end
10 | Enum.map ages, add_10
11 | ```
12 |
13 | Results in...
14 | ```elixir
15 | [55, 54, 26, 25, 21, 21]
16 | ```
17 |
18 | The Pipeline alternative might look like this:
19 |
20 | ```elixir
21 | [2, 4, 6] |>
22 | Enum.map(&IO.inspect(&1)) |>
23 | Enum.map(&(&1 * 10)) |>
24 | Enum.map(&IO.inspect(&1))
25 | ```
26 |
27 | ### [Stream][stream]
28 |
29 | ```elixir
30 | ages = [35, 34, 6, 5, 1, 1]
31 | add_20 = fn (age) -> age + 20 end
32 | Stream.map ages, add_20
33 | ```
34 |
35 | Results in...
36 | ```elixir
37 | Stream.Lazy[enum: [35, 34, 6, 5, 1, 1],
38 | funs: [#Function<32.133702391 in Stream.map/2>], accs: [], after: [],
39 | last: nil]
40 | ```
41 |
42 | Streams are composable and don't execute until asked to provide a value.
43 | ```elixir
44 | stream = [2, 4, 6] |>
45 | Stream.map(&IO.inspect(&1)) |>
46 | Stream.map(&(&1 *2)) |>
47 | Stream.map(&IO.inspect(&1))
48 |
49 | Enum.to_list stream
50 | ```
51 |
52 | Results in...
53 | ```elixir
54 | 2
55 | 4
56 | 4
57 | 8
58 | 6
59 | 12
60 | [4, 8, 12]
61 | ```
62 |
63 | The `[2, 4, 6]` showcases the ability to compose a stream as a series of transformations and evalute the operation as a single iteration. If you look closely, the order of operations changed from the Enum example because instead of iterating the list three separate times as the first example, the second transformation happens with a single iteration of the set.
64 |
65 | ```elixir
66 | next = fn (x) ->
67 | x + 1
68 | end
69 | sleep = fn(x) ->
70 | :timer.sleep 1000
71 | x
72 | end
73 | tap = fn(x) ->
74 | IO.puts "Hey #{x}"
75 | end
76 |
77 |
78 | Stream.iterate(0,next) |> Stream.each(tap) |> Stream.each(sleep) |> Enum.take(10)
79 |
80 | Hey 0
81 | Hey 1
82 | Hey 2
83 | Hey 3
84 | Hey 4
85 | Hey 5
86 | Hey 6
87 | Hey 7
88 | Hey 8
89 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
90 | ```
91 |
92 | ## Reduce
93 |
94 | ```elixir
95 | Enum.reduce 1..10, 0, &(&1 + &2)
96 | Enum.map_reduce([1, 2, 3], 0, fn(x, acc) -> { x * 2, 1 + acc } end)
97 | ```
98 |
99 | Results in...
100 | ```elixir
101 | {[2,4,6], 3}
102 | ```
103 |
104 | [enum]: http://elixir-lang.org/docs/master/Enum.html
105 | [stream]: http://elixir-lang.org/docs/master/Stream.html
106 |
--------------------------------------------------------------------------------
/advanced/pattern_matching.md:
--------------------------------------------------------------------------------
1 | ## Pattern Matching
2 |
3 | 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.
4 |
5 | ```elixir
6 | a = 1 # 1
7 |
8 | 1 = a # 1
9 |
10 | b = 2 # 2
11 |
12 | ^a = b
13 | ** (MatchError) no match of right hand side value: 2
14 | iex(5)> ^a = 1
15 | 1
16 | iex(6)> [first, 2, last] = [1, 2, 3]
17 | [1, 2, 3]
18 | iex(7)> first
19 | 1
20 | iex(8)> last
21 | 3
22 | ```
23 |
24 | `^a = b` shows the syntax for pattern matching against a variable's value instead of performing assignment.
25 |
26 | A common technique is to return a tuple from a function with the atom `:ok`, followed by the requested value. This allows the caller to pattern match against the return value and let the program crash when an unexpected error occurs:
27 |
28 | ```elixir
29 | iex(1)> {:ok, logger_pid} = DB.start_logger
30 | ```
31 |
32 | If `DB.start_logger` failed to open the log file and returned `{:error, :enoent}`, the program would crash with a `MatchError` since pattern matching was performed solely by the use of the `=` operator. This is useful where programs can take no other sane action than crashing, such as when a database's logger fails to open.
33 |
34 |
35 | ### Control Flow Pattern Matching
36 |
37 | Pattern matching can be used with the `case` keyword for powerful control flow handling. `case` pattern matches against each clause and invokes the first match. At least one clause must be matched or a `CaseClauseError` is raised.
38 |
39 | Let's write a mini calculation parser to perform a few basic operations:
40 |
41 | ```elixir
42 | iex(1)> calculate = fn expression ->
43 | ...(1)> case expression do
44 | ...(1)> {:+, num1, num2} -> num1 + num2
45 | ...(1)> {:-, num1, num2} -> num1 - num2
46 | ...(1)> {:*, num1, 0} -> 0
47 | ...(1)> {:*, num1, num2} -> num1 * num2
48 | ...(1)> end
49 | ...(1)> end
50 | #Function<6.17052888 in :erl_eval.expr/5>
51 |
52 | iex(2)> calculate.({:+, 8, 2})
53 | 10
54 | iex(3)> calculate.({:-, 8, 2})
55 | 6
56 | iex(4)> calculate.({:*, 8, 2})
57 | 16
58 | iex(5)> calculate.({:^, 8, 2})
59 | ** (CaseClauseError) no case clause matching: {:^, 8, 2}
60 | iex(5)>
61 | ```
62 |
63 | ### Function Pattern Matching
64 | Pattern matching is not just limited to bindings. Elixir uses pattern matching for anonymous and named function invocation as well.
65 |
66 | [Run it][src_countdown] (./src/countdown.ex)
67 | ```elixir
68 | defmodule Countdown do
69 | def run(from, to) when from >= to do
70 | run(from, to, from)
71 | end
72 |
73 | def run(_from, to, current) when to == current do
74 | IO.puts to
75 | IO.puts "Done!"
76 | end
77 |
78 | def run(from, to, current) do
79 | IO.puts current
80 | run(from, to, current - 1)
81 | end
82 | end
83 |
84 | ```
85 |
86 | ```elixir
87 | iex(10)> c "countdown.ex"
88 | [Countdown]
89 | iex(11)> Countdown.run(10, 2)
90 | 10
91 | 9
92 | 8
93 | 7
94 | 6
95 | 5
96 | 4
97 | 3
98 | 2
99 | Done!
100 | :ok
101 |
102 | iex(13)> Countdown.run(10, 11)
103 | ** (FunctionClauseError) no function clause matching in Countdown.run/2
104 | countdown.ex:3: Countdown.run(10, 11)
105 | ```
106 |
107 | *Guard Clauses* are used with the postfix `when` on function definitions. Multiple functions with the same name can be defined because the guard becomes part of the function's unique signature. Each definition is pattern matched against when calling `run` until the first match is found. Pattern matching on functions is run from top to bottom, in the order the functions are defined.
108 |
109 | ### Record Pattern Matching
110 |
111 | [Run it][src_record_pattern_match] (./src/pattern_match_records.exs)
112 | ```elixir
113 | defrecord Person, gender: nil, name: ""
114 |
115 | defmodule PersonPrefixer do
116 | def prefix(p = Person[gender: :male]), do: "Mr."
117 | def prefix(p = Person[gender: :female]), do: "Mrs."
118 | end
119 |
120 | defmodule SharingMatchLogic do
121 | defmacro is_male(p) do
122 | ix = Person.__record__(:index, :gender)
123 | quote do: elem(unquote(p), unquote(ix)) == :male
124 | end
125 | defmacro is_female(p) do
126 | ix = Person.__record__(:index, :gender)
127 | quote do: elem(unquote(p), unquote(ix)) == :female
128 | end
129 |
130 | def prefix(p = Person[]) when is_male(p), do: "Mr." # won't work
131 | def prefix(p = Person[]) when is_female(p), do: "Mrs." # won't work
132 | end
133 |
134 | john = Person.new gender: :male, name: "John"
135 | jane = Person.new gender: :female, name: "Jane"
136 |
137 | PersonPrefixer.prefix(john) |> IO.puts
138 | PersonPrefixer.prefix(jane) |> IO.puts
139 |
140 | SharingMatchLogic.prefix(john) |> IO.puts
141 | SharingMatchLogic.prefix(jane) |> IO.puts
142 |
143 | ```
144 |
145 | [src_record_pattern_match]: ./src/pattern_match_records.exs
146 | [src_countdown]: ./src/countdown.ex
147 |
--------------------------------------------------------------------------------
/advanced/pipeline.md:
--------------------------------------------------------------------------------
1 | ## Pipeline
2 |
3 | The pipeline is a very simple, but powerful tool in Elixir. Using the pipeline operator `|>` we can easily visualize the transformation of data - which is what our programs do!
4 |
5 |
6 | **Before Pipeline**
7 | ```elixir
8 | lines = String.split file_content, "\n"
9 | lines = Stream.filter lines, &(String.length(&1) > 0)
10 | lengths = Stream.map lines, &(String.split &1)
11 | triangles = Stream.map lengths, &(list_to_tuple &1)
12 | classifications = Stream.map triangles, &({ &1, Classifier.classify &1 })
13 |
14 | messages = Stream.map classifications, user_message
15 | result = Enum.join(messages, "\n")
16 |
17 | IO.puts result
18 | ```
19 |
20 | **After Pipeline**
21 | ```elixir
22 | file_content
23 | |> String.split("\n")
24 | |> Stream.filter( &(String.length(&1) > 0) )
25 | |> Stream.map( &(String.split &1) )
26 | |> Stream.map( &(list_to_tuple &1) )
27 | |> Stream.map( &( { &1, Classifier.classify &1 } ) )
28 | |> Stream.map( user_message )
29 | |> Enum.join("\n")
30 | |> IO.puts
31 | ```
32 |
33 | [Run it (./src/pipeline.exs)](./src/pipeline.exs)
34 |
35 | **Bonus**
36 | ```elixir
37 | %w( gray, white )
38 | |> Stream.cycle
39 | |> Stream.zip(%w(Customer1 Customer2 Customer3 Customer 4)
40 | |> Stream.map( fn {color, value} ->
41 | %s{
#{value} |
}
42 | end)
43 | |> Enum.join "\n"
44 | "1 |
\n2 |
\n3 |
\n4 |
\n5 |
\n6 |
\n7 |
\n8 |
\n9 |
\n10 |
"
48 | ```
49 |
50 | [Run it (./src/pipeline_bonus.exs)](./src/pipeline_bonus.exs)
51 |
--------------------------------------------------------------------------------
/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 | _Note: Prior to 0.12.2, the `<-` operator could be used to send messages. This has been deprecated so that the operator can be repurposed for list comprehensions.
8 |
9 | ### Mailboxes
10 | 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.
11 |
12 | ```elixir
13 | pid = spawn fn ->
14 | receive do
15 | {sender, :ping} ->
16 | IO.puts "Got ping"
17 | send sender, :pong
18 | end
19 | end
20 |
21 | send pid, {self, :ping}
22 |
23 | # Got ping
24 |
25 | receive do
26 | message -> IO.puts "Got #{message} back"
27 | end
28 |
29 | # Got pong back
30 | ```
31 |
32 | `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.
33 |
34 | ```elixir
35 | receive do
36 | message -> IO.inspect message
37 | after 5000 ->
38 | IO.puts "Timeout, giving up"
39 | end
40 | ```
41 |
42 | Results in...
43 | ```elixir
44 | # Timeout, giving up
45 | ```
46 |
47 | ## spawn_link
48 | 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.
49 |
50 | ```elixir
51 | pid = spawn_link fn ->
52 | receive do
53 | :boom -> raise "boom!"
54 | end
55 | end
56 |
57 | send pid, :boom
58 | ```
59 | Results in...
60 | ```elixir
61 | =ERROR REPORT==== 27-Dec-2013::16:49:14 ===
62 | Error in process <0.64.0> with exit value: {{'Elixir.RuntimeError','__exception__',<<5 bytes>>},[{erlang,apply,2,[]}]}
63 |
64 | ** (EXIT from #PID<0.64.0>) {RuntimeError[message: "boom!"], [{:erlang, :apply, 2, []}]}
65 | ```
66 |
67 | ```elixir
68 | pid = spawn fn ->
69 | receive do
70 | :boom -> raise "boom!"
71 | end
72 | end
73 |
74 | send pid, :boom
75 | ```
76 |
77 | Results in...
78 | ```elixir
79 | =ERROR REPORT==== 27-Dec-2013::16:49:50 ===
80 | Error in process <0.71.0> with exit value: {{'Elixir.RuntimeError','__exception__',<<5 bytes>>},[{erlang,apply,2,[]}]}
81 |
82 | iex(5)>
83 | ```
84 |
85 | 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.
86 |
87 | ## Parallelizing Tasks
88 | With the simple spawn/receive mechanisms, tasks can be parallelized with code that is easy to comprehend. Consider a `Maps` module that contains a `map` function which iterates over a list and invokes a provided function for each element. A parallelized version `pmap` can be written which simply spawns a new process for each element invocation and colllects the results from each process before returning. For example:
89 |
90 | ```elixir
91 | defmodule Maps do
92 |
93 | def map([], _), do: []
94 | def map([h|t], func), do: [ func.(h) | map(t, func) ]
95 |
96 | def child(element, func, parent) do
97 | send parent, func.(element)
98 | end
99 | defp spawn_children(collection, func) do
100 | map collection, fn element -> spawn(__MODULE__, :child, [element, func, self]) end
101 | end
102 |
103 | defp collect_results(pids) do
104 | map pids, fn _ -> receive do: ( value -> value) end
105 | end
106 |
107 | def pmap(collection, func) do
108 | collection |> spawn_children(func) |> collect_results
109 | end
110 | end
111 |
112 | Maps.map [1, 2, 3], &(&1 * &1)
113 | # [1, 4, 9]
114 |
115 | Maps.pmap [1, 2, 3], &(&1 * &1)
116 | # [1, 4, 9]
117 | ```
118 | [Run it ./src/triangle_kata.exs][triangle_kata]
119 |
120 | ## Holding State
121 | 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:
122 |
123 | ```elixir
124 | defmodule Counter do
125 | def start(initial_count) do
126 | spawn fn -> listen(initial_count) end
127 | end
128 |
129 | def listen(count) do
130 | receive do
131 | :inc -> listen(count + 1)
132 | {sender, :val} ->
133 | send sender, count
134 | listen(count)
135 | end
136 | end
137 | end
138 |
139 | counter_pid = Counter.start(10)
140 |
141 | send counter_pid, :inc
142 |
143 | send counter_pid, :inc
144 |
145 | send counter_pid, :inc
146 |
147 | send counter_pid, {self, :val}
148 |
149 | receive do
150 | value -> value
151 | end
152 | ```
153 |
154 | Results in...
155 | ```elixir
156 | 13
157 | ```
158 |
159 | ## Registered Processes
160 | Pids can be registered under a name for easy lookup by other processes
161 |
162 | ```elixir
163 | pid = Counter.start 10
164 |
165 | Process.register pid, :count
166 |
167 | Process.whereis(:count) == pid
168 |
169 | send :count, :inc
170 |
171 | receive do
172 | value -> value
173 | end
174 | ```
175 |
176 | Results in...
177 | ```elixir
178 | 11
179 | ```
180 |
181 | [triangle_kata]: ./src/triangle_kata.exs
182 |
--------------------------------------------------------------------------------
/advanced/records_protocols.md:
--------------------------------------------------------------------------------
1 | ## Records
2 | Records provide a way to store and access structured data. Think of them as a
3 | hybrid between tuples and Keyword lists. They can have default values as well as convenient accessors
4 |
5 | ```elixir
6 | iex(1)>
7 | defrecord Status, id: nil, text: "", username: nil, hash_tags: [], mentions: []
8 |
9 | iex(2)> status = Status.new text: "Sweet Elixir!"
10 | Status[id: nil, text: "Sweet Elixir!", username: nil, hash_tags: [],
11 | mentions: []]
12 | iex(3)> status.text
13 | "Sweet Elixir!"
14 | iex(4)> status = status.text("RT Sweet Elixir!")
15 | Status[id: nil, text: "RT Sweet Elixir!", username: nil, hash_tags: [],
16 | mentions: []]
17 | iex(5)> status.text
18 | "RT Sweet Elixir!"
19 | iex(6)> status.update(text: "@elixir-lang rocks!", username: "chris_mccord")
20 | Status[id: nil, text: "@elixir-lang rocks!", username: "chris_mccord",
21 | hash_tags: [], mentions: []]
22 | ```
23 |
24 | ## Protocols
25 |
26 | Protocols provide ad-hoc polymorphism. This pattern allows library consumers
27 | to implement protocols on third party modules and records.
28 |
29 | ```elixir
30 | defimpl String.Chars, for: Status do
31 | def to_string(status = Status[mentions: []]) do
32 | "#{status.username}: #{status.text}"
33 | end
34 | def to_string(status) do
35 | """
36 | #{status.username}': #{status.text}
37 | mentions: #{inspect status.mentions}
38 | """
39 | end
40 | end
41 |
42 |
43 |
44 | iex(2)> status = Status.new username: "chris_mccord", text: "Sweet Elixir!"
45 | Status[id: nil, text: "Sweet Elixir!", username: nil, hash_tags: [],
46 | mentions: []]
47 |
48 | iex(4)> IO.puts status
49 | chrismccord: Sweet Elixir!
50 | :ok
51 | iex(5)>
52 | ```
53 |
54 | ### Presence Protocol
55 |
56 | ```elixir
57 | defprotocol Present do
58 | def present?(data)
59 | end
60 |
61 | defimpl Present, for: [Integer, Float] do
62 | def present?(_), do: true
63 | end
64 |
65 | defimpl Present, for: List do
66 | def present?([]), do: false
67 | def present?(_), do: true
68 | end
69 |
70 | defimpl Present, for: Atom do
71 | def present?(false), do: false
72 | def present?(nil), do: false
73 | def present?(_), do: true
74 | end
75 |
76 | defimpl Present, for: BitString do
77 | def present?(string), do: String.length(string) > 0
78 | end
79 | ```
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/advanced/src/countdown.ex:
--------------------------------------------------------------------------------
1 | defmodule Countdown do
2 |
3 | def run(from, to) when from >= to do
4 | run(from, to, from)
5 | end
6 |
7 | def run(_from, to, current) when to == current do
8 | IO.puts to
9 | IO.puts "Done!"
10 | end
11 |
12 | def run(from, to, current) do
13 | IO.puts current
14 | run(from, to, current - 1)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/advanced/src/pattern_match_records.exs:
--------------------------------------------------------------------------------
1 | defrecord Person, gender: nil, name: ""
2 |
3 | defmodule Blah do
4 | defmacro is_male(p) do
5 | ix = Person.__record__(:index, :gender)
6 | quote do: elem(unquote(p), unquote(ix)) == :male
7 | end
8 | defmacro is_female(p) do
9 | ix = Person.__record__(:index, :gender)
10 | quote do: elem(unquote(p), unquote(ix)) == :female
11 | end
12 |
13 | def prefix(p = Person[]) when is_male(p), do: "Mr." # won't work
14 | def prefix(p = Person[]) when is_female(p), do: "Mrs." # won't work
15 | end
16 |
17 | john = Person.new gender: :male, name: "John"
18 | jane = Person.new gender: :female, name: "Jane"
19 |
20 | IO.puts ""
21 | Blah.prefix(john) |> IO.puts
22 | Blah.prefix(jane) |> IO.puts
--------------------------------------------------------------------------------
/advanced/src/pipeline.exs:
--------------------------------------------------------------------------------
1 | file_content = """
2 | 1 1 1
3 | 33.8 31.9 29
4 | 2 1.7 2
5 | """
6 |
7 | defmodule Classifier do
8 | def classify( {a,a,a} ), do: :equilateral
9 | def classify( {a,b,c} ) when a == b or b == c or a == c, do: :isosceles
10 | def classify( {_a,_b,_c} ), do: :scalene
11 | end
12 |
13 | user_message = fn (triangle_classifications) ->
14 | sides = elem(triangle_classifications, 0)
15 | classification = elem(triangle_classifications, 1)
16 | "Triangle #{inspect sides} is #{to_string classification}"
17 | end
18 |
19 | # **Before pipelines**
20 | lines = String.split file_content, "\n"
21 | lines = Stream.filter lines, &(String.length(&1) > 0)
22 | lengths = Stream.map lines, &(String.split &1)
23 | triangles = Stream.map lengths, &(list_to_tuple &1)
24 | classifications = Stream.map triangles, &({ &1, Classifier.classify &1 })
25 |
26 | messages = Stream.map classifications, user_message
27 | result = Enum.join(messages, "\n")
28 |
29 | IO.puts result
30 |
31 | # **After pipelines**
32 | file_content
33 | |> String.split("\n")
34 | |> Stream.filter( &(String.length(&1) > 0) )
35 | |> Stream.map( &(String.split &1) )
36 | |> Stream.map( &(list_to_tuple &1) )
37 | |> Stream.map( &( { &1, Classifier.classify &1 } ) )
38 | |> Stream.map( user_message )
39 | |> Enum.join("\n")
40 | |> IO.puts
41 |
42 |
--------------------------------------------------------------------------------
/advanced/src/pipeline_bonus.exs:
--------------------------------------------------------------------------------
1 | %w( gray, white )
2 | |> Stream.cycle
3 | |> Stream.zip(%w(Customer1 Customer2 Customer3 Customer4))
4 | |> Stream.map( fn {color, value} ->
5 | %s{#{value} |
}
6 | end)
7 | |> Enum.join("\n")
8 | |> IO.puts
9 |
--------------------------------------------------------------------------------
/advanced/src/records_protocols.exs:
--------------------------------------------------------------------------------
1 | defrecord Tweet, text: "", hash_tags: [], user_mentions: []
2 |
3 | defimpl String.Chars, for: Tweet do
4 | def to_string(Tweet[text: text, user_mentions: []]), do: "'#{text}'"
5 |
6 | def to_string(Tweet[text: text, user_mentions: user_mentions]) do
7 | "'#{text}' mentions #{inspect user_mentions}"
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/advanced/src/triangle_kata.exs:
--------------------------------------------------------------------------------
1 | defmodule Maps do
2 |
3 | def map([], _), do: []
4 | def map([h|t], func), do: [ func.(h) | map(t, func) ]
5 |
6 | def child(element, func, parent) do
7 | send parent, func.(element)
8 | end
9 | defp spawn_children(collection, func) do
10 | map collection, fn element -> spawn(__MODULE__, :child, [element, func, self]) end
11 | end
12 |
13 | defp collect_results(pids) do
14 | map pids, fn _ -> receive do: ( value -> value) end
15 | end
16 |
17 | def pmap(collection, func) do
18 | collection |> spawn_children(func) |> collect_results
19 | end
20 | end
21 |
22 | defmodule ClassifyShapes do
23 | defp slow(result, delay) do
24 | :timer.sleep delay
25 | result
26 | end
27 | defp slow(result) do
28 | slow(result, 1000)
29 | end
30 |
31 | def classify( {a,b,c} ) when a == b and b == c, do: :equilateral
32 | def classify( {a,b,c} ) when a == b or b == c or a == c, do: slow(:isosceles, 1100)
33 | def classify( {a,b,c} ) when a != b and b != c, do: slow(:scalene)
34 |
35 | def classify( [h|t] ), do: [classify(h) | classify(t)]
36 | def classify( [] ) do
37 | IO.puts 'asdf'
38 | []
39 | end
40 | def classify( _ ), do: :unclassified
41 | end
42 |
43 | triangles = [
44 | "blue",
45 | { 9, 10, 11 },
46 | { 9, 10, 11 },
47 | { 9, 10, 11 },
48 | { 9, 10, 11 },
49 | { 9, 10, 11 },
50 | { 9, 10, 11 },
51 | { 9, 10, 11 },
52 | { 10, 11, 11 },
53 | { 10, 11, 10 },
54 | { 11, 11, 10 },
55 | { 12, 12, 12 },
56 | ]
57 |
58 | IO.inspect triangles |> Maps.pmap &ClassifyShapes.classify &1
59 |
60 |
--------------------------------------------------------------------------------
/basics/README.md:
--------------------------------------------------------------------------------
1 | # Basics
2 |
3 | Follow along - we'll be using `iex` (interactive elixir).
4 |
5 | ## Operators
6 | comparison, boolean, arithmetic, and inclusion
7 |
8 | - `==, !=, ===, !==, >, <, <=, >=`
9 | - `and, or, not, !`
10 | - `+, -, *, /`
11 | - `in` (inclusion operator)
12 |
13 | ## Types
14 |
15 | ### Numbers
16 |
17 | ```
18 | iex> is_number 1
19 | true
20 |
21 | iex> is_integer 1
22 | true
23 |
24 | iex> is_number 1.0
25 | true
26 |
27 | iex> is_integer 1.0
28 | false
29 |
30 | iex> is_float 1.0
31 | true
32 | ```
33 |
34 | ### Boolean
35 |
36 | ```
37 | iex> is_boolean true && false
38 | true
39 |
40 | iex> is_boolean true and false
41 | true
42 | ```
43 |
44 | ### Atoms
45 | ... are like Ruby *symbols*, but named after Lisp's *atom*
46 |
47 | ```
48 | iex> is_atom :thing
49 | true
50 |
51 | iex> is_atom true
52 | true
53 |
54 | iex> :false == false
55 | true
56 | ```
57 |
58 | ### Lists
59 | ... are linked lists
60 | ```
61 | iex> is_list [1,2,3]
62 | true
63 |
64 | iex> length [1,2,3]
65 | 3
66 |
67 | iex> [head | tail] = [1,2,3]
68 | [1,2,3]
69 |
70 | iex> head
71 | 1
72 |
73 | iex> tail
74 | [2,3]
75 |
76 | iex> [1,2] ++ [3,4]
77 | [1,2,3,4]
78 |
79 | iex> [1,2,3] -- [3,4]
80 | [1,2]
81 |
82 | iex> h Enum
83 | ...
84 |
85 | iex> h Stream
86 | ...
87 | ```
88 |
89 | ### Tuples
90 | ```
91 | iex> is_tuple {1,"b", :c}
92 | true
93 |
94 | iex> elem {1, "b", :c}, 1
95 | "b"
96 |
97 | iex> {x, y, _} = {1, "b", :c}
98 | {1, "b", :c}
99 |
100 | iex> x
101 | 1
102 |
103 | iex> y
104 | "b"
105 |
106 | iex> t = {1, "b", :c}
107 | {1, "b", :c}
108 |
109 | iex> set_elem t, 2, :d
110 | {1, "b", :d}
111 |
112 | iex> t
113 | {1, "b", :c}
114 |
115 | iex> elem t, 2
116 | "b"
117 |
118 | iex> Integer.parse "1.0a~22c"
119 | {1, ".0a~22c"}
120 |
121 | iex> Integer.parse "codemash"
122 | :error
123 | ```
124 |
125 | ### Strings
126 | ... exist as strings (binaries) and char lists (lists)
127 | ```
128 | iex> is_list 'hello'
129 | true
130 |
131 | iex> is_list "hello"
132 | false
133 |
134 | iex> is_binary 'world'
135 | false
136 |
137 | iex> is_binary "world"
138 | true
139 |
140 | iex> "hello" <> " " <> "world"
141 | "hello world"
142 |
143 | iex> who = "codemash"
144 | iex> "hello #{who}"
145 | "hello codemash"
146 | ```
147 |
148 | ### Keyword lists
149 | ```
150 | iex> presenter = [name: 'Chris', project: 'Atlas']
151 | iex> Keyword.get presenter, :project
152 | "Atlas"
153 |
154 | iex> [head | _] = presenter
155 |
156 | iex> head
157 | { :name, 'Chris' }
158 | ```
159 |
160 | ### Equality
161 |
162 | ```
163 | iex> 1 == 1.0
164 | true
165 |
166 | iex> 1 === 1.0
167 | false
168 |
169 | iex> 1 < :thing
170 | true
171 | ```
172 |
173 | ## Functions
174 |
175 | Like most functional languages, functions in Elixir are first class and can be passed around
176 | and invoked from other functions.
177 |
178 | ### Anonymous Functions
179 |
180 | Anonymous functions are defined with the `fn arg1, arg2 -> end` syntax and invoked via the "dot notation".
181 |
182 | ```elixir
183 | iex(1)> add = fn num1, num2 ->
184 | ...(1)> num1 + num2
185 | ...(1)> end
186 | #Function<12.17052888 in :erl_eval.expr/5>
187 |
188 | iex(2)> subtract = fn num1, num2 ->
189 | ...(2)> num1 - num2
190 | ...(2)> end
191 | #Function<12.17052888 in :erl_eval.expr/5>
192 |
193 | iex(3)> perform_calculation = fn num1, num2, func ->
194 | ...(3)> func.(num1, num2)
195 | ...(3)> end
196 | #Function<18.17052888 in :erl_eval.expr/5>
197 |
198 | iex(4)> perform_calculation.(5, 5, add)
199 | 10
200 | iex(5)> perform_calculation.(5, 5, subtract)
201 | 0
202 | iex(6)> perform_calculation.(5, 5, &(&1 * &2))
203 | 25
204 | iex(7)>
205 | ```
206 |
207 | The last examples showcases Elixir's **shorthand function** syntax and is sugar for:
208 |
209 | ```elixir
210 | iex(6)> perform_calculation.(5, 5, fn a, b -> a * b end)
211 | 25
212 | ```
213 |
214 | The shorthand syntax is useful when the function takes one or two arguments and performs a simple operation.
215 | More complex functions should use the general purpose syntax to optimize for clarity.
216 |
217 | ### Named Functions
218 |
219 | Functions defined on Modules are referred to as **named functions**. Named functions are defined with the `def` syntax.
220 |
221 | ```elixir
222 | iex(4)> defmodule Weather do
223 | ...(4)> def celsius_to_fahrenheit(celsius) do
224 | ...(4)> (celsius * 1.8) + 32
225 | ...(4)> end
226 | ...(4)>
227 | ...(4)> def high, do: 50
228 | ...(4)> def low, do: 32
229 | ...(4)> end
230 | {:module, Weather,
231 | <<70, 79, 82, 49, 0, 0, 8, 116, 66, 69, 65, 77, 65, 116, 111, 109, 0, 0, 0, 133, 0, 0, 0, 13, 14, 69, 108, 105, 120, 105, 114, 46, 87, 101, 97, 116, 104, 101, 114, 8, 95, 95, 105, 110, 102, 111, 95, 95, 4, 100, ...>>,
232 | {:low, 0}}
233 | iex(5)> Weather.high
234 | 50
235 | iex(6)> Weather.celsius_to_fahrenheit(20)
236 | 68.0
237 | iex(7)>
238 | ```
239 |
240 | ### Captured Functions
241 |
242 | Named functions can be **captured** and bound to a variable for cases where a reference is desired instead of invocation.
243 |
244 | ```elixir
245 | iex(2)> add = &Kernel.+/2
246 | &Kernel.+/2
247 | iex(3)> add.(1, 2)
248 | 3
249 | iex(4)>
250 | ```
251 |
252 | Functions in Elixir are referenced by name and **arity**, or the number of arguments. The previous example captures
253 | the `+` function from the `Kernel` module with arity 2, binds it to the `add` variable, and invokes it via the
254 | dot notation.
255 |
256 |
257 | ## Modules
258 |
259 | Our `Weather` module showcased Elixir Modules. Modules hold named functions, can import functions from other Modules,
260 | and use Macro's for extended functionality.
261 |
262 | ```elixir
263 | iex(12)> defmodule Converter do
264 | ...(12)> alias :math, as: Math
265 | ...(12)> import Math, only: [pi: 0]
266 | ...(12)>
267 | ...(12)> def degrees_to_radians(degrees) do
268 | ...(12)> degrees * (pi / 180)
269 | ...(12)> end
270 | ...(12)>
271 | ...(12)> def sin_to_cos(x) do
272 | ...(12)> Math.cos(x - (pi / 2))
273 | ...(12)> end
274 | ...(12)> end
275 | {:module, Converter,
276 | <<70, 79, 82, 49, 0, 0, 8, 140, 66, 69, 65, 77, 65, 116, 111, 109, 0, 0, 0, 143, 0, 0, 0, 14, 16, 69, 108, 105, 120, 105, 114, 46, 67, 111, 110, 118, 101, 114, 116, 101, 114, 8, 95, 95, 105, 110, 102, 111, 95, 95, ...>>,
277 | {:sin_to_cos, 1}}
278 | iex(13)> Converter.degrees_to_radians(90)
279 | 1.5707963267948966
280 | iex(14)> Converter.sin_to_cos(120)
281 | 0.5806111842123187
282 | iex(15)>
283 |
284 | ```
285 |
286 |
287 | ## Macros
288 |
289 | Macros give programmers the power to write code that writes code. 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`.
290 |
291 | ```elixir
292 | iex(1)>
293 | defmodule Condition do
294 | defmacro lest(expression, options) do
295 | quote do
296 | if !unquote(expression), unquote(options)
297 | end
298 | end
299 | end
300 |
301 | iex(12)> require Condition
302 | nil
303 |
304 | iex(13)> Condition.lest 2 == 5, do: "not equal"
305 | "not equal"
306 | :ok
307 |
308 | iex(14)> Condition.lest 5 == 5, do: "not equal"
309 | nil
310 | ```
311 |
312 | 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:
313 |
314 | ```elixir
315 | iex(1)> quote do: 2 == 5
316 | {:==, [context: Elixir, import: Kernel], [2, 5]}
317 |
318 | iex(2)> quote do: (5 * 2) + 7
319 | {:+, [context: Elixir, import: Kernel],
320 | [{:*, [context: Elixir, import: Kernel], [5, 2]}, 7]}
321 |
322 | iex(3)> quote do: total = 88.00
323 | {:=, [], [{:total, [], Elixir}, 88.0]}
324 | ```
325 |
326 |
--------------------------------------------------------------------------------
/elixir-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cromwellryan/sweetelixir/ba29a77360211ab227d8726abfd95cf0489b9935/elixir-logo.png
--------------------------------------------------------------------------------
/otp/README.md:
--------------------------------------------------------------------------------
1 | # Open Telecom Platform (OTP)
2 | Patterns (behaviors) for structuring programs.
3 |
4 | [Elixir-lang.org -> Getting Started -> Building OTP Applications with Mix](http://elixir-lang.org/getting_started/mix/2.html)
5 |
6 | ## Holding state
7 |
8 | - [Custom Stack Server](src/genserver_stack_example)
9 | - [GenServer Stack Server](src/genserver_stack_example)
10 |
11 | ## Supervisor Tree
12 | Supervisors = Monitor workers / restart worker when bad things happen
13 |
14 | Workers = Doers
15 |
16 | 
17 |
18 | ## Behaviors
19 | Contractual callbacks specific to your application.
20 |
21 | * **Supervisor**
22 | * **GenServer**
23 | * GenFSM
24 | * GenEvent
25 | * **Application**
26 |
27 | ## Supervisor.Behavior
28 |
29 | #### Strategies
30 | * *one_for_one* 1 failure = 1 restart
31 | * *one_for_all* 1 failure = all restart
32 | * *rest_for_one* 1 failure = all restart
33 |
34 | ## GenServer.Behavior
35 |
36 | ``init(initial_state)``
37 |
38 | ``handle_cast``
39 | Asyncronous fire and forget call
40 |
41 | ``handle_call``
42 | Syncronous call that (usually) implies a return value.
43 |
44 | ``start_link``
45 | Called by the supervisor to get things running and return a pid for it to monitor
46 |
47 | ## Running
48 |
49 | #### Locally
50 | ```
51 | iex -S mix
52 |
53 | :gen_server.call(:counter, :stat)
54 | #> 0
55 |
56 | :gen_server.cast(:counter, :increment)
57 | :gen_server.cast(:counter, :increment)
58 |
59 | :gen_server.call(:counter, :stat)
60 | #> 2
61 |
62 | # worker blows up
63 | :gen_server.cast(:counter, :boom)
64 |
65 | :gen_server.call(:counter, :stat)
66 | #> 0
67 | ```
68 |
69 | #### Distributed
70 |
71 | *Note:* Erlang/Elixir communicates over port 4369 by default. On locked down networks like public wifi run `epmd -port 80 -daemon` to tell Erlang/Elixir to communicate over port 80.
72 |
73 | ```
74 | # Start our host
75 | mix --name stack@ --cookie foo
76 | ```
77 |
78 | ```
79 | # Start client
80 | iex --name lastname@ foo --cookie bar
81 |
82 | Node.connect :"stack@"
83 | #> true
84 |
85 | :gen_server.cast( {:global, :counter}, :increment )
86 |
87 | :gen_server.call( {:global, :counter}, :stat )
88 | ```
89 |
--------------------------------------------------------------------------------
/otp/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cromwellryan/sweetelixir/ba29a77360211ab227d8726abfd95cf0489b9935/otp/images/.DS_Store
--------------------------------------------------------------------------------
/otp/images/supervisor_tree.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cromwellryan/sweetelixir/ba29a77360211ab227d8726abfd95cf0489b9935/otp/images/supervisor_tree.gif
--------------------------------------------------------------------------------
/otp/images/supervisor_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cromwellryan/sweetelixir/ba29a77360211ab227d8726abfd95cf0489b9935/otp/images/supervisor_tree.png
--------------------------------------------------------------------------------
/otp/images/supervisor_tree.pxm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cromwellryan/sweetelixir/ba29a77360211ab227d8726abfd95cf0489b9935/otp/images/supervisor_tree.pxm
--------------------------------------------------------------------------------
/otp/src/customstack.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 2000 ->
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 |
--------------------------------------------------------------------------------
/otp/src/customstack.md:
--------------------------------------------------------------------------------
1 | ## Show them what it will look like
2 |
3 | ### Start our customserver
4 | ```bash
5 | $> iex customstack.exs
6 | ```
7 | ```elixir
8 | iex> Stack.CustomServer.start []
9 | iex> sned :custom_server, { :push, 10 }
10 | iex> send :custom_server, { :self, :pop }
11 | ```
12 |
13 | ###iex manual receive
14 |
15 | ```elixir
16 | receive do
17 | val -> IO.puts "Pop resulted in #{val}"
18 | end
19 | ```
20 |
21 | ## Guided customstack.exs
22 | ### Module receive loop with after outputting "nothing received"
23 |
24 | ```elixir
25 | defmodule Stack.CustomServer do
26 | def start(initial_stack) do
27 | spawn_link fn ->
28 | Process.register self, :custom_server
29 | listen initial_stack
30 | end
31 | end
32 |
33 | def listen(stack) do
34 | receive do
35 | after 2000 ->
36 | IO.puts "Nothing to do"
37 | listen stack
38 | end
39 | end
40 | end
41 | ```
42 |
43 | ### add receive push
44 |
45 | ```elixir
46 | {:push, value} -> listen([value|stack])
47 | ```
48 |
49 | ### run iex and push value (cast())
50 |
51 | ```elixir
52 | send :custom_server, { :push, 10 }
53 | ```
54 |
55 | ### add receive pop
56 |
57 | ```elixir
58 | {sender, :pop} -> handle_pop(sender, stack)
59 |
60 | #...
61 | def handle_pop(sender, [head|tail]) do
62 | send sender, head
63 | listen tail
64 | end
65 | ```
66 |
67 | ### run iex and pop value (call() - req/reply)
68 |
69 | ```elixir
70 | send :custom_server, {self, :pop}
71 | ```
72 |
73 | ### crash - lots of logic leads us to use OTP applications
74 |
--------------------------------------------------------------------------------
/otp/src/genserver_stack_example/.gitignore:
--------------------------------------------------------------------------------
1 | /ebin
2 | /deps
3 | erl_crash.dump
4 | *.ez
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/otp/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 | sender <- nil
29 | listen []
30 | end
31 | def handle_pop(sender, stack) do
32 | 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 |
--------------------------------------------------------------------------------
/otp/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/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/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 |
--------------------------------------------------------------------------------
/otp/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.11 or ~> 0.12",
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 |
--------------------------------------------------------------------------------
/otp/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/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 |
--------------------------------------------------------------------------------
/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 |
13 | ## Example Usage
14 |
15 | ### 1. Export required environment vairables for GateKeeper to make OAuth requests
16 |
17 | ```bash
18 | $ touch .env
19 |
20 | # .env
21 | export TWEET_CONSUMER_KEY=...
22 | export TWEET_CONSUMER_SECRET=...
23 | export TWEET_ACCESS_TOKEN=...
24 | export TWEET_ACCESS_TOKEN_SECRET=...
25 |
26 | $ source .env
27 | ```
28 |
29 |
30 | ### 2. Start GateKeeper node
31 |
32 | ```bash
33 | $ iex --name gatekeeper@127.0.0.1 --cookie foo -S mix
34 | iex(gatekeeper@127.0.0.1)1> TweetAggregator.GateKeeper.become_leader
35 | :yes
36 | ```
37 |
38 | ### 2. Start Aggregator node
39 |
40 | ```bash
41 | $ iex --name aggregator@127.0.0.1 --cookie foo -S mix
42 | iex(aggregator@127.0.0.1)1> TweetAggregator.Aggregator.become_leader
43 | :yes
44 | ```
45 |
46 | ### 3. Start client search node(s)
47 |
48 | ```bash
49 | $ iex --name client1@127.0.0.1 --cookie foo -S mix
50 | iex(client1@127.0.0.1)1> Node.connect :"aggregator@127.0.0.1"
51 | true
52 | NodeMonitor: aggregator@127.0.0.1 joined
53 |
54 | iex(client1@127.0.0.1)2> Node.connect :"gatekeeper@127.0.0.1"
55 | true
56 | NodeMonitor: gatekeeper@127.0.0.1 joined
57 |
58 | iex(client1@127.0.0.1)3> TweetAggregator.Search.Client.poll ["elixir"]
59 | New results
60 | Client: Got 1 result(s)
61 | ```
62 |
63 | ### 4. Watch Aggregator notifications on Aggregator node
64 |
65 | ```
66 | >> client1
67 | @noctarius2k: Elixir - The Ruby like Erlang VM Language http://t.co/FzCeytAv5t
68 | ```
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/otp/src/tweet_aggregator/lib/tweet_aggregator.ex:
--------------------------------------------------------------------------------
1 | defmodule TweetAggregator 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 | :ssl.start
8 | :inets.start
9 | TweetAggregator.Supervisor.start_link
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/otp/src/tweet_aggregator/lib/tweet_aggregator/aggregator.ex:
--------------------------------------------------------------------------------
1 | defmodule TweetAggregator.Aggregator do
2 | use GenServer.Behaviour
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 |
--------------------------------------------------------------------------------
/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.become_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/otp/src/tweet_aggregator/lib/tweet_aggregator/reporter.ex:
--------------------------------------------------------------------------------
1 | defmodule TweetAggregator.Reporter do
2 |
3 | end
4 |
--------------------------------------------------------------------------------
/otp/src/tweet_aggregator/lib/tweet_aggregator/search/client.ex:
--------------------------------------------------------------------------------
1 | defmodule TweetAggregator.Search.Client do
2 | alias TweetAggregator.GateKeeper
3 | alias TweetAggregator.Aggregator
4 | alias TweetAggregator.Search.Supervisor
5 |
6 | @base_url "https://api.twitter.com/1.1/"
7 | @limit 1
8 |
9 | defrecord Status, id: nil, text: "", username: nil, hash_tags: [], mentions: []
10 | defrecord Query, subscriber: nil, keywords: [], options: [], seen_ids: []
11 |
12 | def server_name, do: :"#{node}_search_server"
13 | def server_pid, do: Process.whereis(server_name)
14 |
15 | def poll(keywords, options \\ []) do
16 | Supervisor.start_link(server_name, Query.new(
17 | subscriber: spawn(fn -> do_poll end),
18 | keywords: keywords,
19 | options: options,
20 | ))
21 | send server_pid, :poll
22 | end
23 | defp do_poll do
24 | receive do
25 | {:results, results} ->
26 | IO.puts "Client: Notifying Aggregator of #{Enum.count results} result(s)"
27 | Enum.each results, &Aggregator.push(server_name, &1)
28 | end
29 | do_poll
30 | end
31 |
32 | def stop do
33 | Supervisor.stop(server_name)
34 | end
35 |
36 | def search(keywords, options \\ []) do
37 | count = Keyword.get options, :count, @limit
38 |
39 | get "search/tweets.json", count: count,
40 | q: keywords_to_query_param(keywords),
41 | result_type: 'recent'
42 | end
43 |
44 | def get(path, params) do
45 | url = String.to_char_list! process_url("search/tweets.json")
46 | access_token = String.to_char_list! GateKeeper.access_token
47 | access_secret = String.to_char_list! GateKeeper.access_token_secret
48 | consumer_key = String.to_char_list! GateKeeper.consumer_key
49 | consumer_secret = String.to_char_list! GateKeeper.consumer_secret
50 | consumer = {consumer_key, consumer_secret, :hmac_sha1}
51 |
52 | case :oauth.get(url, params, consumer, access_token, access_secret) do
53 | {:ok, response} -> {:ok, parse_response(response)}
54 | {:error, reason} -> {:error, reason}
55 | end
56 | end
57 |
58 | def parse_response(response) do
59 | Enum.map to_json(response)["statuses"], fn status ->
60 | Status.new id: status["id"],
61 | text: status["text"],
62 | username: status["user"]["screen_name"]
63 | end
64 | end
65 |
66 | defp to_json(response) do
67 | response
68 | |> elem(2)
69 | |> to_string
70 | |> :jsx.decode
71 | end
72 |
73 | defp keywords_to_query_param(keywords) do
74 | keywords |> Enum.join(" OR ") |> String.to_char_list!
75 | end
76 |
77 | defp process_url(url), do: @base_url <> url
78 | end
79 |
80 |
--------------------------------------------------------------------------------
/otp/src/tweet_aggregator/lib/tweet_aggregator/search/server.ex:
--------------------------------------------------------------------------------
1 | defmodule TweetAggregator.Search.Server do
2 | use GenServer.Behaviour
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 |
--------------------------------------------------------------------------------
/otp/src/tweet_aggregator/lib/tweet_aggregator/search/supervisor.ex:
--------------------------------------------------------------------------------
1 | defmodule TweetAggregator.Search.Supervisor do
2 | use Supervisor.Behaviour
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 |
--------------------------------------------------------------------------------
/otp/src/tweet_aggregator/lib/tweet_aggregator/supervisor.ex:
--------------------------------------------------------------------------------
1 | defmodule TweetAggregator.Supervisor do
2 | use Supervisor.Behaviour
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/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.11 or ~> 0.12",
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/otp/src/tweet_aggregator/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start
2 |
--------------------------------------------------------------------------------
/presenter.exs:
--------------------------------------------------------------------------------
1 | defmodule Presenter do
2 | @moduledoc """
3 | Presenter can be used in `iex` to open sections of the session.
4 |
5 | ## Example Usage
6 |
7 | $ iex presenter.exs
8 |
9 | iex> import Presenter; import Presenter.Basics
10 | nil
11 |
12 | iex> start
13 | nil
14 |
15 | iex> tools
16 | nil
17 |
18 | iex> Basics.pipeline
19 | nil
20 | """
21 | def start, do: open "README.md#getting-started"
22 | def setup, do: open "setup/README.md"
23 | def tools, do: open "tools/README.md"
24 |
25 | def basics, do: open "basics/README.md"
26 |
27 | defmodule Basics do
28 |
29 | def operators, do: open "operators"
30 | def types, do: open "types"
31 | def numbers, do: open "operators"
32 | def boolean, do: open "boolean"
33 | def atoms, do: open "atoms"
34 | def list, do: open "lists"
35 | def tuples, do: open "tuples"
36 | def strings, do: open "strings"
37 | def json_parser, do: System.cmd "open https://gist.github.com/cromwellryan/6349503/raw/0bac9dcbe6ea2c68f27d91c2a44034c7e4d3fb2c/parser.ex"
38 | def keywordlists, do: open "keyword-lists"
39 | def equality, do: open "equality"
40 | def functions, do: open "functions"
41 | def modules, do: open "modules"
42 |
43 | defp open(hash), do: Presenter.open "basics/README.md##{hash}"
44 | end
45 |
46 | defmodule Advanced do
47 | def mapred, do: open "map_reduce"
48 | def pattern, do: open "pattern_matching"
49 | def pipeline, do: open "pipeline"
50 | def processes, do: open "processes"
51 | def records, do: open "records_protocols"
52 |
53 | defp open(section), do: Presenter.open "advanced/#{section}.md"
54 | end
55 |
56 | def open(section) do
57 | System.cmd 'open https://github.com/cromwellryan/sweetelixir/blob/master/#{section}'
58 | nil
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/setup/README.md:
--------------------------------------------------------------------------------
1 | # Setting up your environment for Elixir support
2 |
3 | The only prerequisite for Elixir is Erlang, version R16B or later. Official [precompiled packages](https://www.erlang-solutions.com/downloads/download-erlang-otp)
4 | are available for most platforms.
5 |
6 | To check your installed erlang version:
7 |
8 | ```bash
9 | $ erl
10 | Erlang R16B (erts-5.10.1) ...
11 | ```
12 |
13 |
14 | # OSX
15 |
16 | ### Install via [homebrew](http://brew.sh/)
17 |
18 | ```bash
19 | $ brew update
20 | $ brew install erlang
21 | $ brew install elixir
22 | ```
23 |
24 |
25 | # Linux
26 |
27 | ### Fedora 17+ and Fedora Rawhide
28 |
29 | ```bash
30 | $ sudo yum -y install elixir
31 | ```
32 |
33 | ### Arch Linux (on AUR)
34 |
35 | ```bash
36 | $ yaourt -S elixir
37 | ```
38 |
39 | ### openSUSE (and SLES 11 SP3+)
40 |
41 | - Add Erlang devel repo with zypper ar -f obs://devel:languages:erlang/ erlang
42 | - Install Elixir: `$ zypper in elixir`
43 |
44 | ### Gentoo
45 |
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 | ### Direct Erlang installtion links
55 | - [Windows (32bit)](http://packages.erlang-solutions.com/erlang/esl-erlang/FLAVOUR_1_general/esl-erlang_16.b.2-1~windows_i386.exe)
56 | - [Windows (64bit)](http://packages.erlang-solutions.com/erlang/esl-erlang/FLAVOUR_1_general/esl-erlang_16.b.2-1~windows_amd64.exe)
57 |
58 | ### Install Elixir through [Chocolatey](http://chocolatey.org/)
59 |
60 | ```bash
61 | > cinst elixir
62 | ```
63 |
64 | # Test your setup
65 |
66 | Elixir ships with three executables, `iex`, `elixir`, and `elixirc`.
67 | Fire up `iex` to run the Elixir shell. In iex, or "Iteractive Elixir," we can
68 | execute any valid Elixir expression and see the evaluated result.
69 |
70 | ```bash
71 | Erlang R16B03 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
72 |
73 | Interactive Elixir (0.12.2) - press Ctrl+C to exit (type h() ENTER for help)
74 | $ iex
75 | iex(1)> IO.puts "Sweet Elixir!"
76 | Sweet Elixir!
77 | :ok
78 | iex(2)>
79 | ```
80 |
81 |
--------------------------------------------------------------------------------
/tools/Guardfile:
--------------------------------------------------------------------------------
1 | # gem install guard
2 | # gem install guard-shell
3 | # guard - watch the Magic™
4 |
5 | guard 'shell', :elixirc_bin => "/usr/local/elixirc" do
6 | watch(/(.+\.ex$)/) { |m| `elixirc #{m[0]}` }
7 | end
8 | guard 'shell', :elixir_bin => "/usr/local/elixir" do
9 | watch(/(.+\.exs)/) { |m| `elixir #{m[0]}` }
10 | end
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | # Elixir Tooling
2 |
3 | Elixir ships with four binaries for compilation, project building, and more.
4 |
5 | - `iex` a shell for evaluating elixir expressions and experimenting with
6 | your source code in real-time.
7 | - `elixir` executes compiled elixir files or elixir "scripts" (.exs files)
8 | - `elixirc` elixir compiler
9 | - `mix` elixir's fantastic build tool for project creation,
10 | dependency management, tasks, and more.
11 |
12 |
13 | ## Interactive Elixir - The Elixir Shell
14 |
15 | `iex` is an essential tool for exploring language features and quickly
16 | experimenting with code. Any valid Elixir expression can be evaluated and
17 | using the `h` helper function provides instant access to module documentation.
18 |
19 |
20 | $ iex
21 | Interactive Elixir (0.11.2) - press Ctrl+C to exit (type h() ENTER for help)
22 | iex(1)> 1 + 1
23 | 2
24 | iex(2)> h Enum.first
25 |
26 | def first(collection)
27 |
28 | Returns the first item in the collection or nil otherwise.
29 |
30 | Examples
31 |
32 | ┃ iex> Enum.first([])
33 | ┃ nil
34 | ┃ iex> Enum.first([1, 2, 3])
35 | ┃ 1
36 |
37 |
38 | iex(3)>
39 |
40 |
41 | The `c` helper function compiles files and loads them into the shell.
42 |
43 | ```elixir
44 | #hello.ex
45 | defmodule Hello do
46 | def world do
47 | IO.puts "Hello Word!"
48 | end
49 | end
50 | ```
51 |
52 | iex(2)> c "hello.ex"
53 | [Hello]
54 | iex(3)> Hello.world
55 | Hello Word!
56 | :ok
57 | iex(4)>
58 |
59 |
60 | ## Creating your first project with mix
61 |
62 | $ mix new hello_elixir
63 | * creating README.md
64 | * creating .gitignore
65 | * creating mix.exs
66 | * creating lib
67 | * creating lib/hello_elixir.ex
68 | * creating lib/hello_elixir
69 | * creating lib/hello_elixir/supervisor.ex
70 | * creating test
71 | * creating test/test_helper.exs
72 | * creating test/hello_elixir_test.exs
73 |
74 | Your mix project was created successfully.
75 | You can use mix to compile it, test it, and more:
76 |
77 | cd hello_elixir
78 | mix compile
79 | mix test
80 |
81 | Run `mix help` for more information.
82 |
83 |
84 | Mix creates a directory structure and application manifest that follows
85 | Erlang/OTP conventions and plays nicely with Erlang applications.
86 |
87 | - Source code lives in `lib/hello_elixir/src/`. Any `.ex` file you place in lib
88 | or subdirectories of lib will be automatically compiled and loaded by mix.
89 | - Test are defined as *elixir scripts* with `.exs` extensions in the `src/test`
90 | directory. Any `.exs` file loaded within test and test subdirectories will be
91 | automatically included in the test suite.
92 | - `mix.exs` contains your projects configuration and is where you define your
93 | dependencies.
94 |
95 |
96 |
97 | ## Exploration
98 |
99 | To see what mix has to offer, simply invoke `mix help` and start exploring.
100 | Most useful commands include:
101 |
102 | - `mix deps.get` Retrieve and compile all dependencies
103 | - `mix run` Runs your project, compiling sources where neccessary
104 | - `mix test` Runs all tests located in `src/test/*/**.exs`
105 | - `mix test [test_file_path]` - Runs a specific test
106 | - `iex -S mix` Loads Elixir's interactive shell with all mix dependencies and
107 | is incredibly useful for experimenting your project code
108 |
109 |
--------------------------------------------------------------------------------
/tools/src/hello_elixir/.gitignore:
--------------------------------------------------------------------------------
1 | /_build
2 | /deps
3 | erl_crash.dump
4 | *.ez
5 |
--------------------------------------------------------------------------------
/tools/src/hello_elixir/README.md:
--------------------------------------------------------------------------------
1 | # HelloElixir
2 |
3 | ** TODO: Add description **
4 |
--------------------------------------------------------------------------------
/tools/src/hello_elixir/lib/hello_elixir.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloElixir 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 | HelloElixir.Supervisor.start_link
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/tools/src/hello_elixir/lib/hello_elixir/supervisor.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloElixir.Supervisor do
2 | use Supervisor.Behaviour
3 |
4 | def start_link do
5 | :supervisor.start_link(__MODULE__, [])
6 | end
7 |
8 | def init([]) do
9 | children = [
10 | # Define workers and child supervisors to be supervised
11 | # worker(HelloElixir.Worker, [])
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 |
--------------------------------------------------------------------------------
/tools/src/hello_elixir/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloElixir.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [ app: :hello_elixir,
6 | version: "0.0.1",
7 | elixir: "~> 0.11 or ~> 0.12",
8 | deps: deps ]
9 | end
10 |
11 | # Configuration for the OTP application
12 | def application do
13 | [mod: { HelloElixir, [] }]
14 | end
15 |
16 | # Returns the list of dependencies in the format:
17 | # { :foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1" }
18 | #
19 | # To specify particular versions, regardless of the tag, do:
20 | # { :barbat, "~> 0.1", github: "elixir-lang/barbat.git" }
21 | defp deps do
22 | []
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/tools/src/hello_elixir/test/hello_elixir_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloElixirTest do
2 | use ExUnit.Case
3 |
4 | test "the truth" do
5 | assert(true)
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/tools/src/hello_elixir/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start
2 |
--------------------------------------------------------------------------------