Skip to content

Commit a98b649

Browse files
committed
wip [ci skip]
1 parent 488399e commit a98b649

File tree

6 files changed

+1371
-0
lines changed

6 files changed

+1371
-0
lines changed

lib/koans/24_with_statement.ex

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
defmodule WithStatement do
2+
@moduledoc false
3+
use Koans
4+
5+
@intro "The With Statement - Elegant error handling and happy path programming"
6+
7+
# TODO: default return value
8+
9+
koan "With lets you chain operations that might fail" do
10+
parse_and_add = fn str1, str2 ->
11+
with {:ok, a} <- Integer.parse(str1),
12+
{:ok, b} <- Integer.parse(str2) do
13+
{:ok, a + b}
14+
else
15+
:error -> {:error, :invalid_number}
16+
end
17+
end
18+
19+
assert parse_and_add.("5", "4") == ___
20+
assert parse_and_add.("abc", "1") == ___
21+
end
22+
23+
koan "With short-circuits on the first non-matching pattern" do
24+
process_user = fn user_data ->
25+
with {:ok, name} <- Map.fetch(user_data, :name),
26+
{:ok, age} <- Map.fetch(user_data, :age),
27+
true <- age >= 18 do
28+
{:ok, "Adult user: #{name}"}
29+
else
30+
:error -> {:error, :missing_data}
31+
false -> {:error, :underage}
32+
end
33+
end
34+
35+
assert process_user.(%{name: "Alice", age: 25}) == ___
36+
assert process_user.(%{name: "Bob", age: 16}) == ___
37+
assert process_user.(%{age: 25}) == ___
38+
end
39+
40+
defp safe_divide(_, 0), do: {:error, :division_by_zero}
41+
defp safe_divide(x, y), do: {:ok, x / y}
42+
43+
defp safe_sqrt(x) when x < 0, do: {:error, :negative_sqrt}
44+
defp safe_sqrt(x), do: {:ok, :math.sqrt(x)}
45+
46+
koan "With can handle multiple different error patterns" do
47+
divide_and_sqrt = fn x, y ->
48+
with {:ok, division} <- safe_divide(x, y),
49+
{:ok, sqrt} <- safe_sqrt(division) do
50+
{:ok, sqrt}
51+
else
52+
{:error, :division_by_zero} -> {:error, "Cannot divide by zero"}
53+
{:error, :negative_sqrt} -> {:error, "Cannot take square root of negative number"}
54+
end
55+
end
56+
57+
assert divide_and_sqrt.(16, 4) == ___
58+
assert divide_and_sqrt.(10, 0) == ___
59+
assert divide_and_sqrt.(-16, 4) == ___
60+
end
61+
62+
koan "With works great for nested data extraction" do
63+
get_user_email = fn data ->
64+
with {:ok, user} <- Map.fetch(data, :user),
65+
{:ok, profile} <- Map.fetch(user, :profile),
66+
{:ok, email} <- Map.fetch(profile, :email),
67+
true <- String.contains?(email, "@") do
68+
{:ok, email}
69+
else
70+
:error -> {:error, :missing_data}
71+
false -> {:error, :invalid_email}
72+
end
73+
end
74+
75+
valid_data = %{
76+
user: %{
77+
profile: %{
78+
email: "user@example.com"
79+
}
80+
}
81+
}
82+
83+
invalid_email_data = %{
84+
user: %{
85+
profile: %{
86+
email: "notanemail"
87+
}
88+
}
89+
}
90+
91+
assert get_user_email.(valid_data) == ___
92+
assert get_user_email.(invalid_email_data) == ___
93+
assert get_user_email.(%{}) == ___
94+
end
95+
96+
koan "With can combine pattern matching with guards" do
97+
process_number = fn input ->
98+
with {:ok, num} <- Integer.parse(input),
99+
true <- num > 0,
100+
result when result < 1000 <- num * 10 do
101+
{:ok, result}
102+
else
103+
:error -> {:error, :not_a_number}
104+
false -> {:error, :not_positive}
105+
result when result >= 1000 -> {:error, :result_too_large}
106+
end
107+
end
108+
109+
assert process_number.("5") == ___
110+
assert process_number.("-5") == ___
111+
assert process_number.("150") == ___
112+
assert process_number.("abc") == ___
113+
end
114+
115+
koan "With clauses can have side effects and assignments" do
116+
register_user = fn user_data ->
117+
with {:ok, email} <- validate_email(user_data[:email]),
118+
{:ok, password} <- validate_password(user_data[:password]),
119+
hashed_password = hash_password(password),
120+
{:ok, user} <- save_user(email, hashed_password) do
121+
{:ok, user}
122+
else
123+
{:error, reason} -> {:error, reason}
124+
end
125+
end
126+
127+
user_data = %{email: "test@example.com", password: "secure123"}
128+
assert register_user.(user_data) == ___
129+
end
130+
131+
defp validate_email(email) when is_binary(email) and byte_size(email) > 0 do
132+
if String.contains?(email, "@"), do: {:ok, email}, else: {:error, :invalid_email}
133+
end
134+
135+
defp validate_email(_), do: {:error, :invalid_email}
136+
137+
defp validate_password(password) when is_binary(password) and byte_size(password) >= 6 do
138+
{:ok, password}
139+
end
140+
141+
defp validate_password(_), do: {:error, :weak_password}
142+
143+
defp hash_password(password), do: "hashed_" <> password
144+
145+
defp save_user(email, hashed_password) do
146+
{:ok, %{id: 1, email: email, password: hashed_password}}
147+
end
148+
149+
koan "With can be used without an else clause for simpler cases" do
150+
simple_calculation = fn x, y ->
151+
with num1 when is_number(num1) <- x,
152+
num2 when is_number(num2) <- y do
153+
num1 + num2
154+
end
155+
end
156+
157+
assert simple_calculation.(5, 3) == ___
158+
# When pattern doesn't match and no else, returns the non-matching value
159+
assert simple_calculation.("5", 3) == ___
160+
end
161+
162+
koan "With integrates beautifully with pipe operators" do
163+
process_order = fn order_data ->
164+
order_data
165+
|> validate_order()
166+
|> case do
167+
{:ok, order} ->
168+
with {:ok, payment} <- process_payment(order),
169+
{:ok, shipment} <- create_shipment(order, payment) do
170+
{:ok, %{order: order, payment: payment, shipment: shipment}}
171+
end
172+
173+
error ->
174+
error
175+
end
176+
end
177+
178+
valid_order = %{item: "book", price: 20, customer: "alice"}
179+
assert process_order.(valid_order) == ___
180+
end
181+
182+
defp validate_order(%{item: item, price: price, customer: customer})
183+
when is_binary(item) and is_number(price) and price > 0 and is_binary(customer) do
184+
{:ok, %{item: item, price: price, customer: customer, id: 123}}
185+
end
186+
187+
defp validate_order(_), do: {:error, :invalid_order}
188+
189+
defp process_payment(%{price: price}) when price > 0 do
190+
{:ok, %{amount: price, status: "paid", id: 456}}
191+
end
192+
193+
defp create_shipment(%{customer: customer}, %{status: "paid"}) do
194+
{:ok, %{customer: customer, status: "shipped", tracking: "ABC123"}}
195+
end
196+
197+
koan "With can handle complex nested error scenarios" do
198+
complex_workflow = fn data ->
199+
with {:ok, step1} <- step_one(data),
200+
{:ok, step2} <- step_two(step1),
201+
{:ok, step3} <- step_three(step2) do
202+
{:ok, step3}
203+
else
204+
{:error, :step1_failed} -> {:error, "Failed at step 1: invalid input"}
205+
{:error, :step2_failed} -> {:error, "Failed at step 2: processing error"}
206+
{:error, :step3_failed} -> {:error, "Failed at step 3: final validation error"}
207+
other -> {:error, "Unexpected error: #{inspect(other)}"}
208+
end
209+
end
210+
211+
assert complex_workflow.("valid") == ___
212+
assert complex_workflow.("step1_fail") == ___
213+
assert complex_workflow.("step2_fail") == ___
214+
end
215+
216+
defp step_one("step1_fail"), do: {:error, :step1_failed}
217+
defp step_one(data), do: {:ok, "step1_" <> data}
218+
219+
defp step_two("step1_step2_fail"), do: {:error, :step2_failed}
220+
defp step_two(data), do: {:ok, "step2_" <> data}
221+
222+
defp step_three("step2_step1_step3_fail"), do: {:error, :step3_failed}
223+
defp step_three(data), do: {:ok, "step3_" <> data}
224+
end

0 commit comments

Comments
 (0)