Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/deps
erl_crash.dump
*.ez
.tool-versions
6 changes: 4 additions & 2 deletions lib/koans/05_tuples.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ defmodule Tuples do
assert Tuple.insert_at({:a, "hi"}, 1, :new_thing) == ___
end

koan "Add things at the end" do
assert Tuple.append({"Huey", "Dewey"}, "Louie") == ___
koan "Add things at the end (by constructing a new tuple)" do
{first, second} = {"Huey", "Dewey"}
extended = {first, second, "Louie"}
assert extended == ___
end

koan "Or remove them" do
Expand Down
44 changes: 41 additions & 3 deletions lib/koans/12_pattern_matching.ex
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ defmodule PatternMatching do
end

koan "Errors are shaped differently than successful results" do
dog = %{type: "dog"}
dog = %{type: "barking"}

result =
type =
case Map.fetch(dog, :type) do
{:ok, value} -> value
:error -> "not present"
end

assert result == ___
assert type == ___
end

defmodule Animal do
Expand Down Expand Up @@ -166,4 +166,42 @@ defmodule PatternMatching do
^a = ___
end
end

koan "Pattern matching works with nested data structures" do
user = %{
profile: %{
personal: %{name: "Alice", age: 30},
settings: %{theme: "dark", notifications: true}
}
}

%{profile: %{personal: %{age: age}, settings: %{theme: theme}}} = user
assert age == ___
assert theme == ___
end

koan "Lists can be pattern matched with head and tail" do
numbers = [1, 2, 3, 4, 5]

[first, second | rest] = numbers
assert first == ___
assert second == ___
assert rest == ___

[head | _tail] = numbers
assert head == ___
end

koan "Pattern matching can extract values from function return tuples" do
divide = fn
_, 0 -> {:error, :division_by_zero}
x, y -> {:ok, x / y}
end

{:ok, result} = divide.(10, 2)
assert result == ___

{:error, reason} = divide.(10, 0)
assert reason == ___
end
end
34 changes: 34 additions & 0 deletions lib/koans/13_functions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,28 @@ defmodule Functions do
assert result == ___
end

koan "Pipes make data transformation pipelines readable" do
numbers = [1, 2, 3, 4, 5]

result =
numbers
|> Enum.filter(&(&1 > 2))
|> Enum.map(&(&1 * 2))
|> Enum.sum()

assert result == ___

user_input = " Hello World "

cleaned =
user_input
|> String.trim()
|> String.downcase()
|> String.replace(" ", "_")

assert cleaned == ___
end

koan "Conveniently keyword lists can be used for function options" do
transform = fn str, opts ->
if opts[:upcase] do
Expand All @@ -122,4 +144,16 @@ defmodule Functions do
assert transform.("good", upcase: true) == ___
assert transform.("good", upcase: false) == ___
end

koan "Anonymous functions can use the & capture syntax for very concise definitions" do
add_one = &(&1 + 1)
multiply_by_two = &(&1 * 2)

result = 5 |> add_one.() |> multiply_by_two.()
assert result == ___

# You can also capture existing functions
string_length = &String.length/1
assert string_length.("hello") == ___
end
end
52 changes: 48 additions & 4 deletions lib/koans/14_enums.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@ defmodule Enums do
assert Enum.count([1, 2, 3]) == ___
end

koan "Depending on the type, it counts pairs" do
assert Enum.count(%{a: :foo, b: :bar}) == ___
koan "Counting is similar to length" do
assert length([1, 2, 3]) == ___
end

koan "But it allows you to count certain elements" do
assert Enum.count([1, 2, 3], &(&1 == 2)) == ___
end

koan "Depending on the type, it counts pairs while length does not" do
map = %{a: :foo, b: :bar}
assert Enum.count(map) == ___
assert_raise ___, fn -> length(map) end
end

def less_than_five?(n), do: n < 5
Expand All @@ -34,7 +44,7 @@ defmodule Enums do

def multiply_by_ten(n), do: 10 * n

koan "Map converts each element of a list by running some function with it" do
koan "Mapping converts each element of a list by running some function with it" do
assert Enum.map([1, 2, 3], &multiply_by_ten/1) == ___
end

Expand Down Expand Up @@ -66,7 +76,7 @@ defmodule Enums do
assert Enum.zip(letters, numbers) == ___
end

koan "When you want to find that one pesky element" do
koan "When you want to find that one pesky element, it returns the first" do
assert Enum.find([1, 2, 3, 4], &even?/1) == ___
end

Expand All @@ -83,4 +93,38 @@ defmodule Enums do
koan "Collapse an entire list of elements down to a single one by repeating a function." do
assert Enum.reduce([1, 2, 3], 0, fn element, accumulator -> element + accumulator end) == ___
end

koan "Enum.chunk_every splits lists into smaller lists of fixed size" do
assert Enum.chunk_every([1, 2, 3, 4, 5, 6], 2) == ___
assert Enum.chunk_every([1, 2, 3, 4, 5], 3) == ___
end

koan "Enum.flat_map transforms and flattens in one step" do
result =
[1, 2, 3]
|> Enum.flat_map(&[&1, &1 * 10])

assert result == ___
end

koan "Enum.group_by organizes elements by a grouping function" do
words = ["apple", "banana", "cherry", "apricot", "blueberry"]
grouped = Enum.group_by(words, &String.first/1)

assert grouped["a"] == ___
assert grouped["b"] == ___
end

koan "Stream provides lazy enumeration for large datasets" do
# Streams are lazy - they don't execute until you call Enum on them
stream =
1..1_000_000
|> Stream.filter(&even?/1)
|> Stream.map(&(&1 * 2))
|> Stream.take(3)

# Nothing has been computed yet!
result = Enum.to_list(stream)
assert result == ___
end
end
84 changes: 84 additions & 0 deletions lib/koans/18_genservers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,88 @@ defmodule GenServers do

:ok = Laptop.stop()
end

defmodule TimeoutServer do
@moduledoc false
use GenServer

def start_link(timeout) do
GenServer.start_link(__MODULE__, timeout, name: __MODULE__)
end

def init(timeout) do
{:ok, %{count: 0}, timeout}
end

def get_count do
GenServer.call(__MODULE__, :get_count)
end

def handle_call(:get_count, _from, state) do
{:reply, state.count, state}
end

def handle_info(:timeout, state) do
new_state = %{state | count: state.count + 1}
{:noreply, new_state}
end
end

koan "GenServers can handle info messages and timeouts" do
{:ok, _pid} = TimeoutServer.start_link(100)
# Wait for timeout to occur
:timer.sleep(101)
count = TimeoutServer.get_count()
assert count == ___

GenServer.stop(TimeoutServer)
end

defmodule CrashableServer do
@moduledoc false
use GenServer

def start_link(initial) do
GenServer.start_link(__MODULE__, initial, name: __MODULE__)
end

def init(initial) do
{:ok, initial}
end

def crash do
GenServer.cast(__MODULE__, :crash)
end

def get_state do
GenServer.call(__MODULE__, :get_state)
end

def handle_call(:get_state, _from, state) do
{:reply, state, state}
end

def handle_cast(:crash, _state) do
raise "Intentional crash for testing"
end
end

koan "GenServers can be supervised and restarted" do
# Start under a supervisor
children = [{CrashableServer, "the state"}]
{:ok, supervisor} = Supervisor.start_link(children, strategy: :one_for_one)

# Server should be running
initial_state = CrashableServer.get_state()
assert initial_state == ___

:ok = CrashableServer.crash()
# Wait for recovery
:timer.sleep(100)

state_after_crash_recovery = CrashableServer.get_state()
assert state_after_crash_recovery == ___

Supervisor.stop(supervisor)
end
end
Loading