├── README.md └── code ├── anonymous ├── add_subtract.exs └── hello.exs ├── composition └── composition.exs ├── concurrency └── nodes │ ├── simple_chat │ ├── .gitignore │ ├── README.md │ ├── config │ │ └── config.exs │ ├── lib │ │ ├── simple_chat.ex │ │ └── simple_chat │ │ │ ├── client.ex │ │ │ └── server.ex │ ├── mix.exs │ └── test │ │ ├── simple_chat_test.exs │ │ └── test_helper.exs │ └── slash_chat │ ├── .gitignore │ ├── README.md │ ├── config │ └── config.exs │ ├── lib │ ├── slash_chat.ex │ └── slash_chat │ │ ├── client.ex │ │ ├── command.ex │ │ └── server.ex │ ├── mix.exs │ └── test │ ├── slash_chat_test.exs │ └── test_helper.exs ├── currying └── currying.exs ├── enumerables └── enumerables.exs ├── erl_crash.dump ├── examples ├── coin_game.exs ├── coin_game.rb ├── pmap.exs ├── shopping_cart.exs └── shopping_cart.rb ├── higher_order ├── greater_than.exs └── times.exs ├── multiple_bodies ├── arithmetic.exs ├── fizzbuzz.exs ├── fizzbuzz.ipynb └── fizzbuzz.rb ├── otp ├── cache │ ├── fun_cache.exs │ └── gen_cache │ │ ├── .formatter.exs │ │ ├── .gitignore │ │ ├── README.md │ │ ├── config │ │ └── config.exs │ │ ├── lib │ │ ├── gen_cache.ex │ │ └── gen_cache │ │ │ ├── application.ex │ │ │ └── fun_cache.ex │ │ ├── mix.exs │ │ └── test │ │ ├── gen_cache_test.exs │ │ └── test_helper.exs ├── shopping_cart_genservers.exs └── shopping_cart_supervised.exs ├── partial_application └── partial_application.exs ├── pattern_matching ├── pattern_matching.exs └── tuples.exs ├── recursion ├── factorial.exs ├── forever.exs ├── headtail.exs ├── short_circuit.exs ├── square.exs ├── sum.exs └── walk_directory.exs └── side_effects ├── anonymous_solution.exs ├── mutable_bad_data.rb ├── named_functions.exs ├── pure_functions.exs ├── pure_functions.rb └── stateful_math.rb /README.md: -------------------------------------------------------------------------------- 1 | # A Taste of Functional Programming 2 | Material to introduce functional programming using the Elixir language 3 | 4 | ## Objectives 5 | * Exposure to functional concepts 6 | * Use functional parts of your existing language of choice you’ve never used before 7 | * Lead you to pursue a functional language more in-depth 8 | 9 | ## Paradigm evolution 10 | * Mathematics (lambda calculus) 11 | * Computer science 12 | * spawn of languages and paradigms 13 | * functional, procedural, imperative, declarative, object-oriented programming (OOP) 14 | * OOP overload... 15 | 16 | ## OOP limitations 17 | ``` 18 | [W]e’re going to be living in a multicore, distributed, concurrent — all the buzz words — world. 19 | The conventional models we’ve been doing, the OO stuff… is not going to survive in that 20 | kind of environment.” - Dave Thomas 21 | ``` 22 | ``` 23 | “OOP promised a cure for the scourge of software complexity. …its weaknesses have become 24 | increasingly apparent. Spreading state all over the place leads to concurrency issues 25 | and unpredictable side effects.” - Dave Thomas 26 | ``` 27 | ## Think Functional 28 | * **Functions** 29 | * Easy to reason about 30 | * reliable 31 | * pure 32 | * don't modify variables outside of scope 33 | * no side effects 34 | * deterministic (reproduciable results) 35 | * **Data transformation** 36 | * ie. Unix pipes - `cat foo.log | grep bar | wc -l` 37 | * **No side-effects** 38 | * Side effects are: 39 | * modifying state 40 | * has observable interaction with external functions 41 | * **Immutability** 42 | * Immutable data is known data 43 | * Data that is created is not changed 44 | * Copy and alter 45 | * Compilers can perform optimizations because of this 46 | * Garbage collectors are smart about this 47 | * Avoid race conditions 48 | * **Higher-order functions** 49 | * Functions can receive functions as arguments and return functions 50 | * **Where is my *for* loop?** 51 | * recursion 52 | * map, reduce, filter, reject, take, etc. 53 | 54 | ## Some (impure and pure) functional languages 55 | * LISP, Scheme, Clojure, Erlang, Scala, OCaml, Haskell, F#, Elm, Elixir 56 | 57 | # Elixir 58 | ``` 59 | “Elixir is a dynamic, functional language designed for building scalable and 60 | maintainable applications. Elixir leverages the Erlang VM, known for running low-latency, 61 | distributed and fault-tolerant systems, while also being successfully used in web development 62 | and the embedded software domain.” - http://elixir-lang.org 63 | ``` 64 | 65 | ## Approachable code examples that highlight functional concepts 66 | * ### Anonymous/First-class functions 67 | * https://github.com/kblake/functional-programming/tree/master/code/anonymous 68 | * ### Pattern Matching 69 | * Not an assignment operator but an assertion 70 | * `=` is a match operator 71 | * https://github.com/kblake/functional-programming/tree/master/code/pattern_matching 72 | * ### Multi-bodied functions 73 | * https://github.com/kblake/functional-programming/tree/master/code/multiple_bodies 74 | * ### Higher-order functions 75 | * Functions that can take functions in as arguments or functions that return functions 76 | * https://github.com/kblake/functional-programming/tree/master/code/higher_order 77 | * ### Side-effects & State 78 | * take data, copy it, alter copy, return it 79 | * always return the same output for any given input 80 | * https://github.com/kblake/functional-programming/tree/master/code/side_effects 81 | ``` 82 | Doing the maths: 83 | (value2 * (value1 + value3)) + value1 * value2 84 | 85 | if value1 = 4, value2 = 2, value3 = 0 86 | then result should be 16 87 | 88 | (2 * (4 + 0)) + 4 * 2 89 | 8 + 8 90 | 16 91 | ``` 92 | 93 | * ### Composition 94 | * Combining simple functions to make more complicated ones 95 | * The result of one function is passed to the next; the result of the last function call is the result of the whole 96 | * Piping 97 | * Transforming data 98 | * https://github.com/kblake/functional-programming/tree/master/code/composition 99 | * ### Enumerables 100 | * https://github.com/kblake/functional-programming/tree/master/code/enumerables 101 | * ### Partial function applications 102 | * Elixir does not have built-in **currying** like Haskell or Scala 103 | * https://github.com/kblake/functional-programming/tree/master/code/partial_application 104 | * ### Recursion 105 | * https://www.google.com/?gws_rd=ssl#safe=active&q=recursion 106 | * "Recursion in computer science is a method where the solution to a problem depends on solutions to smaller instances of the same problem (as opposed to iteration). The approach can be applied to many types of problems, and recursion is one of the central ideas of computer science." - https://en.wikipedia.org/wiki/Recursion_(computer_science) 107 | * Looping in traditional languages often times mutate and change data in sometimes unsuspecting ways 108 | * There is a lot of CS material out there that goes into depth on why this is true. Also you'll find recursion mentioned with terms such as Binary Trees and Binary Search Trees 109 | * **Tail-call Optimization** 110 | * If the very last thing a function does is call itself, there is no need to make the call. The runtime can jump back to the start of the function. The recursive call MUST be the last function executed. You may need to accumulate the results as you go. 111 | * Technique that allows the compiler to call a function without using any additional stack space 112 | * https://github.com/kblake/functional-programming/tree/master/code/recursion 113 | * ### Distributed 114 | * **Nodes** (distributed chat cluster) 115 | * Simple Chat - https://github.com/kblake/functional-programming/tree/master/code/concurrency/nodes/simple_chat 116 | * Steps for building the chat client: https://gist.github.com/kblake/63db44d13a933d6811d80d5ea5c2bd2f 117 | * Slash Chat - https://github.com/kblake/functional-programming/tree/master/code/concurrency/nodes/slash_chat 118 | * ### Examples 119 | * Parallel Map 120 | * https://github.com/kblake/functional-programming/blob/master/code/examples/pmap.exs 121 | * OOP to Functional 122 | * Shopping Cart 123 | * OOP: https://github.com/kblake/functional-programming/blob/master/code/examples/shopping_cart.rb 124 | * Functional: https://github.com/kblake/functional-programming/blob/master/code/examples/shopping_cart.exs 125 | * Coin game 126 | * OOP: https://github.com/kblake/functional-programming/blob/master/code/examples/coin_game.rb 127 | * Functional: https://github.com/kblake/functional-programming/blob/master/code/examples/coin_game.exs 128 | * ### OTP 129 | * Genservers 130 | * Shopping Cart 131 | * https://github.com/kblake/functional-programming/blob/master/code/otp/shopping_cart_genservers.exs 132 | * Cache 133 | * https://github.com/kblake/functional-programming/blob/master/code/otp/cache/gen_cache 134 | * Supervision 135 | * Dynamic Supervision of products 136 | * https://github.com/kblake/functional-programming/blob/master/code/otp/shopping_cart_supervised.exs 137 | 138 | ## Sources 139 | * http://nerd.kelseyinnis.com/blog/2012/12/17/slides-from-learning-functional-programming-without-growing-a-neckbeard/ 140 | * http://reactivex.io/learnrx/ 141 | * https://medium.com/@jugoncalves/functional-programming-should-be-your-1-priority-for-2015-47dd4641d6b9#.dzkjdoldo 142 | * http://blog.jenkster.com/2015/12/what-is-functional-programming.html 143 | * http://blog.jenkster.com/2015/12/which-programming-languages-are-functional.html 144 | * https://devchat.tv/react-native-radio/20-CycleJS-Cycle-Native-and-RXJS-with-Andre-Staltz 145 | * https://en.m.wikipedia.org/wiki/Functional_programming 146 | * http://www.vasinov.com/blog/16-months-of-functional-programming/ 147 | * http://phuu.net/2014/08/31/csp-and-transducers.html 148 | * https://medium.com/@cameronp/functional-programming-is-not-weird-you-just-need-some-new-patterns-7a9bf9dc2f77#.dmx784uc7 149 | * http://www.elmbark.com/2016/03/16/mainstream-elm-user-focused-design 150 | * https://github.com/hemanth/functional-programming-jargon 151 | * http://www.infoq.com/presentations/functional-declarative-style 152 | * http://michaelfeathers.typepad.com/michael_feathers_blog/2012/03/tell-above-and-ask-below-hybridizing-oo-and-functional-design.html 153 | * http://eloquentjavascript.net/05_higher_order.html 154 | * https://en.wikipedia.org/wiki/Function_composition_(computer_science) 155 | * http://version2beta.com/articles/functional_first_development/ 156 | * [Basics of functional Programming by Siddharth Kulkarni](https://youtu.be/iSs3LdUZziU) 157 | * https://engineering.pinterest.com/blog/re-architecting-pinterests-ios-app 158 | * https://medium.com/@sarahabimay/writing-oo-in-a-functional-state-of-mind-5d6f56052d23#.b63k5pqo2 159 | * http://blog.patrikstorm.com/function-currying-in-elixir 160 | * **TODO**: Read about Category Theory - https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/ 161 | * https://medium.com/@jugoncalves/functional-programming-should-be-your-1-priority-for-2015-47dd4641d6b9#.jzimhgjpv 162 | * https://blog.codeship.com/statefulness-in-elixir/ 163 | * http://www.pebra.net/blog/2015/12/24/fun-prog-1/ 164 | * http://novarac.com/recursion-in-the-wild-elixir/ 165 | * http://trevork-csc148.blogspot.com/2014/03/recursion.html 166 | * https://rosettacode.org/wiki/Walk_a_directory/Recursively#Elixir 167 | -------------------------------------------------------------------------------- /code/anonymous/add_subtract.exs: -------------------------------------------------------------------------------- 1 | add = fn num1, num2 -> num1 + num2 end 2 | IO.puts(add.(10, 15)) 3 | IO.puts(add.(12, 15)) 4 | IO.puts(add.(14, 19)) 5 | 6 | subtract = fn num1, num2 -> num1 - num2 end 7 | IO.puts(subtract.(15, 10)) 8 | -------------------------------------------------------------------------------- /code/anonymous/hello.exs: -------------------------------------------------------------------------------- 1 | greet = fn -> 2 | IO.puts("Hello World") 3 | IO.puts("blah") 4 | end 5 | 6 | greet.() 7 | -------------------------------------------------------------------------------- /code/composition/composition.exs: -------------------------------------------------------------------------------- 1 | # filing = prepare_filing(sales_tax(Orders.for_customers(DB.find_customers), 2016)) 2 | # inject_blank_urls(markdownify(task.formatted_task_text)).html_safe 3 | 4 | greeting = "hello world" 5 | upcase_greeting = String.upcase(greeting) 6 | loud_greeting = upcase_greeting <> "!" 7 | IO.puts(loud_greeting) 8 | 9 | # compose functions 10 | upcase_greeting = fn greeting -> String.upcase(greeting) end 11 | loud_greeting = fn greeting -> greeting <> "!" end 12 | shout_greeting = loud_greeting.(upcase_greeting.("hello world")) 13 | IO.puts(shout_greeting) 14 | 15 | # Unix 16 | # f | g 17 | 18 | # Haskell 19 | # foo = f . g 20 | 21 | # F#, Elixir, Elm 22 | # foo = f |> g 23 | 24 | shout_greeting = "hello world" |> upcase_greeting.() |> loud_greeting.() 25 | # shout_greeting = "hello world" 26 | # |> upcase_greeting.() 27 | # |> loud_greeting.() 28 | IO.puts(shout_greeting) 29 | 30 | names = ["jane", "jack", "john"] 31 | hip_names = names |> Enum.map(&String.capitalize/1) |> Enum.map(&(&1 <> ".ly")) 32 | 33 | # long-hand 34 | # hip_names2 = names 35 | # |> Enum.map(fn name -> String.capitalize(name) end) 36 | # |> Enum.map(fn name -> name <> ".ly" end) 37 | 38 | # In summary, you can things that look like this: 39 | # filing = prepare_filing(sales_tax(Orders.for_customers(DB.find_customers), 2016)) 40 | 41 | # To this: 42 | # filing = DB.find_customers 43 | # |> Orders.for_customers 44 | # |> sales_tax(2016) 45 | # |> prepare_filing 46 | 47 | # From this: 48 | # inject_blank_urls(markdownify(task.formatted_task_text)).html_safe 49 | 50 | # To this: 51 | # task 52 | # |> formatted_task_text 53 | # |> markdownify 54 | # |> inject_blank_urls 55 | # |> html_safe 56 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/README.md: -------------------------------------------------------------------------------- 1 | # SimpleChat 2 | 3 | **Basic chat app demonstrating processes and distribution features of Elixir (Erlang)** 4 | 5 | ## Usage 6 | 7 | 1. Server 8 | ``` 9 | iex --sname server -S mix 10 | iex(server@yourcomputername)1> SimpleChat.Server.start 11 | ``` 12 | 13 | 2. Client(s) - each client functioning in different terminal sessions 14 | ``` 15 | iex --sname client1 -S mix 16 | client1> SimpleChat.Client.join_server :"server@yourcomputername" 17 | 18 | iex --sname client2 -S mix 19 | client2> SimpleChat.Client.join_server :"server@yourcomputername" 20 | 21 | iex --sname client3 -S mix 22 | client3> SimpleChat.Client.join_server :"server@yourcomputername" 23 | 24 | client1> SimpleChat.Client.broadcast "sup sup" 25 | client2> client1: sup sup 26 | client3> client1: sup sup 27 | 28 | client1> SimpleChat.Client.friends 29 | client1> [:"client2@yourcomputername", :"client3@yourcomputername"] 30 | 31 | client1> SimpleChat.Client.direct_message :"client3@yourcomputername", "hey there, you!" 32 | client3> client1: hey there, you! 33 | 34 | ``` 35 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :simple_chat, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:simple_chat, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/lib/simple_chat.ex: -------------------------------------------------------------------------------- 1 | defmodule SimpleChat do 2 | end 3 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/lib/simple_chat/client.ex: -------------------------------------------------------------------------------- 1 | defmodule SimpleChat.Client do 2 | def join_server(server_id, cookie \\ :cookiemonster) do 3 | Node.set_cookie(Node.self(), cookie) 4 | pid = spawn(__MODULE__, :message_listener, []) 5 | Node.connect(server_id) 6 | :global.register_name(Node.self(), pid) 7 | end 8 | 9 | def server do 10 | :global.whereis_name(SimpleChat.Server.server_name()) 11 | end 12 | 13 | def friends do 14 | SimpleChat.Server.recipients_for(Node.self()) 15 | end 16 | 17 | def direct_message(friend, message) do 18 | send(server, {:private_message, Node.self(), friend, message}) 19 | :ok 20 | end 21 | 22 | def broadcast(message) do 23 | send(server, {:all, Node.self(), message}) 24 | :ok 25 | end 26 | 27 | def message_listener do 28 | receive do 29 | {sender, message} -> 30 | IO.puts("#{sender}: #{message}") 31 | message_listener 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/lib/simple_chat/server.ex: -------------------------------------------------------------------------------- 1 | defmodule SimpleChat.Server do 2 | @server_name :chat_server 3 | 4 | def start do 5 | pid = spawn(__MODULE__, :message_dispenser, []) 6 | :global.register_name(server_name, pid) 7 | end 8 | 9 | def server_name do 10 | @server_name 11 | end 12 | 13 | def recipients_for(sender) do 14 | recips = List.delete(:global.registered_names(), server_name) 15 | List.delete(recips, sender) 16 | end 17 | 18 | defp pid_for(registered_name) do 19 | :global.whereis_name(registered_name) 20 | end 21 | 22 | defp format_sender(sender) do 23 | sender 24 | |> Atom.to_string() 25 | |> String.split("@") 26 | |> List.first() 27 | end 28 | 29 | def message_dispenser do 30 | receive do 31 | {:all, sender, message} -> 32 | Enum.each(recipients_for(sender), fn friend -> 33 | send_message(friend, sender, message) 34 | end) 35 | 36 | message_dispenser 37 | 38 | {:private_message, sender, friend, message} -> 39 | send_message(friend, sender, message <> " (DM)") 40 | message_dispenser 41 | 42 | _ -> 43 | "The chat server does not know how to handle that message" 44 | end 45 | end 46 | 47 | defp send_message(friend, sender, message) do 48 | send(pid_for(friend), {format_sender(sender), message}) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule SimpleChat.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :simple_chat, 7 | version: "0.0.1", 8 | elixir: "~> 1.2", 9 | build_embedded: Mix.env() == :prod, 10 | start_permanent: Mix.env() == :prod, 11 | deps: deps 12 | ] 13 | end 14 | 15 | # Configuration for the OTP application 16 | # 17 | # Type "mix help compile.app" for more information 18 | def application do 19 | [applications: [:logger]] 20 | end 21 | 22 | # Dependencies can be Hex packages: 23 | # 24 | # {:mydep, "~> 0.3.0"} 25 | # 26 | # Or git/path repositories: 27 | # 28 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 29 | # 30 | # Type "mix help deps" for more examples and options 31 | defp deps do 32 | [] 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/test/simple_chat_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SimpleChatTest do 2 | use ExUnit.Case 3 | doctest SimpleChat 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /code/concurrency/nodes/simple_chat/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/README.md: -------------------------------------------------------------------------------- 1 | # SlashChat 2 | 3 | **Builds on the SimpleChat app to add commands to demonstrate pocesses in Elixir (Erlang)** 4 | 5 | ## Usage 6 | 7 | 1. Server 8 | 9 | iex --sname server -S mix 10 | iex(server@yourcomputername)1> SlashChat.Server.start 11 | 12 | 2. Client(s) - each client functioning in different terminal sessions 13 | 14 | iex --sname client1 -S mix 15 | clien1> SlashChat.Client.join_server :"server@yourcomputername" 16 | 17 | iex --sname client2 -S mix 18 | client2> SlashChat.Client.join_server :"server@yourcomputername" 19 | 20 | iex --sname client3 -S mix 21 | client3> SlashChat.Client.join_server :"server@yourcomputername" 22 | 23 | 3. Client(s) - can send a message to every other client 24 | 25 | client1> SlashChat.Client.send_message "/all sup sup" 26 | client2> client1: sup sup 27 | client3> client1: sup sup 28 | 29 | 4. Client(s) - can send a private message to another client 30 | 31 | client1> SlashChat.Client.send_message "/pm client2@yourcomputername sup sup" 32 | client2> client1: sup sup 33 | client3> no message 34 | 35 | 5. Client(s) - can get feedback about an invalid command 36 | 37 | client1> SlashChat.Client.send_message "/dance" 38 | client1> :chat_server: Invalid Command 39 | 40 | 6. Client(s) - can exit the network 41 | 42 | client1> SlashChat.Client.send_message "/exit" 43 | client2> client1: Has left the network 44 | client3> client1: Has left the network 45 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :simple_chat, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:simple_chat, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/lib/slash_chat.ex: -------------------------------------------------------------------------------- 1 | defmodule SlashChat do 2 | end 3 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/lib/slash_chat/client.ex: -------------------------------------------------------------------------------- 1 | defmodule SlashChat.Client do 2 | def join_server(server_id) do 3 | pid = spawn(__MODULE__, :message_listener, []) 4 | Node.connect(server_id) 5 | :global.register_name(Node.self(), pid) 6 | :global.sync() 7 | 8 | case server do 9 | :undefined -> :ok 10 | _ -> send_message("/join") 11 | end 12 | end 13 | 14 | def server do 15 | :global.whereis_name(SlashChat.Server.server_name()) 16 | end 17 | 18 | def send_message(message) do 19 | send(server, {Node.self(), message}) 20 | :ok 21 | end 22 | 23 | def message_listener do 24 | receive do 25 | {sender, message} -> 26 | IO.puts("#{sender}: #{message}") 27 | message_listener 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/lib/slash_chat/command.ex: -------------------------------------------------------------------------------- 1 | defmodule SlashChat.Command do 2 | defp strip_command(message) do 3 | message 4 | |> String.split(" ") 5 | |> List.delete_at(0) 6 | |> Enum.join(" ") 7 | end 8 | 9 | defp get_command(message) do 10 | message 11 | |> String.split(" ") 12 | |> List.first() 13 | end 14 | 15 | defp invalid_command(sender) do 16 | SlashChat.Server.send_message(sender, "Invalid command", SlashChat.Server.server_name()) 17 | end 18 | 19 | defp join_network(sender) do 20 | IO.puts(Atom.to_string(sender) <> " has joined.") 21 | SlashChat.Server.broadcast(sender, "has joined the network.") 22 | end 23 | 24 | defp leave_network(sender) do 25 | IO.puts(Atom.to_string(sender) <> " has left.") 26 | SlashChat.Server.broadcast(sender, "left the network.") 27 | SlashChat.Server.release(sender) 28 | end 29 | 30 | defp notify_all(sender, message) do 31 | SlashChat.Server.broadcast(sender, message) 32 | end 33 | 34 | defp private_message(sender, message) do 35 | [recipient | message_tail] = String.split(message, " ") 36 | SlashChat.Server.send_message(String.to_atom(recipient), Enum.join(message_tail, " "), sender) 37 | end 38 | 39 | def process(sender, message) do 40 | case get_command(message) do 41 | "/all" -> notify_all(sender, strip_command(message)) 42 | "/exit" -> leave_network(sender) 43 | "/join" -> join_network(sender) 44 | "/pm" -> private_message(sender, strip_command(message)) 45 | _ -> invalid_command(sender) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/lib/slash_chat/server.ex: -------------------------------------------------------------------------------- 1 | defmodule SlashChat.Server do 2 | @server_name :chat_server 3 | 4 | def start do 5 | pid = spawn(__MODULE__, :message_dispenser, []) 6 | :global.register_name(server_name, pid) 7 | end 8 | 9 | def server_name do 10 | @server_name 11 | end 12 | 13 | def recipients_for(sender) do 14 | recips = List.delete(:global.registered_names(), server_name) 15 | List.delete(recips, sender) 16 | end 17 | 18 | defp pid_for(registered_name) do 19 | :global.whereis_name(registered_name) 20 | end 21 | 22 | defp format_sender(sender) do 23 | sender 24 | |> Atom.to_string() 25 | |> String.split("@") 26 | |> List.first() 27 | end 28 | 29 | def send_message(recipient, message, sender) do 30 | send(pid_for(recipient), {format_sender(sender), message}) 31 | end 32 | 33 | def release(sender) do 34 | :global.unregister_name(sender) 35 | Node.disconnect(sender) 36 | end 37 | 38 | def broadcast(sender, message) do 39 | Enum.each(recipients_for(sender), fn node -> 40 | send(pid_for(node), {format_sender(sender), message}) 41 | end) 42 | end 43 | 44 | def message_dispenser do 45 | receive do 46 | {sender, message} -> 47 | cond do 48 | message =~ ~r/^\// -> SlashChat.Command.process(sender, message) 49 | true -> send_message(sender, "Invalid command", server_name) 50 | end 51 | 52 | message_dispenser 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule SlashChat.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :slash_chat, 7 | version: "0.0.1", 8 | elixir: "~> 1.2", 9 | build_embedded: Mix.env() == :prod, 10 | start_permanent: Mix.env() == :prod, 11 | deps: deps 12 | ] 13 | end 14 | 15 | # Configuration for the OTP application 16 | # 17 | # Type "mix help compile.app" for more information 18 | def application do 19 | [applications: [:logger]] 20 | end 21 | 22 | # Dependencies can be Hex packages: 23 | # 24 | # {:mydep, "~> 0.3.0"} 25 | # 26 | # Or git/path repositories: 27 | # 28 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 29 | # 30 | # Type "mix help deps" for more examples and options 31 | defp deps do 32 | [] 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/test/slash_chat_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SlashChatTest do 2 | use ExUnit.Case 3 | doctest SlashChat 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /code/concurrency/nodes/slash_chat/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /code/currying/currying.exs: -------------------------------------------------------------------------------- 1 | # Curry is the process of taking a function that has an arity (or number of arguements) of N 2 | # and turning it into a sequence of functions that have an arity of 1 3 | 4 | add = fn x -> 5 | fn y -> 6 | x + y 7 | end 8 | end 9 | 10 | # Tell the function to add 2 to the arguement 11 | add2 = add.(2) 12 | 13 | # Add 2 to 4 14 | add2.(4) 15 | # => 6 16 | -------------------------------------------------------------------------------- /code/enumerables/enumerables.exs: -------------------------------------------------------------------------------- 1 | # Don't have to write another for loop again :) 2 | 3 | # map 4 | nums = 1..10 5 | IO.inspect(Enum.map(nums, &(&1 * 2))) 6 | # IO.inspect nums |> Enum.map(&(&1 * 2)) 7 | 8 | # reduce 9 | IO.inspect(Enum.reduce(nums, fn num, acc -> num + acc end)) 10 | # IO.inspect nums |> Enum.reduce(fn(num, acc) -> num + acc end) 11 | 12 | # filter 13 | # IO.inspect Enum.filter(nums, fn(num) -> rem(num, 2) == 0 end) 14 | IO.inspect(Enum.filter(nums, &(rem(&1, 2) == 0))) 15 | # IO.inspect nums |> Enum.filter &(rem(&1,2) == 0) 16 | 17 | require Integer 18 | 19 | # combine via piping 20 | # first argument of function is the result of the previous call 21 | nums = 1..100 22 | 23 | nums 24 | # muliply all nums by 5 25 | |> Enum.map(&(&1 * 5)) 26 | # filter for those that are even 27 | |> Enum.filter(&Integer.is_even/1) 28 | # reduce by summing 29 | |> Enum.reduce(fn num, acc -> num + acc end) 30 | 31 | # loads 10 million integers up front then takes 5 32 | Enum.map(1..10_000_000, &(&1 + 1)) |> Enum.take(5) 33 | 34 | # Streams and lazy loading 35 | # take five up front and finish. no more processing 36 | Stream.map(1..10_000_000, &(&1 + 1)) |> Enum.take(5) 37 | 38 | # More fun 39 | # http://elixir-lang.org/docs/stable/elixir/Enum.html 40 | :ok 41 | -------------------------------------------------------------------------------- /code/examples/coin_game.exs: -------------------------------------------------------------------------------- 1 | defmodule Coin do 2 | defstruct side_showing: 0 3 | 4 | def flip(coin) do 5 | %Coin{coin | side_showing: :random.uniform(2) - 1} 6 | end 7 | 8 | def equal?(coin1, coin2) do 9 | coin1.side_showing == coin2.side_showing 10 | end 11 | 12 | def coin_side(coin) do 13 | case coin.side_showing do 14 | 1 -> "heads" 15 | 0 -> "tails" 16 | end 17 | end 18 | end 19 | 20 | # TODO: as exercise 21 | # wrap this in a loop asking user for input: "same" or "different" 22 | # if coins are the same and user guessed the same then they win 23 | # if coins are the different and user guessed different then they win 24 | defmodule CoinGame do 25 | def run do 26 | :random.seed(:os.timestamp()) 27 | 28 | coin1 = %Coin{} 29 | coin2 = %Coin{} 30 | 31 | coin1 = Coin.flip(coin1) 32 | coin2 = Coin.flip(coin2) 33 | 34 | IO.puts(Coin.coin_side(coin1)) 35 | IO.puts(Coin.coin_side(coin2)) 36 | 37 | if Coin.equal?(coin1, coin2) do 38 | IO.puts("They are the same") 39 | else 40 | IO.puts("They are different") 41 | end 42 | end 43 | end 44 | 45 | CoinGame.run() 46 | -------------------------------------------------------------------------------- /code/examples/coin_game.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | class Coin 4 | attr_reader :side_showing 5 | 6 | def initialize 7 | @side_showing = 0 8 | end 9 | 10 | def flip 11 | @side_showing = SecureRandom.random_number 2 12 | end 13 | 14 | def == coin 15 | coin.side_showing == @side_showing 16 | end 17 | 18 | def to_s 19 | return "heads" if @side_showing == 1 20 | "tails" 21 | end 22 | end 23 | 24 | 25 | coin1 = Coin.new 26 | coin2 = Coin.new 27 | 28 | coin1.flip 29 | coin2.flip 30 | 31 | puts coin1 32 | puts coin2 33 | 34 | if coin1 == coin2 35 | puts "They are the same" 36 | else 37 | puts "They are different" 38 | end 39 | -------------------------------------------------------------------------------- /code/examples/pmap.exs: -------------------------------------------------------------------------------- 1 | defmodule Parallel do 2 | def pmap(collection, func) do 3 | collection 4 | |> Enum.map(&Task.async(fn -> func.(&1) end)) 5 | |> Enum.map(&Task.await/1) 6 | end 7 | end 8 | 9 | Parallel.pmap(1..5_000_000, &(&1 * &1)) 10 | -------------------------------------------------------------------------------- /code/examples/shopping_cart.exs: -------------------------------------------------------------------------------- 1 | defmodule Product do 2 | defstruct name: "undefined", price: 0.0 3 | end 4 | 5 | defmodule Cart do 6 | defstruct products: [] 7 | 8 | def add_product(cart, product) do 9 | %Cart{cart | products: cart.products ++ List.wrap(product)} 10 | end 11 | 12 | def add_products(cart, products) do 13 | add_product(cart, products) 14 | end 15 | 16 | def contents(cart) do 17 | Enum.each(cart.products, fn product -> 18 | IO.puts("#{product.name}: $#{product.price}") 19 | end) 20 | end 21 | 22 | def sub_total(cart) do 23 | prices = Enum.map(cart.products, fn product -> product.price end) 24 | Enum.sum(prices) 25 | end 26 | end 27 | 28 | defmodule App do 29 | def run do 30 | coffee = %Product{name: "coffee", price: 16.25} 31 | creamer = %Product{name: "creamer", price: 3.5} 32 | sugar = %Product{name: "sugar", price: 2.25} 33 | chocolate_syrup = %Product{name: "chocolate syrup", price: 2.23} 34 | 35 | cart = %Cart{} 36 | cart = Cart.add_products(cart, [coffee, creamer, sugar, chocolate_syrup]) 37 | 38 | Cart.contents(cart) 39 | IO.puts("Subtotal: $#{Cart.sub_total(cart)}") 40 | end 41 | end 42 | 43 | App.run() 44 | -------------------------------------------------------------------------------- /code/examples/shopping_cart.rb: -------------------------------------------------------------------------------- 1 | class Product 2 | attr_reader :name, :price 3 | 4 | def initialize(name, price) 5 | @name = name 6 | @price = price 7 | end 8 | end 9 | 10 | class Cart 11 | def initialize 12 | @products = [] 13 | end 14 | 15 | def add_product(product) 16 | @products << product 17 | end 18 | 19 | def add_products(products) 20 | @products += products 21 | end 22 | 23 | def contents 24 | @products.each do |product| 25 | puts "#{product.name}: $#{product.price}" 26 | end 27 | end 28 | 29 | def sub_total 30 | @products.reduce(0) do |sum, product| 31 | product.price + sum 32 | end 33 | end 34 | end 35 | 36 | 37 | coffee = Product.new("coffee", 16.25) 38 | creamer = Product.new("creamer", 3.5) 39 | sugar = Product.new("sugar", 2.25) 40 | chocolate_syrup = Product.new("chocolate syrup", 2.23) 41 | 42 | cart = Cart.new 43 | cart.add_products([coffee, creamer, sugar, chocolate_syrup]) 44 | 45 | cart.contents 46 | puts "---------------------" 47 | puts "Subtotal: $#{cart.sub_total}" 48 | -------------------------------------------------------------------------------- /code/higher_order/greater_than.exs: -------------------------------------------------------------------------------- 1 | greather_than = fn x -> x > 10 end 2 | 3 | IO.puts(greather_than.(11)) 4 | IO.puts(greather_than.(8)) 5 | 6 | IO.inspect(Enum.map(1..100, greather_than)) 7 | -------------------------------------------------------------------------------- /code/higher_order/times.exs: -------------------------------------------------------------------------------- 1 | times = fn x -> fn y -> y * x end end 2 | 3 | times2 = times.(2) 4 | times3 = times.(3) 5 | times4 = times.(4) 6 | 7 | perform = fn fun, value -> fun.(value) end 8 | 9 | IO.puts(perform.(times2, 5)) 10 | IO.puts(perform.(times3, 5)) 11 | IO.puts(perform.(times4, 5)) 12 | 13 | nums = [10, 39, 31, 83] 14 | 15 | IO.inspect(Enum.map(nums, times2)) 16 | IO.inspect(Enum.map(nums, times3)) 17 | IO.inspect(Enum.map(nums, times4)) 18 | -------------------------------------------------------------------------------- /code/multiple_bodies/arithmetic.exs: -------------------------------------------------------------------------------- 1 | arithmetic = fn 2 | {:add, num1, num2} -> num1 + num2 3 | {:subtract, 10, 5} -> 0 4 | {:subtract, num1, num2} -> num1 - num2 5 | end 6 | 7 | # pattern matches arguments to call correct body 8 | IO.puts(arithmetic.({:add, 10, 5})) 9 | IO.puts(arithmetic.({:subtract, 10, 5})) 10 | IO.puts(arithmetic.({:subtract, 78, 8})) 11 | -------------------------------------------------------------------------------- /code/multiple_bodies/fizzbuzz.exs: -------------------------------------------------------------------------------- 1 | # Fizzbuzz with NO CONDITIONALS 2 | # For the numbers 1 - 100, print Fizz if it is divisible by 3, Buzz if it is divisible by 5, FizzBuzz if divisible by 3 and 5, and just print the number if none of those are true. 3 | 4 | whichfizz = fn 5 | 0, 0, _ -> "FizzBuzz" 6 | 0, _, _ -> "Fizz" 7 | _, 0, _ -> "Buzz" 8 | _, _, n -> n 9 | end 10 | 11 | fizzbuzz = fn n -> 12 | whichfizz.(rem(n, 3), rem(n, 5), n) 13 | end 14 | 15 | IO.inspect(Enum.map(1..100, fizzbuzz)) 16 | -------------------------------------------------------------------------------- /code/multiple_bodies/fizzbuzz.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 72, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "1\n", 15 | "2\n", 16 | "Fizz\n", 17 | "4\n", 18 | "Buzz\n", 19 | "Fizz\n", 20 | "7\n", 21 | "8\n", 22 | "Fizz\n", 23 | "Buzz\n", 24 | "11\n", 25 | "Fizz\n", 26 | "13\n", 27 | "14\n", 28 | "FizzBuzz\n", 29 | "16\n", 30 | "17\n", 31 | "Fizz\n", 32 | "19\n", 33 | "Buzz\n", 34 | "Fizz\n", 35 | "22\n", 36 | "23\n", 37 | "Fizz\n", 38 | "Buzz\n", 39 | "26\n", 40 | "Fizz\n", 41 | "28\n", 42 | "29\n", 43 | "FizzBuzz\n", 44 | "31\n", 45 | "32\n", 46 | "Fizz\n", 47 | "34\n", 48 | "Buzz\n", 49 | "Fizz\n", 50 | "37\n", 51 | "38\n", 52 | "Fizz\n", 53 | "Buzz\n", 54 | "41\n", 55 | "Fizz\n", 56 | "43\n", 57 | "44\n", 58 | "FizzBuzz\n", 59 | "46\n", 60 | "47\n", 61 | "Fizz\n", 62 | "49\n", 63 | "Buzz\n", 64 | "Fizz\n", 65 | "52\n", 66 | "53\n", 67 | "Fizz\n", 68 | "Buzz\n", 69 | "56\n", 70 | "Fizz\n", 71 | "58\n", 72 | "59\n", 73 | "FizzBuzz\n", 74 | "61\n", 75 | "62\n", 76 | "Fizz\n", 77 | "64\n", 78 | "Buzz\n", 79 | "Fizz\n", 80 | "67\n", 81 | "68\n", 82 | "Fizz\n", 83 | "Buzz\n", 84 | "71\n", 85 | "Fizz\n", 86 | "73\n", 87 | "74\n", 88 | "FizzBuzz\n", 89 | "76\n", 90 | "77\n", 91 | "Fizz\n", 92 | "79\n", 93 | "Buzz\n", 94 | "Fizz\n", 95 | "82\n", 96 | "83\n", 97 | "Fizz\n", 98 | "Buzz\n", 99 | "86\n", 100 | "Fizz\n", 101 | "88\n", 102 | "89\n", 103 | "FizzBuzz\n", 104 | "91\n", 105 | "92\n", 106 | "Fizz\n", 107 | "94\n", 108 | "Buzz\n", 109 | "Fizz\n", 110 | "97\n", 111 | "98\n", 112 | "Fizz\n", 113 | "Buzz\n" 114 | ] 115 | } 116 | ], 117 | "source": [ 118 | "def fizzbuzz(n):\n", 119 | " case n:\n", 120 | " match n if (n % 3) == 0 and (n % 5) == 0:\n", 121 | " print(\"FizzBuzz\")\n", 122 | " match n if (n % 3) == 0:\n", 123 | " print(\"Fizz\")\n", 124 | " match n if (n % 5) == 0:\n", 125 | " print(\"Buzz\")\n", 126 | " match n:\n", 127 | " print(n)\n", 128 | "\n", 129 | "range(1, 101) |> map$(fizzbuzz) |> list" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "metadata": { 136 | "collapsed": true 137 | }, 138 | "outputs": [], 139 | "source": [] 140 | } 141 | ], 142 | "metadata": { 143 | "kernelspec": { 144 | "display_name": "Coconut3", 145 | "language": "coconut", 146 | "name": "coconut3" 147 | }, 148 | "language_info": { 149 | "codemirror_mode": { 150 | "name": "python", 151 | "version": 3.6 152 | }, 153 | "file_extension": ".coco", 154 | "mimetype": "text/x-python3", 155 | "name": "coconut", 156 | "pygments_lexer": "coconut" 157 | } 158 | }, 159 | "nbformat": 4, 160 | "nbformat_minor": 0 161 | } 162 | -------------------------------------------------------------------------------- /code/multiple_bodies/fizzbuzz.rb: -------------------------------------------------------------------------------- 1 | # Fizzbuzz 2 | # For the numbers 1 - 100, print Fizz if it is divisible by 3, Buzz if it is divisible by 5, FizzBuzz if divisible by 3 and 5, and just print the number if none of those are true. 3 | 4 | whichfizz = -> (a, b, n) { 5 | if a == 0 && b == 0 6 | 'FizzBuzz' 7 | elsif a == 0 8 | 'Fizz' 9 | elsif b == 0 10 | 'Buzz' 11 | else 12 | n 13 | end 14 | } 15 | 16 | fizzbuzz = ->(n) { 17 | whichfizz.(n % 3, n % 5, n) 18 | } 19 | 20 | p (1..100).map(&fizzbuzz) 21 | -------------------------------------------------------------------------------- /code/otp/cache/fun_cache.exs: -------------------------------------------------------------------------------- 1 | defmodule FunCache do 2 | def init do 3 | %{} 4 | end 5 | 6 | def add(cache, key, value) do 7 | Map.put(cache, key, value) 8 | end 9 | 10 | def get(cache, key) do 11 | cache[key] 12 | end 13 | 14 | def remove(cache, key) do 15 | Map.delete(cache, key) 16 | end 17 | end 18 | 19 | cache = FunCache.init() 20 | IO.inspect(cache) 21 | 22 | cache = FunCache.add(cache, "foo", "bar") 23 | cache = FunCache.add(cache, "baz", "lala") 24 | IO.inspect(cache) 25 | 26 | IO.puts(FunCache.get(cache, "foo")) 27 | 28 | cache = FunCache.remove(cache, "foo") 29 | IO.inspect(cache) 30 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | gen_cache-*.tar 24 | 25 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/README.md: -------------------------------------------------------------------------------- 1 | # GenCache 2 | 3 | **Simple Cache using a GenServer to maintain state** 4 | 5 | ## Usage 6 | 7 | ```elixir 8 | iex -S mix 9 | 10 | > GenCache.contents() 11 | %{} 12 | 13 | > GenCache.add("id1", "foo") 14 | > GenCache.add("id1", "bar") 15 | 16 | > GenCache.contents() 17 | %{"id1" => "foo", "id2" => "bar"} 18 | 19 | > GenCache.remove("id1") 20 | > GenCache.contents() 21 | %{"id2" => "bar"} 22 | ``` -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # third-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :gen_cache, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:gen_cache, :key) 18 | # 19 | # You can also configure a third-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env()}.exs" 31 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/lib/gen_cache.ex: -------------------------------------------------------------------------------- 1 | defmodule GenCache do 2 | use GenServer 3 | alias GenCache.FunCache 4 | 5 | def start_link(_) do 6 | GenServer.start_link(__MODULE__, [], name: :gen_cache) 7 | end 8 | 9 | def contents do 10 | GenServer.call(:gen_cache, :contents) 11 | end 12 | 13 | def add(id, name) do 14 | GenServer.cast(:gen_cache, {:add, id, name}) 15 | end 16 | 17 | def get(id) do 18 | GenServer.call(:gen_cache, {:get, id}) 19 | end 20 | 21 | def remove(id) do 22 | GenServer.cast(:gen_cache, {:remove, id}) 23 | end 24 | 25 | def init(_args) do 26 | {:ok, FunCache.init()} 27 | end 28 | 29 | def handle_call(:contents, _from, cache) do 30 | {:reply, cache, cache} 31 | end 32 | 33 | def handle_call({:get, id}, _from, cache) do 34 | {:reply, FunCache.get(cache, id), cache} 35 | end 36 | 37 | def handle_cast({:remove, id}, cache) do 38 | updated_cache = FunCache.remove(cache, id) 39 | {:noreply, updated_cache} 40 | end 41 | 42 | def handle_cast({:add, id, name}, cache) do 43 | updated_cache = FunCache.add(cache, id, name) 44 | {:noreply, updated_cache} 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/lib/gen_cache/application.ex: -------------------------------------------------------------------------------- 1 | defmodule GenCache.Application do 2 | @moduledoc false 3 | 4 | use Application 5 | 6 | def start(_type, _args) do 7 | children = [ 8 | {GenCache, []} 9 | ] 10 | 11 | opts = [strategy: :one_for_one, name: GenCache.Supervisor] 12 | Supervisor.start_link(children, opts) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/lib/gen_cache/fun_cache.ex: -------------------------------------------------------------------------------- 1 | defmodule GenCache.FunCache do 2 | def init do 3 | %{} 4 | end 5 | 6 | def add(cache, key, value) do 7 | Map.put(cache, key, value) 8 | end 9 | 10 | def get(cache, key) do 11 | cache[key] 12 | end 13 | 14 | def remove(cache, key) do 15 | Map.delete(cache, key) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule GenCache.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :gen_cache, 7 | version: "0.1.0", 8 | elixir: "~> 1.8", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger], 18 | mod: {GenCache.Application, []} 19 | ] 20 | end 21 | 22 | # Run "mix help deps" to learn about dependencies. 23 | defp deps do 24 | [ 25 | # {:dep_from_hexpm, "~> 0.3.0"}, 26 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/test/gen_cache_test.exs: -------------------------------------------------------------------------------- 1 | defmodule GenCacheTest do 2 | use ExUnit.Case 3 | doctest GenCache 4 | 5 | test "cache gets initialized" do 6 | GenCache.start_link() 7 | assert GenCache.contents() == %{} 8 | end 9 | 10 | test "add to cache" do 11 | GenCache.start_link() 12 | GenCache.add("1234", "Karmen Blake") 13 | assert GenCache.contents() == %{"1234" => "Karmen Blake"} 14 | end 15 | 16 | test "get a value with given key" do 17 | GenCache.start_link() 18 | GenCache.add("1234", "Karmen Blake") 19 | assert GenCache.get("1234") == "Karmen Blake" 20 | end 21 | 22 | test "remove a value" do 23 | GenCache.start_link() 24 | GenCache.add("1234", "Karmen Blake") 25 | GenCache.remove("1234") 26 | assert GenCache.contents() == %{} 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /code/otp/cache/gen_cache/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /code/otp/shopping_cart_genservers.exs: -------------------------------------------------------------------------------- 1 | defmodule Product do 2 | use GenServer 3 | 4 | def new(name, price) do 5 | {:ok, product} = GenServer.start_link(__MODULE__, [name, price]) 6 | product 7 | end 8 | 9 | def init(args) do 10 | [name, price] = args 11 | {:ok, %{name: name, price: price}} 12 | end 13 | 14 | def data(pid), do: GenServer.call(pid, :data) 15 | def handle_call(:data, _from, state), do: {:reply, state, state} 16 | 17 | def info(product) do 18 | "#{product[:name]}: $#{product[:price]}" 19 | end 20 | end 21 | 22 | defmodule Cart do 23 | use GenServer 24 | 25 | # API 26 | def new() do 27 | GenServer.start_link(__MODULE__, :ok, name: :cart) 28 | end 29 | 30 | def add_product(product) do 31 | GenServer.cast(:cart, {:add_product, product}) 32 | end 33 | 34 | def add_products(products) do 35 | GenServer.cast(:cart, {:add_products, products}) 36 | end 37 | 38 | def contents, do: GenServer.call(:cart, :contents) 39 | def sub_total, do: GenServer.call(:cart, :sub_total) 40 | 41 | # Callbacks 42 | def init(_args) do 43 | {:ok, []} 44 | end 45 | 46 | def handle_cast({:add_product, product}, products) do 47 | products = [product | products] 48 | {:noreply, products} 49 | end 50 | 51 | def handle_cast({:add_products, new_products}, _products) do 52 | {:noreply, new_products} 53 | end 54 | 55 | def handle_call(:contents, _from, products) do 56 | for pid <- products do 57 | pid 58 | |> Product.data() 59 | |> Product.info() 60 | |> IO.puts() 61 | end 62 | 63 | {:reply, products, products} 64 | end 65 | 66 | def handle_call(:sub_total, _from, products) do 67 | sum = 68 | products 69 | |> Enum.map(&Product.data(&1)) 70 | |> Enum.map(& &1.price) 71 | |> Enum.sum() 72 | 73 | {:reply, sum, products} 74 | end 75 | end 76 | 77 | coffee = Product.new("coffee", 16.25) 78 | creamer = Product.new("creamer", 3.5) 79 | sugar = Product.new("sugar", 2.25) 80 | chocolate_syrup = Product.new("chocolate syrup", 2.23) 81 | 82 | Cart.new() 83 | Cart.add_products([coffee, creamer, sugar, chocolate_syrup]) 84 | 85 | Cart.contents() 86 | IO.puts("---------------------") 87 | IO.puts("Subtotal: $#{Cart.sub_total()}") 88 | -------------------------------------------------------------------------------- /code/otp/shopping_cart_supervised.exs: -------------------------------------------------------------------------------- 1 | defmodule Product do 2 | use GenServer 3 | 4 | def new(name, price) do 5 | GenServer.start_link(__MODULE__, [name, price]) 6 | end 7 | 8 | def init(args) do 9 | [name, price] = args 10 | {:ok, %{name: name, price: price}} 11 | end 12 | 13 | def data(pid), do: GenServer.call(pid, :data) 14 | def handle_call(:data, _from, state), do: {:reply, state, state} 15 | 16 | def info(product) do 17 | "#{product[:name]}: $#{product[:price]}" 18 | end 19 | end 20 | 21 | defmodule ProductSupervisor do 22 | use DynamicSupervisor 23 | 24 | def start_link() do 25 | DynamicSupervisor.start_link(__MODULE__, :ok, name: __MODULE__) 26 | end 27 | 28 | def create_product(name, price) do 29 | spec = %{id: Product, start: {Product, :new, [name, price]}} 30 | DynamicSupervisor.start_child(__MODULE__, spec) 31 | end 32 | 33 | def init(:ok) do 34 | DynamicSupervisor.init(strategy: :one_for_one) 35 | end 36 | end 37 | 38 | ProductSupervisor.start_link() 39 | 40 | {:ok, coffee} = ProductSupervisor.create_product("coffee", 16.25) 41 | {:ok, creamer} = ProductSupervisor.create_product("creamer", 3.5) 42 | {:ok, sugar} = ProductSupervisor.create_product("sugar", 2.25) 43 | {:ok, chocolate_syrup} = ProductSupervisor.create_product("chocolate syrup", 2.23) 44 | 45 | IO.inspect(Product.data(coffee)) 46 | IO.inspect(Product.data(creamer)) 47 | IO.inspect(Product.data(sugar)) 48 | IO.inspect(Product.data(chocolate_syrup)) 49 | -------------------------------------------------------------------------------- /code/partial_application/partial_application.exs: -------------------------------------------------------------------------------- 1 | # partial application example 2 | 3 | # regular function with arity of 2 4 | sum = fn x, y -> x + y end 5 | IO.puts(sum.(5, 6)) 6 | 7 | # same function, partially applied 8 | sum = fn x -> 9 | fn y -> 10 | x + y 11 | end 12 | end 13 | 14 | IO.puts(sum.(18).(4)) 15 | 16 | add_five_to = sum.(5) 17 | 18 | IO.puts(add_five_to.(10)) 19 | IO.puts(add_five_to.(3)) 20 | -------------------------------------------------------------------------------- /code/pattern_matching/pattern_matching.exs: -------------------------------------------------------------------------------- 1 | # pattern matching statements 2 | a = 1 3 | 1 = a 4 | 5 | # a only matches with 1 6 | # does not match 7 | 2 = a 8 | 9 | list = [1, 2, 3] 10 | [a, b, c] = list 11 | IO.puts(a) 12 | IO.puts(b) 13 | IO.puts(c) 14 | 15 | # not assigning 16 | # does not match 17 | # [1,4,3] = list 18 | 19 | # pin operator which keeps it from being reassigned 20 | ^list = [1, 4, 3] 21 | 22 | # these do not match 23 | # [1,2,3] = [1,4,3] 24 | 25 | # ignore values 26 | # matching 27 | # underscores are ignored 28 | # "foo" matches "foo" 29 | [_, _, "foo"] = [1, 2, "foo"] 30 | # not a match 31 | [2, 1, "foo"] = [1, 2, "foo"] 32 | 33 | [_, _, "foo"] = [100, 3939, "foo"] 34 | 35 | # [1, _, "foo"] = [_, 3939, "foo"] 36 | -------------------------------------------------------------------------------- /code/pattern_matching/tuples.exs: -------------------------------------------------------------------------------- 1 | {:ok, value} = {:ok, 1000} 2 | IO.puts(value) 3 | 4 | {:error, message} = {:error, "Could not perform task!"} 5 | IO.puts(message) 6 | 7 | # does not match 8 | {:foo, value} = {:bar, "nope"} 9 | IO.puts(value) 10 | -------------------------------------------------------------------------------- /code/recursion/factorial.exs: -------------------------------------------------------------------------------- 1 | # Factorials in math are written like 6! and are the result of 6 * 5 * 4 * 3 * 2 * 1. 2 | 3 | defmodule FactorialNoTail do 4 | def factorial(0), do: 1 5 | 6 | def factorial(n) do 7 | n * factorial(n - 1) 8 | end 9 | end 10 | 11 | IO.puts(FactorialNoTail.factorial(10)) 12 | 13 | defmodule FactorialTailRecursive do 14 | def factorial(n), do: factorial(n, 1) 15 | 16 | defp factorial(0, accumulator), do: accumulator 17 | 18 | defp factorial(n, accumulator) do 19 | factorial(n - 1, accumulator * n) 20 | end 21 | end 22 | 23 | IO.puts(FactorialTailRecursive.factorial(10)) 24 | -------------------------------------------------------------------------------- /code/recursion/forever.exs: -------------------------------------------------------------------------------- 1 | defmodule Forever do 2 | def add_no_tail(number) do 3 | another_number = 10_000 4 | another_number + add_no_tail(number) 5 | end 6 | 7 | def add_tail_recursive(number) do 8 | another_number = 10_000 9 | add_tail_recursive(number + another_number) 10 | end 11 | end 12 | 13 | # Forever.add_no_tail(1) 14 | Forever.add_tail_recursive(1) 15 | -------------------------------------------------------------------------------- /code/recursion/headtail.exs: -------------------------------------------------------------------------------- 1 | [a, b, c] = [1, 2, 3] 2 | 3 | IO.puts(a) 4 | IO.puts(b) 5 | IO.puts(c) 6 | 7 | # head - first element 8 | # tail - is the rest 9 | 10 | [head | tail] = [1, 2, 3, 4] 11 | IO.puts(head) 12 | IO.inspect(tail) 13 | 14 | [head | tail] = tail 15 | IO.puts(head) 16 | IO.inspect(tail) 17 | 18 | [head | tail] = tail 19 | IO.puts(head) 20 | IO.inspect(tail) 21 | -------------------------------------------------------------------------------- /code/recursion/short_circuit.exs: -------------------------------------------------------------------------------- 1 | defmodule ExpensiveOperationWorker do 2 | def process([head | tail]) do 3 | operation_passed?(head) && process(tail) 4 | end 5 | 6 | def process([]), do: true 7 | 8 | defp operation_passed?(number) do 9 | IO.puts(number) 10 | rem(number, 2) != 0 11 | end 12 | end 13 | 14 | # 7 and 9 do not get evaluated 15 | # because processing short-circuits at 6 16 | IO.puts("Short circuit at 6") 17 | expensive_operations = [1, 3, 5, 6, 7, 9] 18 | ExpensiveOperationWorker.process(expensive_operations) 19 | 20 | # processes all since none are even 21 | IO.puts("\nAll processes run because no even number") 22 | expensive_operations = [1, 3, 5, 7, 9, 11] 23 | ExpensiveOperationWorker.process(expensive_operations) 24 | -------------------------------------------------------------------------------- /code/recursion/square.exs: -------------------------------------------------------------------------------- 1 | defmodule ListStuff do 2 | def square([]), do: [] 3 | def square([head | tail]), do: [head * head | square(tail)] 4 | end 5 | 6 | ListStuff.square([]) 7 | ListStuff.square([4, 5, 6]) 8 | -------------------------------------------------------------------------------- /code/recursion/sum.exs: -------------------------------------------------------------------------------- 1 | defmodule MathList do 2 | def sum_list(list), do: sum_list(list, 0) 3 | 4 | defp sum_list([], accumulator), do: accumulator 5 | 6 | defp sum_list([head | tail], accumulator) do 7 | sum_list(tail, accumulator + head) 8 | end 9 | end 10 | 11 | IO.puts(MathList.sum_list([3, 2, 6])) 12 | -------------------------------------------------------------------------------- /code/recursion/walk_directory.exs: -------------------------------------------------------------------------------- 1 | defmodule Directory do 2 | def walk(dir \\ ".") do 3 | Enum.each(File.ls!(dir), fn file -> 4 | unless hidden_file?(file) do 5 | file_name = "#{dir}/#{file}" 6 | 7 | # just a file, print it 8 | if File.dir?(file_name) do 9 | file_name |> directory_report 10 | # recursively step into next directory 11 | file_name |> walk 12 | else 13 | IO.puts(file_name) 14 | end 15 | end 16 | end) 17 | end 18 | 19 | defp directory_report(dir) do 20 | IO.puts("\n" <> dir <> " [DIR]") 21 | IO.puts(String.duplicate("-", String.length(dir) + 6)) 22 | end 23 | 24 | defp hidden_file?(file) do 25 | String.starts_with?(file, ".") 26 | end 27 | end 28 | 29 | # Directory.walk "/Users/karmenblake/Projects/elixir-stuff/functional-programming" 30 | Directory.walk() 31 | -------------------------------------------------------------------------------- /code/side_effects/anonymous_solution.exs: -------------------------------------------------------------------------------- 1 | # Doing the maths: 2 | # (value2 * (value1 + value3)) + value1 * value2 3 | # if value1 = 4, value2 = 2, value3 = 0 4 | # then result should be 16 5 | 6 | # (2 * (4 + 0)) + 4 * 2 7 | # 8 + 8 8 | # 16 9 | 10 | add = fn num1, num2 -> num1 + num2 end 11 | multiply = fn num1, num2 -> num1 * num2 end 12 | 13 | value1 = 4 14 | value2 = 2 15 | value3 = 0 16 | 17 | result = 18 | add.( 19 | multiply.(value2, add.(value1, value3)), 20 | multiply.(value1, value2) 21 | ) 22 | 23 | IO.puts(result) 24 | -------------------------------------------------------------------------------- /code/side_effects/mutable_bad_data.rb: -------------------------------------------------------------------------------- 1 | class BadBank 2 | attr_accessor :amount 3 | 4 | def initialize(amount) 5 | @amount = amount 6 | end 7 | 8 | def add_money(money) 9 | @amount += money 10 | puts "Adding money #{money} amount $#{amount}" 11 | sleep 0.5 12 | end 13 | 14 | def windfall! 15 | @amount += 10 if amount >= 100 16 | puts "Windfall $#{amount}" 17 | sleep 0.5 18 | amount 19 | end 20 | end 21 | 22 | bank = BadBank.new(10) 23 | puts "Karmen initial bank amount $#{bank.amount}" 24 | t1 = Thread.new { 8.times { bank.add_money(10) } } 25 | t2 = Thread.new { 8.times { bank.add_money(10) } } 26 | t3 = Thread.new { 8.times { bank.windfall! } } 27 | 28 | t1.join 29 | t2.join 30 | t3.join 31 | -------------------------------------------------------------------------------- /code/side_effects/named_functions.exs: -------------------------------------------------------------------------------- 1 | # Doing the maths: 2 | # (value2 * (value1 + value3)) + value1 * value2 3 | # if value1 = 4, value2 = 2, value3 = 0 4 | # then result should be 16 5 | 6 | # (2 * (4 + 0)) + 4 * 2 7 | # 8 + 8 8 | # 16 9 | 10 | defmodule FunctionalMath do 11 | def add(num1, num2) do 12 | num1 + num2 13 | end 14 | 15 | def multiply(num1, num2) do 16 | num1 * num2 17 | end 18 | end 19 | 20 | result = 21 | FunctionalMath.add( 22 | FunctionalMath.multiply(value2, FunctionalMath.add(value1, value3)), 23 | FunctionalMath.multiply(value1, value2) 24 | ) 25 | 26 | # ==> 16, correct answer 27 | IO.puts(result) 28 | -------------------------------------------------------------------------------- /code/side_effects/pure_functions.exs: -------------------------------------------------------------------------------- 1 | name = %{first: "jane", last: "doe"} 2 | 3 | pure = fn name -> 4 | # Map.merge does not alter original 5 | Map.merge(name, %{first: "sally"}) 6 | end 7 | 8 | new_name = pure.(name) 9 | 10 | IO.puts("Pure function - original is not altered") 11 | IO.inspect(name) 12 | IO.inspect(new_name) 13 | 14 | :ok 15 | -------------------------------------------------------------------------------- /code/side_effects/pure_functions.rb: -------------------------------------------------------------------------------- 1 | name = { first: "jane", last: "doe" } 2 | 3 | impure = -> name do 4 | # you can easily alter original 5 | name[:first] = "sally" 6 | name 7 | end 8 | 9 | new_name = impure.(name) 10 | 11 | puts "Impure function - original is altered :(" 12 | puts name 13 | puts new_name 14 | 15 | 16 | 17 | # Fix 18 | name = { first: "jane", last: "doe" } 19 | 20 | pure = -> name do 21 | name.merge({first: "sally"}) 22 | end 23 | 24 | new_name = pure.(name) 25 | 26 | puts 27 | puts "Pure function - original value is not altered :)" 28 | puts name 29 | puts new_name 30 | -------------------------------------------------------------------------------- /code/side_effects/stateful_math.rb: -------------------------------------------------------------------------------- 1 | # Doing the maths: 2 | # (value2 * (value1 + value3)) + value1 * value2 3 | # if value1 = 4, value2 = 2, value3 = 0 4 | # then result should be 16 5 | 6 | # (2 * (4 + 0)) + 4 * 2 7 | # 8 + 8 8 | # 16 9 | 10 | class Value 11 | attr_reader :result 12 | 13 | def initialize(initial_value) 14 | @result = initial_value 15 | end 16 | 17 | def add(other) 18 | @result += other.result 19 | end 20 | 21 | def multiply(other) 22 | @result *= other.result 23 | end 24 | end 25 | 26 | val1 = Value.new(4) 27 | val2 = Value.new(2) 28 | val3 = Value.new(0) 29 | 30 | r1 = val1.add(val3) 31 | r2 = val2.multiply(val1) 32 | r3 = val1.multiply(val2) 33 | puts r2 + r3 #--> 40 34 | 35 | 36 | 37 | val1 = Value.new(4) 38 | val2 = Value.new(2) 39 | val3 = Value.new(0) 40 | 41 | r1 = val1.multiply(val2) 42 | r2 = val1.add(val3) 43 | r3 = val1.multiply(val2) 44 | 45 | puts r1 + r3 # ---> 24 46 | 47 | 48 | class Value 49 | attr_reader :result 50 | 51 | def initialize(initial_value) 52 | @result = initial_value 53 | end 54 | 55 | def add(other) 56 | @result += other.result 57 | self 58 | end 59 | 60 | def multiply(other) 61 | @result *= other.result 62 | self 63 | end 64 | end 65 | 66 | val1 = Value.new(4) 67 | val2 = Value.new(2) 68 | val3 = Value.new(0) 69 | 70 | result = val1.add(val3).multiply(val2).add(val1.multiply(val2)).result 71 | 72 | puts result # --> 32 73 | --------------------------------------------------------------------------------