Skip to content

Commit 9eea84f

Browse files
committed
wip [ci skip]
1 parent 6fad214 commit 9eea84f

12 files changed

+2102
-0
lines changed

lib/koans/12_pattern_matching.ex

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,42 @@ defmodule PatternMatching do
166166
^a = ___
167167
end
168168
end
169+
170+
koan "Pattern matching works with nested data structures" do
171+
user = %{
172+
profile: %{
173+
personal: %{name: "Alice", age: 30},
174+
settings: %{theme: "dark", notifications: true}
175+
}
176+
}
177+
178+
%{profile: %{personal: %{name: name}, settings: %{theme: theme}}} = user
179+
assert name == ___
180+
assert theme == ___
181+
end
182+
183+
koan "Lists can be pattern matched with head and tail" do
184+
numbers = [1, 2, 3, 4, 5]
185+
186+
[first, second | rest] = numbers
187+
assert first == ___
188+
assert second == ___
189+
assert rest == ___
190+
191+
[head | _tail] = numbers
192+
assert head == ___
193+
end
194+
195+
koan "Pattern matching can extract values from function return tuples" do
196+
divide = fn
197+
_, 0 -> {:error, :division_by_zero}
198+
x, y -> {:ok, x / y}
199+
end
200+
201+
{:ok, result} = divide.(10, 2)
202+
assert result == ___
203+
204+
{:error, reason} = divide.(10, 0)
205+
assert reason == ___
206+
end
169207
end

lib/koans/13_functions.ex

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,30 @@ defmodule Functions do
110110
assert result == ___
111111
end
112112

113+
koan "Pipes make data transformation pipelines readable" do
114+
numbers = [1, 2, 3, 4, 5]
115+
116+
result =
117+
numbers
118+
|> Enum.filter(&(&1 > 2))
119+
|> Enum.map(&(&1 * 2))
120+
|> Enum.sum()
121+
122+
assert result == ___
123+
end
124+
125+
koan "The pipe operator works with any function that takes the piped value as first argument" do
126+
user_input = " Hello World "
127+
128+
cleaned =
129+
user_input
130+
|> String.trim()
131+
|> String.downcase()
132+
|> String.replace(" ", "_")
133+
134+
assert cleaned == ___
135+
end
136+
113137
koan "Conveniently keyword lists can be used for function options" do
114138
transform = fn str, opts ->
115139
if opts[:upcase] do
@@ -122,4 +146,16 @@ defmodule Functions do
122146
assert transform.("good", upcase: true) == ___
123147
assert transform.("good", upcase: false) == ___
124148
end
149+
150+
koan "Anonymous functions can use the & capture syntax for very concise definitions" do
151+
add_one = &(&1 + 1)
152+
multiply_by_two = &(&1 * 2)
153+
154+
result = 5 |> add_one.() |> multiply_by_two.()
155+
assert result == ___
156+
157+
# You can also capture existing functions
158+
string_length = &String.length/1
159+
assert string_length.("hello") == ___
160+
end
125161
end

lib/koans/14_enums.ex

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,38 @@ defmodule Enums do
8383
koan "Collapse an entire list of elements down to a single one by repeating a function." do
8484
assert Enum.reduce([1, 2, 3], 0, fn element, accumulator -> element + accumulator end) == ___
8585
end
86+
87+
koan "Enum.chunk_every splits lists into smaller lists of fixed size" do
88+
assert Enum.chunk_every([1, 2, 3, 4, 5, 6], 2) == ___
89+
assert Enum.chunk_every([1, 2, 3, 4, 5], 3) == ___
90+
end
91+
92+
koan "Enum.flat_map transforms and flattens in one step" do
93+
result =
94+
[1, 2, 3]
95+
|> Enum.flat_map(&[&1, &1 * 10])
96+
97+
assert result == ___
98+
end
99+
100+
koan "Enum.group_by organizes elements by a grouping function" do
101+
words = ["apple", "banana", "cherry", "apricot", "blueberry"]
102+
grouped = Enum.group_by(words, &String.first/1)
103+
104+
assert grouped["a"] == ___
105+
assert grouped["b"] == ___
106+
end
107+
108+
koan "Stream provides lazy enumeration for large datasets" do
109+
# Streams are lazy - they don't execute until you call Enum on them
110+
stream =
111+
1..1_000_000
112+
|> Stream.filter(&(rem(&1, 2) == 0))
113+
|> Stream.map(&(&1 * 2))
114+
|> Stream.take(3)
115+
116+
# Nothing has been computed yet!
117+
result = Enum.to_list(stream)
118+
assert result == ___
119+
end
86120
end

lib/koans/18_genservers.ex

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,83 @@ defmodule GenServers do
160160

161161
:ok = Laptop.stop()
162162
end
163+
164+
koan "GenServers can handle info messages and timeouts" do
165+
defmodule TimeoutServer do
166+
use GenServer
167+
168+
def start_link(timeout) do
169+
GenServer.start_link(__MODULE__, timeout, name: __MODULE__)
170+
end
171+
172+
def init(timeout) do
173+
{:ok, %{count: 0}, timeout}
174+
end
175+
176+
def get_count do
177+
GenServer.call(__MODULE__, :get_count)
178+
end
179+
180+
def handle_call(:get_count, _from, state) do
181+
{:reply, state.count, state}
182+
end
183+
184+
def handle_info(:timeout, state) do
185+
new_state = %{state | count: state.count + 1}
186+
# Reset timeout
187+
{:noreply, new_state, 1000}
188+
end
189+
end
190+
191+
TimeoutServer.start_link(100)
192+
# Wait for timeouts to occur
193+
:timer.sleep(250)
194+
count = TimeoutServer.get_count()
195+
assert count >= 2 == ___
196+
197+
GenServer.stop(TimeoutServer)
198+
end
199+
200+
koan "GenServers can be supervised and restarted" do
201+
defmodule CrashableServer do
202+
use GenServer
203+
204+
def start_link(_) do
205+
GenServer.start_link(__MODULE__, 0, name: __MODULE__)
206+
end
207+
208+
def init(initial) do
209+
{:ok, initial}
210+
end
211+
212+
def crash do
213+
GenServer.cast(__MODULE__, :crash)
214+
end
215+
216+
def get_state do
217+
GenServer.call(__MODULE__, :get_state)
218+
end
219+
220+
def handle_call(:get_state, _from, state) do
221+
{:reply, state, state}
222+
end
223+
224+
def handle_cast(:crash, _state) do
225+
raise "Intentional crash for testing"
226+
end
227+
end
228+
229+
# Start under a supervisor
230+
{:ok, supervisor} =
231+
Supervisor.start_link(
232+
[{CrashableServer, []}],
233+
strategy: :one_for_one
234+
)
235+
236+
# Server should be running
237+
initial_state = CrashableServer.get_state()
238+
assert initial_state == ___
239+
240+
Supervisor.stop(supervisor)
241+
end
163242
end

lib/koans/21_control_flow.ex

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
defmodule ControlFlow do
2+
@moduledoc false
3+
use Koans
4+
5+
@intro "Control Flow - Making decisions and choosing paths"
6+
7+
koan "If statements evaluate conditions" do
8+
result = if true, do: "yes", else: "no"
9+
assert result == ___
10+
end
11+
12+
koan "If can be written in block form" do
13+
result =
14+
if 1 + 1 == 2 do
15+
"math works"
16+
else
17+
"math is broken"
18+
end
19+
20+
assert result == ___
21+
end
22+
23+
koan "Unless is the opposite of if" do
24+
result = unless false, do: "will execute", else: "will not execute"
25+
assert result == ___
26+
end
27+
28+
koan "Nil and false are falsy, everything else is truthy" do
29+
assert if(nil, do: "truthy", else: "falsy") == ___
30+
assert if(false, do: "truthy", else: "falsy") == ___
31+
assert if(0, do: "truthy", else: "falsy") == ___
32+
assert if("", do: "truthy", else: "falsy") == ___
33+
assert if([], do: "truthy", else: "falsy") == ___
34+
end
35+
36+
koan "Case matches against patterns" do
37+
result =
38+
case {1, 2, 3} do
39+
{4, 5, 6} -> "no match"
40+
{1, x, 3} -> "matched with x = #{x}"
41+
end
42+
43+
assert result == ___
44+
end
45+
46+
koan "Case can have multiple clauses with different patterns" do
47+
check_number = fn x ->
48+
case x do
49+
0 -> "zero"
50+
n when n > 0 -> "positive"
51+
n when n < 0 -> "negative"
52+
end
53+
end
54+
55+
assert check_number.(5) == ___
56+
assert check_number.(0) == ___
57+
assert check_number.(-3) == ___
58+
end
59+
60+
koan "Case clauses are tried in order until one matches" do
61+
check_list = fn list ->
62+
case list do
63+
[] -> "empty"
64+
[_] -> "one element"
65+
[_, _] -> "two elements"
66+
_ -> "many elements"
67+
end
68+
end
69+
70+
assert check_list.([]) == ___
71+
assert check_list.([:a]) == ___
72+
assert check_list.([:a, :b]) == ___
73+
assert check_list.([:a, :b, :c, :d]) == ___
74+
end
75+
76+
koan "Cond evaluates conditions until one is truthy" do
77+
temperature = 25
78+
79+
weather =
80+
cond do
81+
temperature < 0 -> "freezing"
82+
temperature < 10 -> "cold"
83+
temperature < 25 -> "cool"
84+
temperature < 30 -> "warm"
85+
true -> "hot"
86+
end
87+
88+
assert weather == ___
89+
end
90+
91+
koan "Cond requires at least one clause to be true" do
92+
safe_divide = fn x, y ->
93+
cond do
94+
y == 0 -> {:error, "division by zero"}
95+
true -> {:ok, x / y}
96+
end
97+
end
98+
99+
assert safe_divide.(10, 2) == ___
100+
assert safe_divide.(10, 0) == ___
101+
end
102+
103+
koan "If/unless can be used as statement modifiers" do
104+
x = 5
105+
message = if x > 0, do: "x is positive"
106+
assert message == ___
107+
end
108+
109+
koan "Case can destructure complex patterns" do
110+
parse_response = fn response ->
111+
case response do
112+
{:ok, %{status: 200, body: body}} -> "Success: #{body}"
113+
{:ok, %{status: status}} when status >= 400 -> "Client error: #{status}"
114+
{:ok, %{status: status}} when status >= 500 -> "Server error: #{status}"
115+
{:error, reason} -> "Request failed: #{reason}"
116+
end
117+
end
118+
119+
assert parse_response.({:ok, %{status: 200, body: "Hello"}}) == ___
120+
assert parse_response.({:ok, %{status: 404}}) == ___
121+
assert parse_response.({:error, :timeout}) == ___
122+
end
123+
124+
koan "Guards in case can use complex expressions" do
125+
categorize = fn number ->
126+
case number do
127+
n when is_integer(n) and n > 0 and rem(n, 2) == 0 -> "positive even integer"
128+
n when is_integer(n) and n > 0 and rem(n, 2) == 1 -> "positive odd integer"
129+
n when is_integer(n) and n < 0 -> "negative integer"
130+
n when is_float(n) -> "float"
131+
_ -> "other"
132+
end
133+
end
134+
135+
assert categorize.(4) == ___
136+
assert categorize.(3) == ___
137+
assert categorize.(-5) == ___
138+
assert categorize.(3.14) == ___
139+
assert categorize.("hello") == ___
140+
end
141+
142+
koan "Multiple conditions can be checked in sequence" do
143+
process_user = fn user ->
144+
if user.active do
145+
if user.verified do
146+
if user.premium do
147+
"premium verified active user"
148+
else
149+
"verified active user"
150+
end
151+
else
152+
"unverified active user"
153+
end
154+
else
155+
"inactive user"
156+
end
157+
end
158+
159+
user = %{active: true, verified: true, premium: false}
160+
assert process_user.(user) == ___
161+
end
162+
end

0 commit comments

Comments
 (0)