Skip to content

Commit 395eeb3

Browse files
authored
Merge pull request #299 from elixirkoans/iamvery/new-koans
Iterate on new and existing koans
2 parents 48498ae + 05a7d27 commit 395eeb3

18 files changed

+1099
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/deps
44
erl_crash.dump
55
*.ez
6+
.tool-versions

lib/koans/05_tuples.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ defmodule Tuples do
2424
assert Tuple.insert_at({:a, "hi"}, 1, :new_thing) == ___
2525
end
2626

27-
koan "Add things at the end" do
28-
assert Tuple.append({"Huey", "Dewey"}, "Louie") == ___
27+
koan "Add things at the end (by constructing a new tuple)" do
28+
{first, second} = {"Huey", "Dewey"}
29+
extended = {first, second, "Louie"}
30+
assert extended == ___
2931
end
3032

3133
koan "Or remove them" do

lib/koans/12_pattern_matching.ex

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,15 @@ defmodule PatternMatching do
8383
end
8484

8585
koan "Errors are shaped differently than successful results" do
86-
dog = %{type: "dog"}
86+
dog = %{type: "barking"}
8787

88-
result =
88+
type =
8989
case Map.fetch(dog, :type) do
9090
{:ok, value} -> value
9191
:error -> "not present"
9292
end
9393

94-
assert result == ___
94+
assert type == ___
9595
end
9696

9797
defmodule Animal do
@@ -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: %{age: age}, settings: %{theme: theme}}} = user
179+
assert age == ___
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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,28 @@ 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+
124+
user_input = " Hello World "
125+
126+
cleaned =
127+
user_input
128+
|> String.trim()
129+
|> String.downcase()
130+
|> String.replace(" ", "_")
131+
132+
assert cleaned == ___
133+
end
134+
113135
koan "Conveniently keyword lists can be used for function options" do
114136
transform = fn str, opts ->
115137
if opts[:upcase] do
@@ -122,4 +144,16 @@ defmodule Functions do
122144
assert transform.("good", upcase: true) == ___
123145
assert transform.("good", upcase: false) == ___
124146
end
147+
148+
koan "Anonymous functions can use the & capture syntax for very concise definitions" do
149+
add_one = &(&1 + 1)
150+
multiply_by_two = &(&1 * 2)
151+
152+
result = 5 |> add_one.() |> multiply_by_two.()
153+
assert result == ___
154+
155+
# You can also capture existing functions
156+
string_length = &String.length/1
157+
assert string_length.("hello") == ___
158+
end
125159
end

lib/koans/14_enums.ex

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,18 @@ defmodule Enums do
88
assert Enum.count([1, 2, 3]) == ___
99
end
1010

11-
koan "Depending on the type, it counts pairs" do
12-
assert Enum.count(%{a: :foo, b: :bar}) == ___
11+
koan "Counting is similar to length" do
12+
assert length([1, 2, 3]) == ___
13+
end
14+
15+
koan "But it allows you to count certain elements" do
16+
assert Enum.count([1, 2, 3], &(&1 == 2)) == ___
17+
end
18+
19+
koan "Depending on the type, it counts pairs while length does not" do
20+
map = %{a: :foo, b: :bar}
21+
assert Enum.count(map) == ___
22+
assert_raise ___, fn -> length(map) end
1323
end
1424

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

3545
def multiply_by_ten(n), do: 10 * n
3646

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

@@ -66,7 +76,7 @@ defmodule Enums do
6676
assert Enum.zip(letters, numbers) == ___
6777
end
6878

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

@@ -83,4 +93,38 @@ defmodule Enums do
8393
koan "Collapse an entire list of elements down to a single one by repeating a function." do
8494
assert Enum.reduce([1, 2, 3], 0, fn element, accumulator -> element + accumulator end) == ___
8595
end
96+
97+
koan "Enum.chunk_every splits lists into smaller lists of fixed size" do
98+
assert Enum.chunk_every([1, 2, 3, 4, 5, 6], 2) == ___
99+
assert Enum.chunk_every([1, 2, 3, 4, 5], 3) == ___
100+
end
101+
102+
koan "Enum.flat_map transforms and flattens in one step" do
103+
result =
104+
[1, 2, 3]
105+
|> Enum.flat_map(&[&1, &1 * 10])
106+
107+
assert result == ___
108+
end
109+
110+
koan "Enum.group_by organizes elements by a grouping function" do
111+
words = ["apple", "banana", "cherry", "apricot", "blueberry"]
112+
grouped = Enum.group_by(words, &String.first/1)
113+
114+
assert grouped["a"] == ___
115+
assert grouped["b"] == ___
116+
end
117+
118+
koan "Stream provides lazy enumeration for large datasets" do
119+
# Streams are lazy - they don't execute until you call Enum on them
120+
stream =
121+
1..1_000_000
122+
|> Stream.filter(&even?/1)
123+
|> Stream.map(&(&1 * 2))
124+
|> Stream.take(3)
125+
126+
# Nothing has been computed yet!
127+
result = Enum.to_list(stream)
128+
assert result == ___
129+
end
86130
end

lib/koans/18_genservers.ex

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

161161
:ok = Laptop.stop()
162162
end
163+
164+
defmodule TimeoutServer do
165+
@moduledoc false
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+
{:noreply, new_state}
187+
end
188+
end
189+
190+
koan "GenServers can handle info messages and timeouts" do
191+
{:ok, _pid} = TimeoutServer.start_link(100)
192+
# Wait for timeout to occur
193+
:timer.sleep(101)
194+
count = TimeoutServer.get_count()
195+
assert count == ___
196+
197+
GenServer.stop(TimeoutServer)
198+
end
199+
200+
defmodule CrashableServer do
201+
@moduledoc false
202+
use GenServer
203+
204+
def start_link(initial) do
205+
GenServer.start_link(__MODULE__, initial, 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+
koan "GenServers can be supervised and restarted" do
230+
# Start under a supervisor
231+
children = [{CrashableServer, "the state"}]
232+
{:ok, supervisor} = Supervisor.start_link(children, strategy: :one_for_one)
233+
234+
# Server should be running
235+
initial_state = CrashableServer.get_state()
236+
assert initial_state == ___
237+
238+
:ok = CrashableServer.crash()
239+
# Wait for recovery
240+
:timer.sleep(100)
241+
242+
state_after_crash_recovery = CrashableServer.get_state()
243+
assert state_after_crash_recovery == ___
244+
245+
Supervisor.stop(supervisor)
246+
end
163247
end

0 commit comments

Comments
 (0)