├── .gitignore ├── README.md ├── lab1 ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ └── config.exs ├── lib │ └── lab1.ex ├── mix.exs ├── solution.ex └── test │ ├── lab1_test.exs │ └── test_helper.exs ├── lab2 ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ └── config.exs ├── lib │ └── lab2.ex ├── mix.exs ├── solution.ex └── test │ ├── lab2_test.exs │ └── test_helper.exs ├── lab3 ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ └── config.exs ├── lib │ └── lab3.ex ├── mix.exs ├── solution.ex └── test │ ├── lab3_test.exs │ └── test_helper.exs ├── lab4 ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ └── config.exs ├── lib │ └── lab4.ex ├── mix.exs ├── solution.ex └── test │ ├── lab4_test.exs │ └── test_helper.exs ├── lab5 ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ └── config.exs ├── lib │ └── lab5.ex ├── mix.exs ├── solution.ex └── test │ ├── lab5_test.exs │ └── test_helper.exs ├── lab6 ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ └── config.exs ├── lib │ └── lab6.ex ├── mix.exs ├── solution.ex └── test │ ├── lab6_test.exs │ └── test_helper.exs └── schedule.md /.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 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ### Lab requirements 4 | 5 | 1. Install git 6 | - Windows: Install via https://git-scm.com 7 | - OS X: Install with package manager or https://git-scm.com 8 | - Linux: Install via distribution package manager or https://git-scm.com 9 | 10 | 2. Install Erlang & Elixir (at least 1.5) 11 | - See https://elixir-lang.org/install.html 12 | 13 | ##### Verify your installations 14 | 15 | Verify your installation by calling the executables `git`, `erl` and `iex` in 16 | your shell. Your Erlang installation needs to be on OTP 18 and Elixir on 1.5.0 17 | or higher. Check the version by running `iex`: 18 | 19 | ``` 20 | ~ λ iex 21 | Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false] 22 | 23 | Interactive Elixir (1.5.1) - press Ctrl+C to exit (type h() ENTER for help) 24 | iex(1)> 25 | ``` 26 | 27 | 28 | ### Installing a lab 29 | 30 | The repository for all labs is found at https://github.com/ericmj/workshop. 31 | 32 | 1. git clone from your console 33 | 34 | ```$ git clone https://github.com/ericmj/workshop.git``` 35 | 36 | 2. Enter the workshop directory 37 | 38 | ```$ cd workshop``` 39 | 40 | 2. Enter the directory for the current lab named labN 41 | 42 | ```$ cd labN``` 43 | 44 | 45 | ### Running tests 46 | 47 | * Run all tests for a project: `$ mix test` 48 | 49 | * Run all tests in a specific file: `$ mix test test/my_test.exs` 50 | 51 | * Run all tests on a specific file and line: `$ mix test test/my_test.exs:42` 52 | 53 | 54 | ### Lab links 55 | 56 | The individual labs can be found at the following URLs: 57 | 58 | *Lab 1* - Elixir basics: https://github.com/ericmj/workshop/tree/master/lab1 59 | 60 | *Lab 2* - Data structures and higher order functions: https://github.com/ericmj/workshop/tree/master/lab2 61 | 62 | *Lab 3* - Implement a chat: https://github.com/ericmj/workshop/tree/master/lab3 63 | 64 | *Lab 4* - Processes: https://github.com/ericmj/workshop/tree/master/lab4 65 | 66 | *Lab 5* - Chat with processes: https://github.com/ericmj/workshop/tree/master/lab5 67 | 68 | *Lab 6* - Chat with OTP: https://github.com/ericmj/workshop/tree/master/lab6 69 | -------------------------------------------------------------------------------- /lab1/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["{lib,test}/**/*.exs?", "solution.ex"] 3 | ] 4 | -------------------------------------------------------------------------------- /lab1/.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 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | -------------------------------------------------------------------------------- /lab1/README.md: -------------------------------------------------------------------------------- 1 | # Lab1 2 | 3 | ### Lab purpose 4 | 5 | This lab will teach you how to write and run simple Elixir scripts. You will 6 | learn how to write modules and functions. Additionally you will learn some 7 | functional programming concepts such as recursion, list handling and pattern 8 | matching. 9 | 10 | ### Lab instructions 11 | 12 | Run files in your shell with `$ elixir lib/lab1.ex`. 13 | 14 | Run the tests in your shell with `$ mix test`. Check the README in the base directory for more 15 | detailed instructions on how to run tests. 16 | 17 | You will notice that all your tests are skipped. Your job is to implement the functions so 18 | that the tests pass. Remove one `@tag :skip` line at a time and focus on having that test pass. 19 | Repeat until you have no more skipped tests. 20 | 21 | As you build your functions, you can require each separate file in 22 | in Elixir's interactive shell with `$ iex -r lib/lab1.ex`. Alternatively, you can load and 23 | start the full Mix project environment with `$ iex -S mix`. 24 | 25 | After changes to a module, that module can be reloaded in IEx with `r(Lab1)`. 26 | 27 | 1. Enter the existing directory `lab1`. There is a stub module `Lab1` in the file `lib/lab1.ex`, 28 | all tasks in this lab should be implemented as functions in this module. 29 | 30 | 2. Return the first and third element of a list. 31 | 32 | 3. Return all but the first three elements of a list. 33 | 34 | 4. Given a list of integers, add up all of the elements of that list. 35 | 36 | 5. Return the minimum value of a list with recursion. 37 | 38 | 6. Return the average value of a list with recursion. 39 | 40 | ##### For example 41 | 42 | ```elixir 43 | # lib/lab1.ex 44 | 45 | defmodule Lab1 do 46 | def second_element([_first, second | _rest]) do 47 | second 48 | end 49 | 50 | def second_element(_) do 51 | nil 52 | end 53 | end 54 | ``` 55 | 56 | ``` 57 | ~ λ iex -r lib/lab1.ex 58 | Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false] 59 | 60 | Interactive Elixir (1.5.1) - press Ctrl+C to exit (type h() ENTER for help) 61 | iex(1)> Lab1.second_element [:a, :b, :c] 62 | :b 63 | ``` 64 | 65 | 66 | ### Links 67 | 68 | Getting started guide: https://elixir-lang.org/getting-started/introduction.html 69 | 70 | API docs: https://hexdocs.pm/elixir/ 71 | 72 | 1. Modules https://elixir-lang.org/getting-started/modules.html 73 | 74 | 2. Recursion https://elixir-lang.org/getting-started/recursion.html 75 | 76 | 3. Basic types https://elixir-lang.org/getting-started/basic-types.html 77 | 78 | 4. `IO.inspect/1` https://hexdocs.pm/elixir/IO.html#inspect/2 79 | 80 | 81 | ### Solution ( no peeking :) ) 82 | 83 | See `solution.ex` in the `lab1` directory. 84 | -------------------------------------------------------------------------------- /lab1/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 :lab1, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:lab1, :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 | -------------------------------------------------------------------------------- /lab1/lib/lab1.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab1 do 2 | def first_and_third(_list) do 3 | raise "not implemented yet" 4 | end 5 | 6 | def drop_three(_list) do 7 | raise "not implemented yet" 8 | end 9 | 10 | def sum(_list) do 11 | raise "not implemented yet" 12 | end 13 | 14 | def min(_list) do 15 | raise "not implemented yet" 16 | end 17 | 18 | def average(_list) do 19 | raise "not implemented yet" 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lab1/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab1.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :lab1, 6 | version: "0.1.0", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type "mix help deps" for more examples and options 29 | defp deps do 30 | [] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lab1/solution.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab1 do 2 | def first_and_third([first, _second, third | _rest]) do 3 | {first, third} 4 | end 5 | 6 | def first_and_third(_) do 7 | nil 8 | end 9 | 10 | def drop_three([_first, _second, _third | rest]) do 11 | rest 12 | end 13 | 14 | def drop_three(_) do 15 | [] 16 | end 17 | 18 | def sum([head | tail]) do 19 | head + sum(tail) 20 | end 21 | 22 | def sum([]) do 23 | 0 24 | end 25 | 26 | def min([head | tail]) do 27 | do_min(tail, head) 28 | end 29 | 30 | defp do_min([head | tail], min) when head < min, 31 | do: do_min(tail, head) 32 | 33 | defp do_min([_head | tail], min), 34 | do: do_min(tail, min) 35 | 36 | defp do_min([], min), 37 | do: min 38 | 39 | def average([head | tail]) do 40 | do_average(tail, head, 1) 41 | end 42 | 43 | defp do_average([head | tail], sum, count) do 44 | do_average(tail, head + sum, count + 1) 45 | end 46 | 47 | defp do_average([], sum, count) do 48 | sum / count 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lab1/test/lab1_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab1Test do 2 | use ExUnit.Case 3 | 4 | @tag :skip 5 | test "task 2: first_and_third" do 6 | assert Lab1.first_and_third([1, 2, 3]) == {1, 3} 7 | assert Lab1.first_and_third([1, 2, 3, 4]) == {1, 3} 8 | assert Lab1.first_and_third([]) == nil 9 | end 10 | 11 | @tag :skip 12 | test "task 3: drop_three" do 13 | assert Lab1.drop_three([1, 2, 3, 4, 5]) == [4, 5] 14 | assert Lab1.drop_three([1, 2, 3]) == [] 15 | assert Lab1.drop_three([]) == [] 16 | end 17 | 18 | @tag :skip 19 | test "task 4: sum" do 20 | assert Lab1.sum([]) == 0 21 | assert Lab1.sum([5]) == 5 22 | assert Lab1.sum([5, 6, 7]) == 18 23 | end 24 | 25 | @tag :skip 26 | test "task 5: min" do 27 | assert Lab1.min([5]) == 5 28 | assert Lab1.min([1, 2, 3]) == 1 29 | assert Lab1.min([3, 2, 1]) == 1 30 | assert Lab1.min([2, 1, 3]) == 1 31 | assert Lab1.min([1, -1, 0]) == -1 32 | end 33 | 34 | @tag :skip 35 | test "task 6: average" do 36 | assert Lab1.average([5]) == 5 37 | assert Lab1.average([5, 5]) == 5 38 | assert Lab1.average([5, 6, 7]) == 6 39 | assert Lab1.average([]) == nil 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lab1/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /lab2/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["{lib,test}/**/*.exs?", "solution.ex"] 3 | ] 4 | -------------------------------------------------------------------------------- /lab2/.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 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | -------------------------------------------------------------------------------- /lab2/README.md: -------------------------------------------------------------------------------- 1 | # Lab2 2 | 3 | ### Lab purpose 4 | 5 | In the previous lab we learned the basics about running scripts and tests, 6 | pattern matching, and recursion. 7 | 8 | Pattern matching and recursion are powerful tools that Elixir developers often use. 9 | However, for many common tasks the standard library provides higher-level building blocks. 10 | 11 | In this we lab will introduce new data types and new forms of pattern matching 12 | with binaries. We are also going to use using higher order functions with the 13 | `Enum` module to do transformation on lists and build composed transformation 14 | with pipes. 15 | 16 | 17 | ### Lab instructions 18 | 19 | Like you did in the last lab, implement the stubbed out functions in `lib/lab2.ex` and run 20 | the tests with `$ mix test` to verify that the implementation is correct. 21 | 22 | 1. Return a list of all bytes in a binary. 23 | 24 | 2. Return the number of bits in a binary. 25 | 26 | 3. Double all numbers in a list of numbers using the `Enum` module. 27 | 28 | 4. Separate all odd and even numbers in a list. Return the result as a tuple containing a list of odd numbers and list of even numbers. 29 | 30 | 5. Return the three largest numbers from a list, in the order of the smallest number first. 31 | 32 | 6. Capitalize every word in a string. Each word in the string is separate by a space. *Tip*: use the `String.split/2` and `Enum.join/2` functions. Try to write this function as a single pipeline. 33 | 34 | 7. Return the second longest word in string. Each word in the string is separate by a space. If the string is too short (has less than two words), return `nil`. Tip: use `Enum.sort_by/3` to sort the list and `elem/2` to fetch a value from a tuple. Again, try to write this function as a single pipeline. 35 | 36 | 37 | ### Links 38 | 39 | Getting started guide: https://elixir-lang.org/getting-started/introduction.html 40 | 41 | API docs: https://hexdocs.pm/elixir/ 42 | 43 | 1. Binaries and bitstrings https://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html#binaries-and-bitstrings 44 | 45 | 2. `Enum` module https://hexdocs.pm/elixir/Enum.html 46 | 47 | 3. Pipes https://hexdocs.pm/elixir/Kernel.html#%7C%3E/2 48 | 49 | 4. Anonymous functions https://elixir-lang.org/getting-started/basic-types.html#anonymous-functions 50 | 51 | 5. Function capturing https://elixir-lang.org/getting-started/modules-and-functions.html#function-capturing 52 | 53 | 6. `String` module https://hexdocs.pm/elixir/String.html 54 | 55 | 56 | ### Solution ( no peeking :) ) 57 | 58 | See `solution.ex` in the `lab2` directory. 59 | -------------------------------------------------------------------------------- /lab2/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 :lab2, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:lab2, :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 | -------------------------------------------------------------------------------- /lab2/lib/lab2.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab2 do 2 | def binary_to_list(_binary) do 3 | raise "not implemented yet" 4 | end 5 | 6 | def num_bits(_bitstring) do 7 | raise "not implemented yet" 8 | end 9 | 10 | def double_all(_list) do 11 | raise "not implemented yet" 12 | end 13 | 14 | def odds_and_evens(_list) do 15 | raise "not implemented yet" 16 | end 17 | 18 | def three_largest(_list) do 19 | raise "not implemented yet" 20 | end 21 | 22 | def capitalize_all(_string) do 23 | raise "not implemented yet" 24 | end 25 | 26 | def second_longest(_string) do 27 | raise "not implemented yet" 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lab2/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab2.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :lab2, 6 | version: "0.1.0", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type "mix help deps" for more examples and options 29 | defp deps do 30 | [] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lab2/solution.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab2 do 2 | def binary_to_list(<<>>) do 3 | [] 4 | end 5 | 6 | def binary_to_list(<>) do 7 | [byte | binary_to_list(rest)] 8 | end 9 | 10 | def num_bits(bitstring) do 11 | do_num_bits(bitstring, 0) 12 | end 13 | 14 | defp do_num_bits(<<_bit::1, rest::bitstring>>, count) do 15 | do_num_bits(rest, count + 1) 16 | end 17 | 18 | defp do_num_bits(<<>>, count) do 19 | count 20 | end 21 | 22 | def double_all(list) do 23 | Enum.map(list, &(&1 + &1)) 24 | end 25 | 26 | def odds_and_evens(list) do 27 | odds = Enum.reject(list, &(rem(&1, 2) == 0)) 28 | evens = Enum.filter(list, &(rem(&1, 2) == 0)) 29 | {odds, evens} 30 | end 31 | 32 | def three_largest(list) do 33 | list 34 | |> Enum.sort(&>=/2) 35 | |> Enum.take(3) 36 | |> Enum.sort(&<=/2) 37 | end 38 | 39 | def capitalize_all(string) do 40 | string 41 | |> String.split(" ") 42 | |> Enum.map(&String.capitalize/1) 43 | |> Enum.join(" ") 44 | end 45 | 46 | def second_longest(string) do 47 | string 48 | |> String.split(" ") 49 | |> Enum.map(&{&1, String.length(&1)}) 50 | |> Enum.sort_by(fn {_string, length} -> length end, &>=/2) 51 | |> Enum.at(1, {nil, 0}) 52 | |> elem(0) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lab2/test/lab2_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab2Test do 2 | use ExUnit.Case 3 | 4 | @tag :skip 5 | test "task 1: binary_to_list" do 6 | assert Lab2.binary_to_list(<<1, 2, 3>>) == [1, 2, 3] 7 | assert Lab2.binary_to_list(<<>>) == [] 8 | assert Lab2.binary_to_list(<<10::16, 300::16>>) == [0, 10, 1, 44] 9 | end 10 | 11 | @tag :skip 12 | test "task 2: num_bits" do 13 | assert Lab2.num_bits(<<1, 2, 3>>) == 24 14 | assert Lab2.num_bits(<<>>) == 0 15 | assert Lab2.num_bits(<<10::16, 300::16>>) == 32 16 | assert Lab2.num_bits(<<10::4>>) == 4 17 | assert Lab2.num_bits(<<12::7, 10::4>>) == 11 18 | end 19 | 20 | @tag :skip 21 | test "task 3: double_all" do 22 | assert Lab2.double_all([]) == [] 23 | assert Lab2.double_all([1, 2, 3]) == [2, 4, 6] 24 | assert Lab2.double_all([12, 0, -20]) == [24, 0, -40] 25 | assert Lab2.double_all([42.53]) == [85.06] 26 | end 27 | 28 | @tag :skip 29 | test "task 4: odds_and_evens" do 30 | assert Lab2.odds_and_evens([]) == {[], []} 31 | assert Lab2.odds_and_evens([1, 3, 5]) == {[1, 3, 5], []} 32 | assert Lab2.odds_and_evens([2, 4, 6]) == {[], [2, 4, 6]} 33 | assert Lab2.odds_and_evens([6, 5, 4, 3, 2, 1]) == {[5, 3, 1], [6, 4, 2]} 34 | end 35 | 36 | @tag :skip 37 | test "task 5: three_largest" do 38 | assert Lab2.three_largest([]) == [] 39 | assert Lab2.three_largest([4, 9, 6, 3, 1]) == [4, 6, 9] 40 | assert Lab2.three_largest([-7, 3, 0, 9, 3, 2]) == [3, 3, 9] 41 | assert Lab2.three_largest([5, -2]) == [-2, 5] 42 | end 43 | 44 | @tag :skip 45 | test "task 6: capitalize_all" do 46 | assert Lab2.capitalize_all("") == "" 47 | assert Lab2.capitalize_all("foo bar") == "Foo Bar" 48 | assert Lab2.capitalize_all("FOO BAR") == "Foo Bar" 49 | assert Lab2.capitalize_all("elixirconf US 2017") == "Elixirconf Us 2017" 50 | end 51 | 52 | @tag :skip 53 | test "task 7: second_longest" do 54 | assert Lab2.second_longest("fo bar") == "fo" 55 | assert Lab2.second_longest("foo ba") == "ba" 56 | assert Lab2.second_longest("foo ba") == "ba" 57 | assert Lab2.second_longest("welcome to elixirconf 2017") == "welcome" 58 | assert Lab2.second_longest("") == nil 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lab2/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /lab3/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["{lib,test}/**/*.exs?", "solution.ex"] 3 | ] 4 | -------------------------------------------------------------------------------- /lab3/.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 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | -------------------------------------------------------------------------------- /lab3/README.md: -------------------------------------------------------------------------------- 1 | # Lab3 2 | 3 | ### Lab purpose 4 | 5 | In this lab, we will implement a chat room. Elixir is an immutable language, which means that 6 | actions don't modify objects. Instead, we will have functions that transform our chat room data 7 | structure and return a new chat room, leaving the original unchanged. 8 | 9 | The chat room data structure, expressed as an Elixir struct `%Lab3.Chat{}` in our 10 | code, has a single field `:members`. The `:members` field holds a map where the 11 | key is the user name and the value is the messages to that user, `%{username => messages}`. 12 | `messages` is a list of tuples `{from, text}` where `from` is the username of the 13 | sender and `text` is the contents of the message. 14 | 15 | As you can see the chat room struct we are working with is nested with multiple levels of data 16 | structures. This is common in functional programming, and in this lab we will learn 17 | how to work with such data structures. 18 | 19 | ### Lab instructions 20 | 21 | The `lab3` directory has a file `lib/lab3.ex` which contains an implementation of out chat 22 | room with some stubbed out functions. These are the functions you'll implement. For each 23 | new function, you will: 24 | 25 | 1. Run `mix test`. You will see the failures for each of your unimplemented features. 26 | 27 | 2. Implement the missing feature, and iterate on it until the test passes. 28 | 29 | Below are the functions for the chat room we will implement in this lab. 30 | 31 | * `leave()` will remove a member from the chatroom 32 | * `members()` will return the names of the members in the chatroom, as a list of strings 33 | * `send_message()` will add a new message to the message list for a user 34 | * `send_messages()` will add a new message to the message list for all users 35 | 36 | When joining, the function will raise if the user has already joined. The `leave` 37 | function should do the same if a user that hasn't joined yet tries to leave. 38 | 39 | If you have more time, you can implement two other functions, which filter through messages in the chatroom. 40 | 41 | * `messages_from_user()` and `messages_to_user()` will list all of the messages from or to a given user. 42 | 43 | In this lab we will be using the `Map` and `Enum` modules, so take a quick look through 44 | the documentation for those modules before you get started so you know roughly what 45 | they provide. If you get stuck anywhere, try to go over the functions 46 | in those modules to see if there is a function that could help you solve your problem. 47 | 48 | 49 | ### Links 50 | 51 | Getting started guide: https://elixir-lang.org/getting-started/introduction.html 52 | 53 | API docs: https://hexdocs.pm/elixir/ 54 | 55 | 1. Maps https://elixir-lang.org/getting-started/maps-and-dicts.html 56 | 57 | 2. Map syntax https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%25%7B%7D/1 58 | 59 | 3. Structs https://elixir-lang.org/getting-started/struct.html 60 | 61 | 4. `raise/1` https://hexdocs.pm/elixir/Kernel.html#raise/1 62 | 63 | 5. `assert_raise/1` https://hexdocs.pm/ex_unit/ExUnit.Assertions.html#assert_raise/2 64 | 65 | 6. `Enum.flat_map/2` https://hexdocs.pm/elixir/Enum.html#flat_map/2 66 | 67 | 7. `for` comprehensions https://hexdocs.pm/elixir/Kernel.SpecialForms.html#for/1 68 | 69 | 8. `Enum.into/3` https://hexdocs.pm/elixir/Enum.html#into/3 70 | 71 | 9. `Map` module https://hexdocs.pm/elixir/Map.html 72 | 73 | 74 | ### Solution ( no peeking :) ) 75 | 76 | See `solution.ex` in the `lab3` directory. 77 | -------------------------------------------------------------------------------- /lab3/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 :lab3, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:lab3, :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 | -------------------------------------------------------------------------------- /lab3/lib/lab3.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab3 do 2 | defmodule Chat do 3 | defstruct [:members] 4 | end 5 | 6 | def new() do 7 | %Chat{members: %{}} 8 | end 9 | 10 | def has_member?(chat, username) do 11 | Map.has_key?(chat.members, username) 12 | end 13 | 14 | def join(chat, username) do 15 | if Map.has_key?(chat.members, username) do 16 | raise ArgumentError, message: "username already taken" 17 | else 18 | members = Map.put(chat.members, username, []) 19 | %{chat | members: members} 20 | end 21 | end 22 | 23 | def members(_chat) do 24 | raise "not implemented yet" 25 | end 26 | 27 | def leave(_chat, _username) do 28 | raise "not implemented yet" 29 | end 30 | 31 | def send_message(_chat, _from, _to, _message) do 32 | raise "not implemented yet" 33 | end 34 | 35 | def send_messages(_chat, _from, _message) do 36 | raise "not implemented yet" 37 | end 38 | 39 | def messages_to_user(_chat, _to) do 40 | raise "not implemented yet" 41 | end 42 | 43 | def messages_from_user(_chat, _from) do 44 | raise "not implemented yet" 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lab3/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab3.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :lab3, 6 | version: "0.1.0", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type "mix help deps" for more examples and options 29 | defp deps do 30 | [] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lab3/solution.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab3 do 2 | defmodule Chat do 3 | defstruct [:members] 4 | end 5 | 6 | def new() do 7 | %Chat{members: %{}} 8 | end 9 | 10 | def has_member?(chat, username) do 11 | Map.has_key?(chat.members, username) 12 | end 13 | 14 | def join(chat, username) do 15 | if Map.has_key?(chat.members, username) do 16 | raise ArgumentError, message: "username already taken" 17 | else 18 | members = Map.put(chat.members, username, []) 19 | %{chat | members: members} 20 | end 21 | end 22 | 23 | def leave(chat, username) do 24 | if Map.has_key?(chat.members, username) do 25 | members = Map.delete(chat.members, username) 26 | %{chat | members: members} 27 | else 28 | raise ArgumentError, message: "user not in chat" 29 | end 30 | end 31 | 32 | def members(chat) do 33 | Enum.map(chat.members, fn {username, _messages} -> username end) 34 | end 35 | 36 | def send_message(chat, from, to, message) do 37 | case Map.fetch(chat.members, to) do 38 | {:ok, messages} -> 39 | members = Map.put(chat.members, to, [{from, message} | messages]) 40 | %{chat | members: members} 41 | 42 | :error -> 43 | raise ArgumentError, message: "user not in chat" 44 | end 45 | end 46 | 47 | def send_messages(chat, from, message) do 48 | message_tuple = {from, message} 49 | 50 | members = 51 | Enum.into(chat.members, %{}, fn {username, messages} -> 52 | {username, [message_tuple | messages]} 53 | end) 54 | 55 | %{chat | members: members} 56 | end 57 | 58 | def messages_to_user(chat, to) do 59 | messages = Map.get(chat.members, to, []) 60 | Enum.map(messages, fn {_from, contents} -> contents end) 61 | end 62 | 63 | def messages_from_user(chat, from) do 64 | Enum.flat_map(chat.members, fn {_to, messages} -> 65 | for {^from, message} <- messages, do: message 66 | end) 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lab3/test/lab3_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab3Test do 2 | use ExUnit.Case 3 | alias Lab3.Chat 4 | 5 | setup do 6 | %{room: Lab3.new()} 7 | end 8 | 9 | @tag :skip 10 | test "new room", %{room: room} do 11 | assert %Chat{} = room 12 | end 13 | 14 | @tag :skip 15 | test "join new member", %{room: room} do 16 | room = Lab3.join(room, "Joe") 17 | assert Lab3.has_member?(room, "Joe") 18 | refute Lab3.has_member?(room, "Jose") 19 | assert Lab3.members(room) == ["Joe"] 20 | end 21 | 22 | @tag :skip 23 | test "join existing member", %{room: room} do 24 | room = Lab3.join(room, "Joe") 25 | assert_raise ArgumentError, fn -> 26 | Lab3.join(room, "Joe") 27 | end 28 | end 29 | 30 | @tag :skip 31 | test "leave existing member", %{room: room} do 32 | room = Lab3.join(room, "Joe") 33 | room = Lab3.leave(room, "Joe") 34 | refute Lab3.has_member?(room, "Joe") 35 | end 36 | 37 | @tag :skip 38 | test "leave non-existent member", %{room: room} do 39 | assert_raise ArgumentError, fn -> 40 | Lab3.leave(room, "Joe") 41 | end 42 | end 43 | 44 | @tag :skip 45 | test "broadcast message", %{room: room} do 46 | room = Lab3.join(room, "Joe") 47 | room = Lab3.join(room, "Robert") 48 | room = Lab3.send_messages(room, "Joe", "Hello World") 49 | assert Lab3.messages_to_user(room, "Joe") == ["Hello World"] 50 | assert Lab3.messages_to_user(room, "Robert") == ["Hello World"] 51 | end 52 | 53 | @tag :skip 54 | test "messages to user", %{room: room} do 55 | room = Lab3.join(room, "Joe") 56 | room = Lab3.send_message(room, "Robert", "Joe", "Hello Joe") 57 | assert Lab3.messages_to_user(room, "Joe") == ["Hello Joe"] 58 | assert Lab3.messages_to_user(room, "Robert") == [] 59 | end 60 | 61 | @tag :skip 62 | test "messages from user", %{room: room} do 63 | room = Lab3.join(room, "Joe") 64 | room = Lab3.send_message(room, "Robert", "Joe", "Hello Joe") 65 | assert Lab3.messages_from_user(room, "Robert") == ["Hello Joe"] 66 | assert Lab3.messages_from_user(room, "Joe") == [] 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lab3/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /lab4/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["{lib,test}/**/*.exs?", "solution.ex"] 3 | ] 4 | -------------------------------------------------------------------------------- /lab4/.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 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | -------------------------------------------------------------------------------- /lab4/README.md: -------------------------------------------------------------------------------- 1 | # Lab4 2 | 3 | ### Lab purpose 4 | 5 | Learn about processes and message passing. You'll have some simple exercises 6 | that make use of basic Elixir concurrency using primitives. 7 | 8 | ### Lab instructions 9 | 10 | The `lab4` directory has a file `lib/lab4.ex` which contains an implementation of process-related 11 | functions. As usual, use that template to implement features until all of your tests pass. 12 | 13 | 1. Create a process that just prints the first message it receives 14 | and then dies. This function should return the `pid` of the new process. The message needs to 15 | be printed to the console with `IO.write(:stderr, message)` to be able to 16 | easily capture it in tests. 17 | 18 | 2. Create a process that prints messages like in the previous task, but instead 19 | of dying after the first message, keep printing new messages with `IO.write(:stderr, message)`. 20 | 21 | 3. Create a process that waits for a message containing a list, sum the list 22 | (you can use code from lab1), and reply to the original process. The message will be in the format: 23 | `{:sum, pid, list}`, where `pid` is the pid of the process the reply should be sent to. 24 | 25 | 4. Spawn N number of processes doing the previous task in parallel and wait 26 | for the responses. (As an extra exercise make sure to return the results in 27 | the order the input was given.) 28 | 29 | 30 | ### Links 31 | 32 | Getting started guide: https://elixir-lang.org/getting-started/introduction.html 33 | 34 | API docs: https://hexdocs.pm/elixir/ 35 | 36 | 1. `spawn/1` https://hexdocs.pm/elixir/Kernel.html#spawn/1 37 | 38 | 2. `send/2` https://hexdocs.pm/elixir/Kernel.html#send/2 39 | 40 | 3. `receive/1` https://hexdocs.pm/elixir/Kernel.SpecialForms.html#receive/1 41 | 42 | 4. `Process` https://hexdocs.pm/elixir/Process.html 43 | 44 | 5. Processes https://elixir-lang.org/getting-started/processes.html 45 | 46 | 47 | ### Solution ( no peeking :) ) 48 | 49 | See `solution.ex` in the `lab4` directory. 50 | -------------------------------------------------------------------------------- /lab4/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 :lab4, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:lab4, :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 | -------------------------------------------------------------------------------- /lab4/lib/lab4.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab4 do 2 | def print_first_message() do 3 | raise "not implemented yet" 4 | end 5 | 6 | def print_all_messages() do 7 | raise "not implemented yet" 8 | end 9 | 10 | def sum() do 11 | raise "not implemented yet" 12 | end 13 | 14 | def sum_all(_list_of_lists) do 15 | raise "not implemented yet" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lab4/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab4.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :lab4, 6 | version: "0.1.0", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type "mix help deps" for more examples and options 29 | defp deps do 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lab4/solution.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab4 do 2 | def print_first_message() do 3 | spawn(fn -> 4 | receive do 5 | message -> IO.write(:stderr, message) 6 | end 7 | end) 8 | end 9 | 10 | def print_all_messages() do 11 | spawn(&print_all_messages_recursive/0) 12 | end 13 | 14 | defp print_all_messages_recursive() do 15 | receive do 16 | message -> 17 | IO.write(:stderr, message) 18 | print_all_messages_recursive() 19 | end 20 | end 21 | 22 | def sum() do 23 | spawn(fn -> 24 | receive do 25 | {:sum, pid, list} -> send(pid, Enum.sum(list)) 26 | end 27 | end) 28 | end 29 | 30 | def sum_all(list_of_lists) do 31 | current_pid = self() 32 | 33 | refs = 34 | Enum.map(list_of_lists, fn list -> 35 | ref = make_ref() 36 | 37 | spawn(fn -> 38 | send(current_pid, {ref, Enum.sum(list)}) 39 | end) 40 | 41 | ref 42 | end) 43 | 44 | Enum.map(refs, fn ref -> 45 | receive do 46 | {^ref, result} -> result 47 | end 48 | end) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lab4/test/lab4_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab4Test do 2 | use ExUnit.Case 3 | import ExUnit.CaptureIO 4 | 5 | defp receive_once do 6 | receive do 7 | result -> result 8 | end 9 | end 10 | 11 | @tag :skip 12 | test "task 1: print_first_message" do 13 | pid = Lab4.print_first_message() 14 | 15 | assert capture_io(:stderr, fn -> 16 | send(pid, "foo") 17 | # Wait until output has been captured 18 | Process.sleep(100) 19 | end) == "foo" 20 | 21 | assert capture_io(:stderr, fn -> 22 | send(pid, "foo") 23 | # Wait until output has been captured 24 | Process.sleep(100) 25 | end) == "" 26 | 27 | refute Process.alive?(pid) 28 | end 29 | 30 | @tag :skip 31 | test "task 2: print_all_messages" do 32 | pid = Lab4.print_all_messages() 33 | 34 | assert capture_io(:stderr, fn -> 35 | send(pid, "foo") 36 | # Wait until output has been captured 37 | Process.sleep(100) 38 | end) == "foo" 39 | 40 | assert capture_io(:stderr, fn -> 41 | send(pid, "bar") 42 | # Wait until output has been captured 43 | Process.sleep(100) 44 | end) == "bar" 45 | 46 | assert Process.alive?(pid) 47 | end 48 | 49 | @tag :skip 50 | test "task 3: sum" do 51 | pid = Lab4.sum() 52 | send(pid, {:sum, self(), 1..2}) 53 | assert receive_once() == 3 54 | 55 | pid = Lab4.sum() 56 | send(pid, {:sum, self(), 1..10}) 57 | assert receive_once() == 55 58 | end 59 | 60 | @tag :skip 61 | test "task 4: sum_all" do 62 | assert Lab4.sum_all([1..2]) == [3] 63 | assert Lab4.sum_all([1..2, 3..4, 5..6]) == [3, 7, 11] 64 | 65 | assert Lab4.sum_all([[1, 2]]) == [3] 66 | assert Lab4.sum_all([[1, 2], [3, 4], [5, 6]]) == [3, 7, 11] 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lab4/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /lab5/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["{lib,test}/**/*.exs?", "solution.ex"] 3 | ] 4 | -------------------------------------------------------------------------------- /lab5/.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 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | -------------------------------------------------------------------------------- /lab5/README.md: -------------------------------------------------------------------------------- 1 | # Lab5 2 | 3 | ## Lab purpose 4 | 5 | In this lab we will build the chat room we prototyped in lab3. Instead of simple functions, 6 | we'll use concurrency using the concepts we learned in lab4. 7 | 8 | Our `%Chat{}` state will be simpler. It will have a `:members` field that is a map. 9 | The keys will be user names, and the values will be processes, represented as `PID`s. 10 | Then we can send messages directly to each user process. 11 | 12 | ## Lab instructions 13 | 14 | As usual, in the directory `lab5`, you'll see a file called `lib/lab5.ex` with 15 | several stubbed out functions. You can run your tests using `$ mix test`, and 16 | implement the stubbed out functions until all of your tests pass. 17 | 18 | Like most Elixir concurrent processes, instead of raising exceptions, errors 19 | will return `{:error, "message"}`. 20 | 21 | ## Links 22 | 23 | * Getting started guide: https://elixir-lang.org/getting-started/introduction.html 24 | 25 | * API docs: https://hexdocs.pm/elixir/ 26 | 27 | ## Solution ( no peeking :) ) 28 | 29 | See `solution.ex` in the `lab5` directory. 30 | -------------------------------------------------------------------------------- /lab5/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 :lab5, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:lab5, :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 | -------------------------------------------------------------------------------- /lab5/lib/lab5.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab5 do 2 | defmodule Chat do 3 | defstruct [:members] 4 | end 5 | 6 | def new() do 7 | spawn_link(fn -> 8 | loop(%Chat{members: %{}}) 9 | end) 10 | end 11 | 12 | defp loop(chat) do 13 | receive do 14 | {:has_member?, pid, username} -> 15 | send(pid, Map.has_key?(chat.members, username)) 16 | loop(chat) 17 | 18 | {:join, pid, username} -> 19 | if Map.has_key?(chat.members, username) do 20 | send(pid, {:error, "username already taken"}) 21 | loop(chat) 22 | else 23 | send(pid, :ok) 24 | # Here we store the PID of the calling process to 25 | # later be able to send send messages to it 26 | members = Map.put(chat.members, username, pid) 27 | loop(%{chat | members: members}) 28 | end 29 | end 30 | end 31 | 32 | defp send_and_wait_reply(pid, message) do 33 | send(pid, message) 34 | receive do 35 | message -> 36 | message 37 | after 38 | 1000 -> 39 | raise "timeout waiting for reply" 40 | end 41 | end 42 | 43 | def has_member?(pid, username) do 44 | send_and_wait_reply(pid, {:has_member?, self(), username}) 45 | end 46 | 47 | def join(pid, username) do 48 | send_and_wait_reply(pid, {:join, self(), username}) 49 | end 50 | 51 | raise "not implemented yet" 52 | def members(pid) do 53 | end 54 | 55 | def leave(_pid, _username) do 56 | raise "not implemented yet" 57 | end 58 | 59 | def send_message(_pid, _to, _from, _message) do 60 | raise "not implemented yet" 61 | end 62 | 63 | def send_messages(_pid, _from, _message) do 64 | raise "not implemented yet" 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lab5/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab5.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :lab5, 6 | version: "0.1.0", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type "mix help deps" for more examples and options 29 | defp deps do 30 | [] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lab5/solution.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab5 do 2 | defmodule Chat do 3 | defstruct [:members] 4 | end 5 | 6 | def new() do 7 | spawn_link(fn -> 8 | loop(%Chat{members: %{}}) 9 | end) 10 | end 11 | 12 | defp loop(chat) do 13 | receive do 14 | {:has_member?, pid, username} -> 15 | send(pid, Map.has_key?(chat.members, username)) 16 | loop(chat) 17 | 18 | {:join, pid, username} -> 19 | if Map.has_key?(chat.members, username) do 20 | send(pid, {:error, "username already taken"}) 21 | loop(chat) 22 | else 23 | send(pid, :ok) 24 | members = Map.put(chat.members, username, pid) 25 | loop(%{chat | members: members}) 26 | end 27 | 28 | {:leave, pid, username} -> 29 | if Map.has_key?(chat.members, username) do 30 | send(pid, :ok) 31 | members = Map.delete(chat.members, username) 32 | loop(%{chat | members: members}) 33 | else 34 | send(pid, {:error, "user not in chat"}) 35 | loop(chat) 36 | end 37 | 38 | {:members, pid} -> 39 | members = Map.keys(chat.members) 40 | send(pid, members) 41 | loop(chat) 42 | 43 | {:push_message, pid, to, from, message} -> 44 | case Map.fetch(chat.members, to) do 45 | {:ok, to} -> 46 | send(to, {:message, from, message}) 47 | send(pid, :ok) 48 | loop(chat) 49 | 50 | :error -> 51 | send(pid, {:error, "user not in chat"}) 52 | loop(chat) 53 | end 54 | 55 | {:broadcast_message, pid, from, message} -> 56 | chat.members 57 | |> Map.delete(from) 58 | |> Map.values() 59 | |> Enum.each(&send(&1, {:message, from, message})) 60 | 61 | send(pid, :ok) 62 | loop(chat) 63 | end 64 | end 65 | 66 | defp send_and_wait_reply(pid, message) do 67 | send(pid, message) 68 | 69 | receive do 70 | message -> 71 | message 72 | after 73 | 1000 -> 74 | raise "timeout waiting for reply" 75 | end 76 | end 77 | 78 | def has_member?(pid, username) do 79 | send_and_wait_reply(pid, {:has_member?, self(), username}) 80 | end 81 | 82 | def join(pid, username) do 83 | send_and_wait_reply(pid, {:join, self(), username}) 84 | end 85 | 86 | def leave(pid, username) do 87 | send_and_wait_reply(pid, {:leave, self(), username}) 88 | end 89 | 90 | def members(pid) do 91 | send_and_wait_reply(pid, {:members, self()}) 92 | end 93 | 94 | def send_message(pid, to, from, message) do 95 | send_and_wait_reply(pid, {:push_message, self(), to, from, message}) 96 | end 97 | 98 | def send_messages(pid, from, message) do 99 | send_and_wait_reply(pid, {:broadcast_message, self(), from, message}) 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lab5/test/lab5_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab5Test do 2 | use ExUnit.Case 3 | alias Lab5 4 | 5 | setup do 6 | %{room: Lab5.new()} 7 | end 8 | 9 | @tag :skip 10 | test "join new member", %{room: room} do 11 | assert :ok = Lab5.join(room, "Joe") 12 | assert Lab5.has_member?(room, "Joe") 13 | refute Lab5.has_member?(room, "Jose") 14 | assert Lab5.members(room) == ["Joe"] 15 | end 16 | 17 | @tag :skip 18 | test "join existing member", %{room: room} do 19 | assert :ok = Lab5.join(room, "Joe") 20 | assert {:error, _} = Lab5.join(room, "Joe") 21 | end 22 | 23 | @tag :skip 24 | test "leave existing member", %{room: room} do 25 | assert :ok = Lab5.join(room, "Joe") 26 | assert :ok = Lab5.leave(room, "Joe") 27 | refute Lab5.has_member?(room, "Joe") 28 | end 29 | 30 | @tag :skip 31 | test "leave non-existent member", %{room: room} do 32 | assert {:error, _} = Lab5.leave(room, "Joe") 33 | end 34 | 35 | @tag :skip 36 | test "push message", %{room: room} do 37 | pid1 = spawn_link(fn -> 38 | assert :ok = Lab5.join(room, "Joe") 39 | # Wait until Robert joined 40 | Process.sleep(200) 41 | assert :ok = Lab5.send_message(room, "Robert", "Joe", "Hello World") 42 | # Don't send message to yourself 43 | refute_receive {:message, "Joe", "Hello World"}, 500 44 | end) 45 | 46 | pid2 = spawn_link(fn -> 47 | assert :ok = Lab5.join(room, "Robert") 48 | assert_receive {:message, "Joe", "Hello World"}, 500 49 | # Only receive one message 50 | refute_receive {:message, "Joe", "Hello World"}, 500 51 | end) 52 | 53 | await_exits([pid1, pid2]) 54 | end 55 | 56 | @tag :skip 57 | test "broadcast message", %{room: room} do 58 | pid1 = spawn_link(fn -> 59 | assert :ok = Lab5.join(room, "Joe") 60 | # Wait until Robert and Mike joined 61 | Process.sleep(200) 62 | assert :ok = Lab5.send_messages(room, "Joe", "Hello World") 63 | # Don't send message to yourself 64 | refute_receive {:message, "Joe", "Hello World"}, 500 65 | end) 66 | 67 | pid2 = spawn_link(fn -> 68 | assert :ok = Lab5.join(room, "Robert") 69 | assert_receive {:message, "Joe", "Hello World"}, 500 70 | # Only receive one message 71 | refute_receive {:message, "Joe", "Hello World"}, 500 72 | end) 73 | 74 | pid3 = spawn_link(fn -> 75 | assert :ok = Lab5.join(room, "Mike") 76 | assert_receive {:message, "Joe", "Hello World"}, 500 77 | # Only receive one message 78 | refute_receive {:message, "Joe", "Hello World"}, 500 79 | end) 80 | 81 | await_exits([pid1, pid2, pid3]) 82 | end 83 | 84 | defp await_exits(pids) do 85 | pids 86 | |> Enum.map(&{&1, Process.monitor(&1)}) 87 | |> Enum.each(fn {pid, ref} -> 88 | assert_receive {:DOWN, ^ref, :process, ^pid, :normal}, 1000 89 | end) 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lab5/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /lab6/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["{lib,test}/**/*.exs?", "solution.ex"] 3 | ] 4 | -------------------------------------------------------------------------------- /lab6/.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 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | -------------------------------------------------------------------------------- /lab6/README.md: -------------------------------------------------------------------------------- 1 | # Lab6 2 | 3 | ## Lab purpose 4 | 5 | In this lab we will rebuild the chat room from the previous lab with an OTP `GenServer`. 6 | 7 | Notice the simplicity. The `GenServer` removes boilerplate your code would otherwise 8 | have to handle. 9 | 10 | You will replace the `send_and_wait_reply` function from lab5 with a `GenServer.call`. 11 | 12 | You'll have a `handle_call` for each `GenServer.call`. OTP will call this callback 13 | every time a "call" arrives to the process. 14 | 15 | From the callback we need to send a reply and return the updated chat room, just like 16 | we did in lab5. You'll do so by returning a `{:reply, reply, state}` tuple from the 17 | callback where `reply` is the value you want to send back to the caller and `state` 18 | is our (possibly updated) chat room struct, which becomes the new process state. 19 | 20 | ## Lab instructions 21 | 22 | As usual, in the directory `lab6`, you'll see a file called `lib/lab6.ex` with 23 | several stubbed out functions. You can run your tests using `mix test`, and 24 | implement the stubbed out functions until all of your tests pass. 25 | 26 | We will reuse the test suite from lab5 since we are only changing the implementation. 27 | 28 | ## Links 29 | 30 | * Getting started guide: https://elixir-lang.org/getting-started/introduction.html 31 | 32 | * API docs: https://hexdocs.pm/elixir/ 33 | 34 | * `GenServer` API: https://hexdocs.pm/elixir/GenServer.html 35 | 36 | * `GenServer` guide: https://elixir-lang.org/getting-started/mix-otp/genserver.html 37 | 38 | ## Solution ( no peeking :) ) 39 | 40 | See `solution.ex` in the `lab6` directory. 41 | -------------------------------------------------------------------------------- /lab6/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 :lab6, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:lab6, :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 | -------------------------------------------------------------------------------- /lab6/lib/lab6.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab6 do 2 | defmodule Chat do 3 | defstruct [:members] 4 | end 5 | 6 | use GenServer 7 | 8 | # PUBLIC API 9 | 10 | def new() do 11 | GenServer.start_link(__MODULE__, []) 12 | end 13 | 14 | def has_member?(server, username) do 15 | GenServer.call(server, {:has_member?, username}) 16 | end 17 | 18 | def join(server, username) do 19 | GenServer.call(server, {:join, self(), username}) 20 | end 21 | 22 | def members(_server) do 23 | raise "not implemented yet" 24 | end 25 | 26 | def leave(_server, _username) do 27 | raise "not implemented yet" 28 | end 29 | 30 | def send_message(_server, _to, _from, _message) do 31 | raise "not implemented yet" 32 | end 33 | 34 | def send_messages(_server, _from, _message) do 35 | raise "not implemented yet" 36 | end 37 | 38 | # GENSERVER CALLBACKS 39 | 40 | def init([]) do 41 | {:ok, %Chat{members: %{}}} 42 | end 43 | 44 | def handle_call({:has_member?, username}, _from, chat) do 45 | {:reply, Map.has_key?(chat.members, username), chat} 46 | end 47 | 48 | def handle_call({:join, pid, username}, _from, chat) do 49 | if Map.has_key?(chat.members, username) do 50 | {:reply, {:error, "username already taken"}, chat} 51 | else 52 | members = Map.put(chat.members, username, pid) 53 | chat = %{chat | members: members} 54 | {:reply, :ok, chat} 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lab6/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab6.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :lab6, 6 | version: "0.1.0", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type "mix help deps" for more examples and options 29 | defp deps do 30 | [] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lab6/solution.ex: -------------------------------------------------------------------------------- 1 | defmodule Lab6 do 2 | defmodule Chat do 3 | defstruct [:members] 4 | end 5 | 6 | use GenServer 7 | 8 | # PUBLIC API 9 | 10 | def new() do 11 | GenServer.start_link(__MODULE__, []) 12 | end 13 | 14 | def has_member?(server, username) do 15 | GenServer.call(server, {:has_member?, username}) 16 | end 17 | 18 | def join(server, username) do 19 | GenServer.call(server, {:join, username}) 20 | end 21 | 22 | def members(server) do 23 | GenServer.call(server, :members) 24 | end 25 | 26 | def leave(server, username) do 27 | GenServer.call(server, {:leave, username}) 28 | end 29 | 30 | def send_message(server, to, from, message) do 31 | GenServer.call(server, {:send_message, to, from, message}) 32 | end 33 | 34 | def send_messages(server, from, message) do 35 | GenServer.call(server, {:send_messages, from, message}) 36 | end 37 | 38 | # GENSERVER CALLBACKS 39 | 40 | def init([]) do 41 | {:ok, %Chat{members: %{}}} 42 | end 43 | 44 | def handle_call({:has_member?, username}, _from, chat) do 45 | {:reply, Map.has_key?(chat.members, username), chat} 46 | end 47 | 48 | def handle_call({:join, username}, {pid, _tag}, chat) do 49 | if Map.has_key?(chat.members, username) do 50 | {:reply, {:error, "username already taken"}, chat} 51 | else 52 | members = Map.put(chat.members, username, pid) 53 | chat = %{chat | members: members} 54 | {:reply, :ok, chat} 55 | end 56 | end 57 | 58 | def handle_call(:members, _from, chat) do 59 | members = Map.keys(chat.members) 60 | {:reply, members, chat} 61 | end 62 | 63 | def handle_call({:leave, username}, _from, chat) do 64 | if Map.has_key?(chat.members, username) do 65 | members = Map.delete(chat.members, username) 66 | chat = %{chat | members: members} 67 | {:reply, :ok, chat} 68 | else 69 | {:reply, {:error, "user not in chat"}, chat} 70 | end 71 | end 72 | 73 | def handle_call({:send_message, to, from, message}, _from, chat) do 74 | case Map.fetch(chat.members, to) do 75 | {:ok, to} -> 76 | send(to, {:message, from, message}) 77 | {:reply, :ok, chat} 78 | 79 | :error -> 80 | {:reply, {:error, "user not in chat"}, chat} 81 | end 82 | end 83 | 84 | def handle_call({:send_messages, from, message}, _from, chat) do 85 | chat.members 86 | |> Map.delete(from) 87 | |> Map.values() 88 | |> Enum.each(&send(&1, {:message, from, message})) 89 | 90 | {:reply, :ok, chat} 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lab6/test/lab6_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Lab6Test do 2 | use ExUnit.Case 3 | alias Lab6 4 | 5 | setup do 6 | {:ok, server} = Lab6.new() 7 | %{room: server} 8 | end 9 | 10 | @tag :skip 11 | test "join new member", %{room: room} do 12 | assert :ok = Lab6.join(room, "Joe") 13 | assert Lab6.has_member?(room, "Joe") 14 | refute Lab6.has_member?(room, "Jose") 15 | assert Lab6.members(room) == ["Joe"] 16 | end 17 | 18 | @tag :skip 19 | test "join existing member", %{room: room} do 20 | assert :ok = Lab6.join(room, "Joe") 21 | assert {:error, _} = Lab6.join(room, "Joe") 22 | end 23 | 24 | @tag :skip 25 | test "leave existing member", %{room: room} do 26 | assert :ok = Lab6.join(room, "Joe") 27 | assert :ok = Lab6.leave(room, "Joe") 28 | refute Lab6.has_member?(room, "Joe") 29 | end 30 | 31 | @tag :skip 32 | test "leave non-existent member", %{room: room} do 33 | assert {:error, _} = Lab6.leave(room, "Joe") 34 | end 35 | 36 | @tag :skip 37 | test "push message", %{room: room} do 38 | pid1 = spawn_link(fn -> 39 | assert :ok = Lab6.join(room, "Joe") 40 | # Wait until Robert joined 41 | Process.sleep(200) 42 | assert :ok = Lab6.send_message(room, "Robert", "Joe", "Hello World") 43 | # Don't send message to yourself 44 | refute_receive {:message, "Joe", "Hello World"}, 500 45 | end) 46 | 47 | pid2 = spawn_link(fn -> 48 | assert :ok = Lab6.join(room, "Robert") 49 | assert_receive {:message, "Joe", "Hello World"}, 500 50 | # Only receive one message 51 | refute_receive {:message, "Joe", "Hello World"}, 500 52 | end) 53 | 54 | await_exits([pid1, pid2]) 55 | end 56 | 57 | @tag :skip 58 | test "broadcast message", %{room: room} do 59 | pid1 = spawn_link(fn -> 60 | assert :ok = Lab6.join(room, "Joe") 61 | # Wait until Robert and Mike joined 62 | Process.sleep(200) 63 | assert :ok = Lab6.send_messages(room, "Joe", "Hello World") 64 | # Don't send message to yourself 65 | refute_receive {:message, "Joe", "Hello World"}, 500 66 | end) 67 | 68 | pid2 = spawn_link(fn -> 69 | assert :ok = Lab6.join(room, "Robert") 70 | assert_receive {:message, "Joe", "Hello World"}, 500 71 | # Only receive one message 72 | refute_receive {:message, "Joe", "Hello World"}, 500 73 | end) 74 | 75 | pid3 = spawn_link(fn -> 76 | assert :ok = Lab6.join(room, "Mike") 77 | assert_receive {:message, "Joe", "Hello World"}, 500 78 | # Only receive one message 79 | refute_receive {:message, "Joe", "Hello World"}, 500 80 | end) 81 | 82 | await_exits([pid1, pid2, pid3]) 83 | end 84 | 85 | defp await_exits(pids) do 86 | pids 87 | |> Enum.map(&{&1, Process.monitor(&1)}) 88 | |> Enum.each(fn {pid, ref} -> 89 | assert_receive {:DOWN, ^ref, :process, ^pid, :normal}, 1000 90 | end) 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lab6/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /schedule.md: -------------------------------------------------------------------------------- 1 | # Schedule 2 | 3 | You learn Elixir best the way you learn any language, by coding. Tests make it easy to attack 4 | one concept at a time and master one concept before moving on to the next. Since each class 5 | moves at a different rate, we'll get to as many of these as we can before time runs out. 6 | We have more content than we'll finish today. 7 | 8 | Each of the sections will have the following parts: 9 | * Live coding and instruction 10 | * Lab introduction 11 | * Lab Coding 12 | * Lab Review 13 | 14 | 15 | 16 | ## Section 1: Basics with Mix and language abstractions 17 | Learn Elixir mix, the primary build tool, and language basics including 18 | * mix 19 | * modules and functions 20 | * primitives 21 | * tuples 22 | * lists 23 | * matches 24 | 25 | ## Section 2: Lists, tuples, and matches 26 | Learn to use core language concepts including 27 | * higher order functions 28 | * Enum 29 | * pipes 30 | * for comprehensions 31 | 32 | ## Section 3: Maps, structs and immutability 33 | Learn to use structs and maps to solve real world problems. Start a chat room that uses concepts: 34 | * structs 35 | * maps 36 | * immutability 37 | 38 | ## Section 4: Concurrency Primitives 39 | Elixir is a concurrent language. Use basic concurrency constructs including 40 | * send 41 | * receive 42 | * spawn 43 | 44 | ## Section 5: Looping construct with send and receive 45 | Elixir allows true message passing between processes. We'll build a primitive cache 46 | that: 47 | * Stores state 48 | * Uses recursive looping 49 | * Matches receive tuples 50 | 51 | ## Section 6: OTP 52 | Elixir uses OTP, a concurrency abstraction that is in the center of most advanced Elixir programming. 53 | In this section, we'll cover OTP concepts including 54 | * GenServer 55 | * Call 56 | * Cast 57 | 58 | ## Breaks and Lunch 59 | Customized per class 60 | --------------------------------------------------------------------------------