commit 1dba0af06450220c9985d99a6c3456cdc1baad33 Author: AYM1607 Date: Sat Jun 20 13:57:33 2020 -0500 First commit diff --git a/Elixir.Circle.beam b/Elixir.Circle.beam new file mode 100644 index 0000000..2df3033 Binary files /dev/null and b/Elixir.Circle.beam differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d6835a --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# elixir-in-action diff --git a/aliases.ex b/aliases.ex new file mode 100644 index 0000000..9d52e33 --- /dev/null +++ b/aliases.ex @@ -0,0 +1,21 @@ +defmodule Some.Nested do + def print_from_nested do + IO.puts("Somehting from nested") + end +end + +defmodule MyModule do + import IO + alias IO, as: MyIO + alias Some.Nested + + def print do + puts("Something") + end + + def print_from_alias do + MyIO.puts("Something from alias") + end + + def print_from_nested, do: Nested.print_from_nested() +end diff --git a/arity_calc.ex b/arity_calc.ex new file mode 100644 index 0000000..3437a58 --- /dev/null +++ b/arity_calc.ex @@ -0,0 +1,21 @@ +defmodule CalcOne do + def sum(a) do + sum(a, 0) + end + + def sum(a, b) do + a + b + end +end + +defmodule CalcTwo do + def sum(a, b \\ 0) do + a + b + end +end + +defmodule CalcThree do + def fun(a, b, c \\ 0, d, e \\ 0) do + a + b + c + d + e + end +end diff --git a/chapter3/enum_streams_practice.ex b/chapter3/enum_streams_practice.ex new file mode 100644 index 0000000..7ed0c02 --- /dev/null +++ b/chapter3/enum_streams_practice.ex @@ -0,0 +1,37 @@ +defmodule FileHelper do + defp filtered_lines!(path) do + File.stream!(path) + |> Stream.map(&String.replace(&1, "\n", "")) + end + + def line_lengths!(path) do + path + |> filtered_lines!() + |> Enum.map(&String.length/1) + end + + def longest_line_length(path) do + path + |> filtered_lines!() + |> Stream.map(&String.length/1) + |> Enum.max() + end + + def longest_line(path) do + path + |> filtered_lines!() + |> Enum.max_by(&String.length/1) + end + + def words_per_line(path) do + path + |> filtered_lines!() + |> Enum.map(&word_count/1) + end + + def word_count(string) do + string + |> String.split() + |> length() + end +end diff --git a/chapter3/enum_streams_practice.txt b/chapter3/enum_streams_practice.txt new file mode 100644 index 0000000..8190d27 --- /dev/null +++ b/chapter3/enum_streams_practice.txt @@ -0,0 +1,6 @@ +This is a file +that contains some words +and is mean to be just an example +to be used to practice +with elixir streams +and enums diff --git a/chapter3/natural_nums.ex b/chapter3/natural_nums.ex new file mode 100644 index 0000000..a7c8743 --- /dev/null +++ b/chapter3/natural_nums.ex @@ -0,0 +1,8 @@ +defmodule NaturalNums do + def print(1), do: IO.puts(1) + + def print(n) when is_integer(n) and n > 1 do + print(n - 1) + IO.puts(n) + end +end diff --git a/chapter3/recursion_practice.ex b/chapter3/recursion_practice.ex new file mode 100644 index 0000000..4e0f088 --- /dev/null +++ b/chapter3/recursion_practice.ex @@ -0,0 +1,21 @@ +defmodule ListHelper do + def list_len([]), do: 0 + + def list_len([_ | tail]) do + 1 + list_len(tail) + end + + def range(num, num), do: [num] + + def range(num1, num2) do + [num1 | range(num1 + 1, num2)] + end + + def positive([]), do: [] + + def positive([head | tail]) when head > 0 do + [head | positive(tail)] + end + + def positive([_ | tail]), do: positive(tail) +end diff --git a/chapter3/recursion_practice_tc.ex b/chapter3/recursion_practice_tc.ex new file mode 100644 index 0000000..115fd47 --- /dev/null +++ b/chapter3/recursion_practice_tc.ex @@ -0,0 +1,33 @@ +defmodule ListHelper do + def list_len(list) do + list_len_helper(0, list) + end + + defp list_len_helper(current_len, []), do: current_len + + defp list_len_helper(current_len, [_ | tail]) do + list_len_helper(current_len + 1, tail) + end + + def range(num1, num2), do: range_helper([], num1, num2) + + defp range_helper(current_list, num, num), do: [num | current_list] + + defp range_helper(current_list, num1, num2) do + range_helper([num2 | current_list], num1, num2 - 1) + end + + def positive(list) do + Enum.reverse(positive_helper([], list)) + end + + defp positive_helper(current_list, []), do: current_list + + defp positive_helper(current_list, [head | tail]) when head > 0 do + positive_helper([head | current_list], tail) + end + + defp positive_helper(current_list, [_ | tail]) do + positive_helper(current_list, tail) + end +end diff --git a/chapter3/sum_list.ex b/chapter3/sum_list.ex new file mode 100644 index 0000000..45031fc --- /dev/null +++ b/chapter3/sum_list.ex @@ -0,0 +1,7 @@ +defmodule ListHelper do + def sum([]), do: 0 + + def sum([head | tail]) do + head + sum(tail) + end +end diff --git a/chapter3/sum_list_tc.ex b/chapter3/sum_list_tc.ex new file mode 100644 index 0000000..bfe7cfb --- /dev/null +++ b/chapter3/sum_list_tc.ex @@ -0,0 +1,16 @@ +defmodule ListHelper do + def sum(list) do + do_sum(0, list) + end + + defp do_sum(current_sum, []) do + current_sum + end + + defp do_sum(current_sum, [head | tail]) do + # More concise implementation + # do_sum(current_sum + head, tail) + new_sum = head + current_sum + do_sum(new_sum, tail) + end +end diff --git a/chapter3/user_extraction.ex b/chapter3/user_extraction.ex new file mode 100644 index 0000000..23e8935 --- /dev/null +++ b/chapter3/user_extraction.ex @@ -0,0 +1,40 @@ +defmodule UserData do + defp extract_login(%{"login" => login}), do: {:ok, login} + defp extract_login(_), do: {:error, "login missing"} + + defp extract_password(%{"password" => password}), do: {:ok, password} + defp extract_password(_), do: {:error, "password mising"} + + defp extract_email(%{"email" => email}), do: {:ok, email} + defp extract_email(_), do: {:error, "email missing"} + + def extract_user_case(user) do + case extract_login(user) do + {:error, reason} -> + {:error, reason} + + {:ok, login} -> + case extract_email(user) do + {:error, reason} -> + {:error, reason} + + {:ok, email} -> + case extract_password(user) do + {:error, reason} -> + {:error, reason} + + {:ok, password} -> + %{login: login, email: email, password: password} + end + end + end + end + + def extract_user_with(user) do + with {:ok, login} <- extract_login(user), + {:ok, email} <- extract_email(user), + {:ok, password} <- extract_password(user) do + {:ok, %{login: login, email: email, password: password}} + end + end +end diff --git a/chapter3/user_extraction_2.ex b/chapter3/user_extraction_2.ex new file mode 100644 index 0000000..761a8c9 --- /dev/null +++ b/chapter3/user_extraction_2.ex @@ -0,0 +1,11 @@ +defmodule UserExtraction do + def extract(user) do + case Enum.filter(["login", "email", "password"], &(not Map.has_key?(user, &1))) do + [] -> + {:ok, %{login: user["login"], email: user["email"], password: user["password"]}} + + missing_fields -> + {:error, "missing fields: #{Enum.join(missing_fields, ", ")}"} + end + end +end diff --git a/chapter4/files/todos.csv b/chapter4/files/todos.csv new file mode 100644 index 0000000..5ec48d0 --- /dev/null +++ b/chapter4/files/todos.csv @@ -0,0 +1,3 @@ +2018/12/19,Dentist +2018/12/20,Shopping +2018/12/19,Movies diff --git a/chapter4/fraction.ex b/chapter4/fraction.ex new file mode 100644 index 0000000..2bbfd82 --- /dev/null +++ b/chapter4/fraction.ex @@ -0,0 +1,16 @@ +defmodule Fraction do + defstruct a: nil, b: nil + + def new(a, b), do: %Fraction{a: a, b: b} + + def value(%Fraction{a: a, b: b}) do + a / b + end + + def add(%Fraction{a: a1, b: b1}, %Fraction{a: a2, b: b2}) do + new( + a1 * b2 + a2 * b1, + b1 * b2 + ) + end +end diff --git a/chapter4/simple_todo.ex b/chapter4/simple_todo.ex new file mode 100644 index 0000000..70c5330 --- /dev/null +++ b/chapter4/simple_todo.ex @@ -0,0 +1,16 @@ +defmodule TodoList do + def new(), do: %{} + + def add_entry(todo_list, date, title) do + Map.update( + todo_list, + date, + [title], + &[title | &1] + ) + end + + def entries(todo_list, date) do + Map.get(todo_list, date, []) + end +end diff --git a/chapter4/todo_builder.ex b/chapter4/todo_builder.ex new file mode 100644 index 0000000..83b703d --- /dev/null +++ b/chapter4/todo_builder.ex @@ -0,0 +1,59 @@ +defmodule TodoList do + defstruct auto_id: 1, entries: %{} + + def new(), do: %TodoList{} + + def new(entries \\ []) do + Enum.reduce( + entries, + %TodoList{}, + fn entry, todo_list_acc -> + add_entry(todo_list_acc, entry) + end + # Alternative definition + # &add_entry(&2, &1) + ) + end + + def add_entry(todo_list, entry) do + entry = Map.put(entry, :id, todo_list.auto_id) + + new_entries = + Map.put( + todo_list.entries, + todo_list.auto_id, + entry + ) + + %TodoList{todo_list | entries: new_entries, auto_id: todo_list.auto_id + 1} + end + + def entries(todo_list, date) do + todo_list.entries + |> Stream.filter(fn {_, entry} -> entry.date == date end) + |> Enum.map(fn {_, entry} -> entry end) + end + + def update_entry(todo_list, %{} = new_entry) do + update_entry(todo_list, new_entry.id, fn _ -> new_entry end) + end + + def update_entry(todo_list, entry_id, updater_fun) do + case Map.fetch(todo_list.entries, entry_id) do + :error -> + todo_list + + {:ok, old_entry} -> + old_entry_id = old_entry.id + # Make sure that the result of the updater is a map and the + # id remains unchanged. + new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry) + new_entries = Map.put(todo_list.entries, new_entry.id, new_entry) + %TodoList{todo_list | entries: new_entries} + end + end + + def delete_entry(todo_list, entry_id) do + %TodoList{todo_list | entries: Map.delete(todo_list.entries, entry_id)} + end +end diff --git a/chapter4/todo_crud.ex b/chapter4/todo_crud.ex new file mode 100644 index 0000000..f3433c6 --- /dev/null +++ b/chapter4/todo_crud.ex @@ -0,0 +1,47 @@ +defmodule TodoList do + defstruct auto_id: 1, entries: %{} + + def new(), do: %TodoList{} + + def add_entry(todo_list, entry) do + entry = Map.put(entry, :id, todo_list.auto_id) + + new_entries = + Map.put( + todo_list.entries, + todo_list.auto_id, + entry + ) + + %TodoList{todo_list | entries: new_entries, auto_id: todo_list.auto_id + 1} + end + + def entries(todo_list, date) do + todo_list.entries + |> Stream.filter(fn {_, entry} -> entry.date == date end) + |> Enum.map(fn {_, entry} -> entry end) + end + + def update_entry(todo_list, %{} = new_entry) do + update_entry(todo_list, new_entry.id, fn _ -> new_entry end) + end + + def update_entry(todo_list, entry_id, updater_fun) do + case Map.fetch(todo_list.entries, entry_id) do + :error -> + todo_list + + {:ok, old_entry} -> + old_entry_id = old_entry.id + # Make sure that the result of the updater is a map and the + # id remains unchanged. + new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry) + new_entries = Map.put(todo_list.entries, new_entry.id, new_entry) + %TodoList{todo_list | entries: new_entries} + end + end + + def delete_entry(todo_list, entry_id) do + %TodoList{todo_list | entries: Map.delete(todo_list.entries, entry_id)} + end +end diff --git a/chapter4/todo_import.ex b/chapter4/todo_import.ex new file mode 100644 index 0000000..56fa15f --- /dev/null +++ b/chapter4/todo_import.ex @@ -0,0 +1,126 @@ +defmodule TodoList do + defstruct auto_id: 1, entries: %{} + + def new(entries \\ []) do + Enum.reduce( + entries, + %TodoList{}, + fn entry, todo_list_acc -> + add_entry(todo_list_acc, entry) + end + # Alternative definition + # &add_entry(&2, &1) + ) + end + + def add_entry(todo_list, entry) do + entry = Map.put(entry, :id, todo_list.auto_id) + + new_entries = + Map.put( + todo_list.entries, + todo_list.auto_id, + entry + ) + + %TodoList{todo_list | entries: new_entries, auto_id: todo_list.auto_id + 1} + end + + def entries(todo_list, date) do + todo_list.entries + |> Stream.filter(fn {_, entry} -> entry.date == date end) + |> Enum.map(fn {_, entry} -> entry end) + end + + def update_entry(todo_list, %{} = new_entry) do + update_entry(todo_list, new_entry.id, fn _ -> new_entry end) + end + + def update_entry(todo_list, entry_id, updater_fun) do + case Map.fetch(todo_list.entries, entry_id) do + :error -> + todo_list + + {:ok, old_entry} -> + old_entry_id = old_entry.id + # Make sure that the result of the updater is a map and the + # id remains unchanged. + new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry) + new_entries = Map.put(todo_list.entries, new_entry.id, new_entry) + %TodoList{todo_list | entries: new_entries} + end + end + + def delete_entry(todo_list, entry_id) do + %TodoList{todo_list | entries: Map.delete(todo_list.entries, entry_id)} + end +end + +defmodule TodoList.CsvImporter do + def import(path) do + File.stream!(path) + |> Stream.map(&String.replace(&1, "\n", "")) + |> Stream.map(fn string -> + [date, title] = String.split(string, ",") + {date, title} + end) + |> Stream.map(fn {date, title} -> + [year, month, day] = + date + |> String.split("/") + |> Enum.map(&String.to_integer(&1)) + + {{year, month, day}, title} + end) + |> Stream.map(fn {{year, month, day}, title} -> + {:ok, date} = Date.new(year, month, day) + %{date: date, title: title} + end) + |> TodoList.new() + end +end + +defmodule TodoList.CsvImporterAlternative do + def import(path) do + path + |> read_lines + |> create_entires + |> TodoList.new() + end + + defp read_lines(path) do + path + |> File.stream!() + |> Stream.map(&String.replace(&1, "\n", "")) + end + + defp create_entires(lines) do + lines + |> Stream.map(&extract_fileds/1) + |> Stream.map(&create_entry/1) + end + + defp extract_fileds(line) do + line + |> String.split(",") + |> convert_date + end + + defp convert_date([date_string, title]) do + {parse_date(date_string), title} + end + + defp parse_date(date_string) do + [year, month, day] = + date_string + |> String.split("/") + |> Enum.map(&String.to_integer/1) + + {:ok, date = Date.new(year, month, day)} + date + end + + defp create_entry({date, title}) do + %{date: date, title: title} + end +end diff --git a/chapter4/todo_map_entry.ex b/chapter4/todo_map_entry.ex new file mode 100644 index 0000000..f29d4bf --- /dev/null +++ b/chapter4/todo_map_entry.ex @@ -0,0 +1,28 @@ +defmodule MultiDict do + def new(), do: %{} + + def add(dict, key, value) do + Map.update( + dict, + key, + [value], + &[value | &1] + ) + end + + def get(dict, key) do + Map.get(dict, key, []) + end +end + +defmodule TodoList do + def new(), do: MultiDict.new() + + def add_entry(todo_list, entry) do + MultiDict.add(todo_list, entry.date, entry) + end + + def entries(todo_list, date) do + MultiDict.get(todo_list, date) + end +end diff --git a/chapter4/todo_multi_dict.ex b/chapter4/todo_multi_dict.ex new file mode 100644 index 0000000..6d8644b --- /dev/null +++ b/chapter4/todo_multi_dict.ex @@ -0,0 +1,28 @@ +defmodule MultiDict do + def new(), do: %{} + + def add(dict, key, value) do + Map.update( + dict, + key, + [value], + &[value | &1] + ) + end + + def get(dict, key) do + Map.get(dict, key, []) + end +end + +defmodule TodoList do + def new(), do: MultiDict.new() + + def add_entry(todo_list, date, title) do + MultiDict.add(todo_list, date, title) + end + + def entries(todo_list, date) do + MultiDict.get(todo_list, date) + end +end diff --git a/chapter5/calculator.ex b/chapter5/calculator.ex new file mode 100644 index 0000000..faf8c09 --- /dev/null +++ b/chapter5/calculator.ex @@ -0,0 +1,46 @@ +defmodule Calculator do + def start do + spawn(fn -> loop(0) end) + end + + defp loop(current_value) do + new_value = + receive do + {:value, caller} -> + send(caller, {:response, current_value}) + current_value + + {:add, value} -> + current_value + value + + {:sub, value} -> + current_value - value + + {:mul, value} -> + current_value * mul + + {:div, value} -> + current_value / value + + invalid_request -> + IO.puts("invalid request #{inspect(invalid_request)}") + current_value + end + + loop(new_value) + end + + def value(server_pid) do + send(server_pid, {:value, self()}) + + receive do + {:response, value} -> + value + end + end + + def add(server_pid, value), do: send(server_pid, {:add, value}) + def sub(server_pid, value), do: send(server_pid, {:sub, value}) + def mul(server_pid, value), do: send(server_pid, {:mul, value}) + def div(server_pid, value), do: send(server_pid, {:div, value}) +end diff --git a/chapter5/calculator_refactor.ex b/chapter5/calculator_refactor.ex new file mode 100644 index 0000000..a7936b4 --- /dev/null +++ b/chapter5/calculator_refactor.ex @@ -0,0 +1,35 @@ +defmodule Calculator do + def start do + spawn(fn -> loop(0) end) + end + + defp loop(current_value) do + new_value = + receive do + message -> process_message(current_value, message) + end + + loop(new_value) + end + + defp process_message(current_value, {:value, caller}) do + send(caller, {:response, current_value}) + current_value + end + + defp process_message(current_value, {:add, value}) do + current_value + value + end + + defp process_message(current_value, {:sub, value}) do + current_value - value + end + + defp process_message(current_value, {:mul, value}) do + current_value * value + end + + defp process_message(current_value, {:div, value}) do + current_value / value + end +end diff --git a/chapter5/database_server.ex b/chapter5/database_server.ex new file mode 100644 index 0000000..8bcb10c --- /dev/null +++ b/chapter5/database_server.ex @@ -0,0 +1,31 @@ +defmodule DatabaseServer do + def start do + spawn(&loop/0) + end + + defp loop do + receive do + {:run_query, caller, query_def} -> + send(caller, {:query_result, run_query(query_def)}) + end + + loop() + end + + defp run_query(query_def) do + Process.sleep(2000) + "#{query_def} result" + end + + def run_async(server_pid, query_def) do + send(server_pid, {:run_query, self(), query_def}) + end + + def get_result do + receive do + {:query_result, result} -> result + after + 5000 -> {:error, :timeout} + end + end +end diff --git a/chapter5/process_bottleneck.ex b/chapter5/process_bottleneck.ex new file mode 100644 index 0000000..bd89d32 --- /dev/null +++ b/chapter5/process_bottleneck.ex @@ -0,0 +1,23 @@ +defmodule Server do + def start do + spawn(fn -> loop() end) + end + + def send_msg(server, message) do + send(server, {self(), message}) + + receive do + {:response, response} -> response + end + end + + def loop do + receive do + {caller, message} -> + Process.sleep(1000) + send(caller, {:response, message}) + end + + loop() + end +end diff --git a/chapter5/registered_todo_server.ex b/chapter5/registered_todo_server.ex new file mode 100644 index 0000000..6d243bf --- /dev/null +++ b/chapter5/registered_todo_server.ex @@ -0,0 +1,110 @@ +defmodule TodoServer do + def start do + process_pid = spawn(fn -> loop(TodoList.new()) end) + Process.register(process_pid, :todo_server) + end + + # Alternative for the start function + # def start do + # spawn(fn -> + # Process.register(self(), :todo_server) + # loop(TodoList.new()) + # end) + # end + + def add_entry(new_entry) do + send(:todo_server, {:add_entry, new_entry}) + end + + def entries(date) do + send(:todo_server, {:entries, self(), date}) + + receive do + {:todo_etries, entries} -> entries + after + 5000 -> {:error, :timeout} + end + end + + def update_entry(entry_id, updater_fun) do + send(:todo_server, {:update_entry, entry_id, updater_fun}) + end + + def delete_entry(entry_id) do + send(:todo_server, {:delete_entry, entry_id}) + end + + defp loop(todo_list) do + new_todo_list = + receive do + message -> process_message(todo_list, message) + end + + loop(new_todo_list) + end + + defp process_message(todo_list, {:add_entry, new_entry}) do + TodoList.add_entry(todo_list, new_entry) + end + + defp process_message(todo_list, {:entries, caller, date}) do + send(caller, {:todo_etries, TodoList.entries(todo_list, date)}) + todo_list + end + + defp process_message(todo_list, {:update_entry, entry_id, updater_fun}) do + TodoList.update_entry(todo_list, entry_id, updater_fun) + end + + defp process_message(todo_list, {:delete_entry, entry_id}) do + TodoList.delete_entry(todo_list, entry_id) + end +end + +defmodule TodoList do + defstruct auto_id: 1, entries: %{} + + def new(), do: %TodoList{} + + def add_entry(todo_list, entry) do + entry = Map.put(entry, :id, todo_list.auto_id) + + new_entries = + Map.put( + todo_list.entries, + todo_list.auto_id, + entry + ) + + %TodoList{todo_list | entries: new_entries, auto_id: todo_list.auto_id + 1} + end + + def entries(todo_list, date) do + todo_list.entries + |> Stream.filter(fn {_, entry} -> entry.date == date end) + |> Enum.map(fn {_, entry} -> entry end) + end + + def update_entry(todo_list, %{} = new_entry) do + update_entry(todo_list, new_entry.id, fn _ -> new_entry end) + end + + def update_entry(todo_list, entry_id, updater_fun) do + case Map.fetch(todo_list.entries, entry_id) do + :error -> + todo_list + + {:ok, old_entry} -> + old_entry_id = old_entry.id + # Make sure that the result of the updater is a map and the + # id remains unchanged. + new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry) + new_entries = Map.put(todo_list.entries, new_entry.id, new_entry) + %TodoList{todo_list | entries: new_entries} + end + end + + def delete_entry(todo_list, entry_id) do + %TodoList{todo_list | entries: Map.delete(todo_list.entries, entry_id)} + end +end diff --git a/chapter5/stateful_database_server.ex b/chapter5/stateful_database_server.ex new file mode 100644 index 0000000..c9c6d33 --- /dev/null +++ b/chapter5/stateful_database_server.ex @@ -0,0 +1,34 @@ +defmodule DatabaseServer do + def start do + spawn(fn -> + connection = :rand.uniform(1000) + loop(connection) + end) + end + + defp loop(connection) do + receive do + {:run_query, caller, query_def} -> + send(caller, {:query_result, run_query(connection, query_def)}) + end + + loop(connection) + end + + defp run_query(connection, query_def) do + Process.sleep(2000) + "Connection #{connection}: #{query_def} result" + end + + def run_async(server_pid, query_def) do + send(server_pid, {:run_query, self(), query_def}) + end + + def get_result do + receive do + {:query_result, result} -> result + after + 5000 -> {:error, :timeout} + end + end +end diff --git a/chapter5/todo_server.ex b/chapter5/todo_server.ex new file mode 100644 index 0000000..cc94e80 --- /dev/null +++ b/chapter5/todo_server.ex @@ -0,0 +1,101 @@ +defmodule TodoServer do + def start do + spawn(fn -> loop(TodoList.new()) end) + end + + def add_entry(todo_server, new_entry) do + send(todo_server, {:add_entry, new_entry}) + end + + def entries(todo_server, date) do + send(todo_server, {:entries, self(), date}) + + receive do + {:todo_etries, entries} -> entries + after + 5000 -> {:error, :timeout} + end + end + + def update_entry(todo_server, entry_id, updater_fun) do + send(todo_server, {:update_entry, entry_id, updater_fun}) + end + + def delete_entry(todo_server, entry_id) do + send(todo_server, {:delete_entry, entry_id}) + end + + defp loop(todo_list) do + new_todo_list = + receive do + message -> process_message(todo_list, message) + end + + loop(new_todo_list) + end + + defp process_message(todo_list, {:add_entry, new_entry}) do + TodoList.add_entry(todo_list, new_entry) + end + + defp process_message(todo_list, {:entries, caller, date}) do + send(caller, {:todo_etries, TodoList.entries(todo_list, date)}) + todo_list + end + + defp process_message(todo_list, {:update_entry, entry_id, updater_fun}) do + TodoList.update_entry(todo_list, entry_id, updater_fun) + end + + defp process_message(todo_list, {:delete_entry, entry_id}) do + TodoList.delete_entry(todo_list, entry_id) + end +end + +defmodule TodoList do + defstruct auto_id: 1, entries: %{} + + def new(), do: %TodoList{} + + def add_entry(todo_list, entry) do + entry = Map.put(entry, :id, todo_list.auto_id) + + new_entries = + Map.put( + todo_list.entries, + todo_list.auto_id, + entry + ) + + %TodoList{todo_list | entries: new_entries, auto_id: todo_list.auto_id + 1} + end + + def entries(todo_list, date) do + todo_list.entries + |> Stream.filter(fn {_, entry} -> entry.date == date end) + |> Enum.map(fn {_, entry} -> entry end) + end + + def update_entry(todo_list, %{} = new_entry) do + update_entry(todo_list, new_entry.id, fn _ -> new_entry end) + end + + def update_entry(todo_list, entry_id, updater_fun) do + case Map.fetch(todo_list.entries, entry_id) do + :error -> + todo_list + + {:ok, old_entry} -> + old_entry_id = old_entry.id + # Make sure that the result of the updater is a map and the + # id remains unchanged. + new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry) + new_entries = Map.put(todo_list.entries, new_entry.id, new_entry) + %TodoList{todo_list | entries: new_entries} + end + end + + def delete_entry(todo_list, entry_id) do + %TodoList{todo_list | entries: Map.delete(todo_list.entries, entry_id)} + end +end diff --git a/chapter6/server_process.ex b/chapter6/server_process.ex new file mode 100644 index 0000000..b209918 --- /dev/null +++ b/chapter6/server_process.ex @@ -0,0 +1,55 @@ +defmodule ServerProcess do + def start(callback_module) do + spawn(fn -> + initial_state = callback_module.init() + loop(callback_module, initial_state) + end) + end + + def loop(callback_module, current_state) do + receive do + {request, caller} -> + {response, new_state} = + callback_module.handle_call( + request, + current_state + ) + + send(caller, {:response, response}) + loop(callback_module, new_state) + end + end + + def call(server_pid, request) do + send(server_pid, {request, self()}) + + receive do + {:response, response} -> + response + end + end +end + +defmodule KeyValueStore do + def init, do: %{} + + def start do + ServerProcess.start(KeyValueStore) + end + + def put(pid, key, value) do + ServerProcess.call(pid, {:put, key, value}) + end + + def get(pid, key) do + ServerProcess.call(pid, {:get, key}) + end + + def handle_call({:put, key, value}, state) do + {:ok, Map.put(state, key, value)} + end + + def handle_call({:get, key}, state) do + {Map.get(state, key), state} + end +end diff --git a/circle.ex b/circle.ex new file mode 100644 index 0000000..4107150 --- /dev/null +++ b/circle.ex @@ -0,0 +1,12 @@ +defmodule Circle do + @moduledoc "Implements basic circle functions" + @pi 3.14159 + + @doc "Computes the area of a circle" + @spec area(number) :: number + def area(r), do: r * r * @pi + + @doc "Computes the circumferenc of a circle" + @spec circumference(number) :: number + def circumference(r), do: 2 * r * @pi +end diff --git a/geometry.ex b/geometry.ex new file mode 100644 index 0000000..9e3f474 --- /dev/null +++ b/geometry.ex @@ -0,0 +1,30 @@ +defmodule Geometry do + def rectangle_area(a, b) do + a * b + end + + def rectangle_area_condensed(a, b), do: a * b +end + +defmodule Rectangle do + def area(a, b), do: a * b + def area(a), do: area(a, a) +end + +defmodule GeometryMultiClause do + def area({:rectangle, a, b}) do + a * b + end + + def area({:square, a}) do + a * a + end + + def area({:circle, r}) do + r * r * 3.14159 + end + + def area(unknown) do + {:error, {:unkown_shape, unknown}} + end +end diff --git a/private_fun.ex b/private_fun.ex new file mode 100644 index 0000000..5a518de --- /dev/null +++ b/private_fun.ex @@ -0,0 +1,9 @@ +defmodule TestPrivate do + def double(a) do + sum(a, a) + end + + defp sum(a, b) do + a + b + end +end diff --git a/rect.ex b/rect.ex new file mode 100644 index 0000000..bcfee99 --- /dev/null +++ b/rect.ex @@ -0,0 +1,5 @@ +defmodule Rectangle do + def area({a, b}) do + a * b + end +end diff --git a/script.exs b/script.exs new file mode 100644 index 0000000..b25dbee --- /dev/null +++ b/script.exs @@ -0,0 +1,7 @@ +defmodule MyModule do + def run do + IO.puts("Called MyModule run!") + end +end + +MyModule.run() diff --git a/test_num.ex b/test_num.ex new file mode 100644 index 0000000..222995e --- /dev/null +++ b/test_num.ex @@ -0,0 +1,11 @@ +defmodule TestNum do + def test(x) when x < 0 do + :negative + end + + def test(0), do: :zero + + def test(x) when x > 0 do + :positive + end +end diff --git a/test_num2.ex b/test_num2.ex new file mode 100644 index 0000000..02ebd41 --- /dev/null +++ b/test_num2.ex @@ -0,0 +1,11 @@ +defmodule TestNum do + def test(x) when is_number(x) and x < 0 do + :negative + end + + def test(0), do: :zero + + def test(x) when is_number(x) and x > 0 do + :positive + end +end