├── .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 | ![Supervisor Tree](images/supervisor_tree.png) 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 | --------------------------------------------------------------------------------