Skip to content

Commit 9a69b72

Browse files
committed
wip [ci skip]
1 parent 5865e4f commit 9a69b72

File tree

7 files changed

+1496
-0
lines changed

7 files changed

+1496
-0
lines changed

CLAUDE_THOUGHTS_ON_KOANS.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Claude's Analysis of Elixir Koans
2+
3+
## Overall Assessment
4+
5+
The Elixir koans provide a solid foundation for learning Elixir's core concepts through hands-on practice. The progression from basic data types to advanced concurrency concepts follows a logical learning path that builds knowledge incrementally.
6+
7+
## Strengths
8+
9+
### 1. **Excellent Progression and Coverage**
10+
- Well-structured from fundamentals (equalities, strings, numbers) to advanced topics (processes, GenServers, protocols)
11+
- Covers all essential Elixir data types and concepts systematically
12+
- Good balance between breadth and depth
13+
14+
### 2. **Interactive Learning Approach**
15+
- The fill-in-the-blank (`___`) format encourages active engagement
16+
- Immediate feedback through test execution
17+
- Zen-like koan naming creates an engaging learning atmosphere
18+
19+
### 3. **Strong Foundation Building**
20+
- **Basic Types**: Numbers, strings, atoms, booleans are well covered
21+
- **Data Structures**: Comprehensive coverage of lists, tuples, maps, keyword lists, MapSets, and structs
22+
- **Advanced Features**: Pattern matching, functions, enums, and comprehensions are thoughtfully presented
23+
24+
### 4. **Concurrency Excellence**
25+
- Outstanding coverage of Elixir's actor model with processes, Tasks, Agents, and GenServers
26+
- Practical examples showing message passing, state management, and supervision
27+
- Good introduction to OTP concepts
28+
29+
## Areas for Improvement
30+
31+
### 1. **Missing Fundamental Concepts**
32+
- **Pipe Operator**: Only briefly mentioned in functions.ex:104-111, but deserves dedicated coverage as it's idiomatic Elixir
33+
- **with Statement**: Missing entirely - important for error handling and nested operations
34+
- **Case/Cond/If Statements**: Only case is briefly shown in pattern matching
35+
- **Guard Clauses**: Mentioned in functions but could use more comprehensive coverage
36+
- **Binary Pattern Matching**: Missing - important for working with binary data
37+
38+
### 2. **Limited Error Handling**
39+
- Only basic error tuple patterns (`{:ok, value}`, `{:error, reason}`) are shown
40+
- Missing `try/catch/rescue/after` constructs
41+
- No coverage of custom exception types
42+
- Could benefit from more comprehensive error handling patterns
43+
44+
### 3. **Module System Gaps**
45+
- Basic module definition shown but missing:
46+
- Module attributes beyond `@moduledoc`
47+
- Import/alias/require directives
48+
- Module compilation hooks
49+
- Behaviors beyond GenServer
50+
51+
### 4. **Syntax and Language Features**
52+
- **Documentation**: No coverage of `@doc` or doctests
53+
- **Typespecs**: Missing `@spec` and `@type` - important for larger codebases
54+
- **Macros**: Not covered (though perhaps too advanced for koans)
55+
- **Use/Import/Alias**: Mentioned but not explained
56+
57+
### 5. **Practical Application**
58+
- Most examples are abstract - could benefit from more real-world scenarios
59+
- Missing file I/O operations
60+
- No coverage of common patterns like supervision trees
61+
- HTTP client/server basics could be valuable
62+
63+
## Outdated or Problematic Areas
64+
65+
### 1. **Syntax Updates Needed**
66+
- All syntax appears current for modern Elixir (1.14+)
67+
- No deprecated functions or patterns identified
68+
69+
### 2. **Best Practices Alignment**
70+
- Code follows current Elixir style guidelines
71+
- Function definitions and module structures are idiomatic
72+
73+
### 3. **Minor Issues**
74+
- Line 113 in Numbers.ex uses pattern matching syntax that's slightly advanced for its position
75+
- Some variable names could be more descriptive in complex examples
76+
77+
## Recommended Additions
78+
79+
### 1. **New Koans to Add**
80+
```
81+
21_control_flow.ex # if/unless/cond/case comprehensive coverage
82+
22_error_handling.ex # try/catch/rescue/after, error tuples
83+
23_pipe_operator.ex # |>, then/2, comprehensive piping patterns
84+
24_with_statement.ex # with clauses, error handling patterns
85+
25_binary_matching.ex # <<>>, binary patterns, string manipulation
86+
26_module_attributes.ex # @doc, @spec, @type, compile-time attributes
87+
27_io_and_files.ex # File operations, IO operations
88+
28_otp_behaviors.ex # Custom behaviors, supervision basics
89+
```
90+
91+
### 2. **Enhanced Existing Koans**
92+
- **Functions**: Add more pipe operator examples and capture syntax variations
93+
- **Pattern Matching**: Include more binary pattern matching examples
94+
- **GenServers**: Add supervision and error handling examples
95+
- **Enums**: Include Stream module basics for lazy evaluation
96+
97+
### 3. **Pedagogical Improvements**
98+
- Add more real-world context to abstract examples
99+
- Include common pitfalls and "gotcha" moments
100+
- Add exercises that build on previous koans
101+
- Include performance considerations where relevant
102+
103+
## Conclusion
104+
105+
The Elixir koans are well-crafted and provide excellent coverage of Elixir's core concepts. They successfully teach the fundamentals and introduce advanced topics in a logical progression. The main gaps are in practical error handling, advanced control flow, and some modern Elixir idioms.
106+
107+
**Recommendation**: These koans do a good job introducing Elixir basics. The most impactful improvements would be:
108+
1. Adding dedicated coverage for the pipe operator and `with` statement
109+
2. Expanding error handling beyond basic tuple patterns
110+
3. Including more real-world, practical examples
111+
4. Adding binary pattern matching for string/data processing
112+
113+
The current koans provide a solid foundation, but learners would benefit from supplementary material covering the missing concepts before moving to production Elixir development.

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+
koan "With lets you chain operations that might fail" do
8+
parse_and_add = fn str1, str2 ->
9+
with {:ok, a} <- Integer.parse(str1),
10+
{:ok, b} <- Integer.parse(str2) do
11+
{:ok, a + b}
12+
else
13+
:error -> {:error, :invalid_number}
14+
end
15+
end
16+
17+
assert parse_and_add.("5", "4") == ___
18+
assert parse_and_add.("abc", "1") == ___
19+
end
20+
21+
koan "With short-circuits on the first non-matching pattern" do
22+
process_user = fn user_data ->
23+
with {:ok, name} <- Map.fetch(user_data, :name),
24+
{:ok, age} <- Map.fetch(user_data, :age),
25+
true <- age >= 18 do
26+
{:ok, "Adult user: #{name}"}
27+
else
28+
:error -> {:error, :missing_data}
29+
false -> {:error, :underage}
30+
end
31+
end
32+
33+
assert process_user.(%{name: "Alice", age: 25}) == ___
34+
assert process_user.(%{name: "Bob", age: 16}) == ___
35+
assert process_user.(%{age: 25}) == ___
36+
end
37+
38+
defp safe_divide(_, 0), do: {:error, :division_by_zero}
39+
defp safe_divide(x, y), do: {:ok, x / y}
40+
41+
defp safe_sqrt(x) when x < 0, do: {:error, :negative_sqrt}
42+
defp safe_sqrt(x), do: {:ok, :math.sqrt(x)}
43+
44+
koan "With can handle multiple different error patterns" do
45+
divide_and_sqrt = fn x, y ->
46+
with {:ok, division} <- safe_divide(x, y),
47+
{:ok, sqrt} <- safe_sqrt(division) do
48+
{:ok, sqrt}
49+
else
50+
{:error, :division_by_zero} -> {:error, "Cannot divide by zero"}
51+
{:error, :negative_sqrt} -> {:error, "Cannot take square root of negative number"}
52+
end
53+
end
54+
55+
assert divide_and_sqrt.(16, 4) == ___
56+
assert divide_and_sqrt.(10, 0) == ___
57+
assert divide_and_sqrt.(-16, 4) == ___
58+
end
59+
60+
koan "With works great for nested data extraction" do
61+
get_user_email = fn data ->
62+
with {:ok, user} <- Map.fetch(data, :user),
63+
{:ok, profile} <- Map.fetch(user, :profile),
64+
{:ok, email} <- Map.fetch(profile, :email),
65+
true <- String.contains?(email, "@") do
66+
{:ok, email}
67+
else
68+
:error -> {:error, :missing_data}
69+
false -> {:error, :invalid_email}
70+
end
71+
end
72+
73+
valid_data = %{
74+
user: %{
75+
profile: %{
76+
email: "user@example.com"
77+
}
78+
}
79+
}
80+
81+
invalid_email_data = %{
82+
user: %{
83+
profile: %{
84+
email: "notanemail"
85+
}
86+
}
87+
}
88+
89+
assert get_user_email.(valid_data) == ___
90+
assert get_user_email.(invalid_email_data) == ___
91+
assert get_user_email.(%{}) == ___
92+
end
93+
94+
koan "With can combine pattern matching with guards" do
95+
process_number = fn input ->
96+
with {:ok, num} <- Integer.parse(input),
97+
true <- num > 0,
98+
result when result < 1000 <- num * 10 do
99+
{:ok, result}
100+
else
101+
:error -> {:error, :not_a_number}
102+
false -> {:error, :not_positive}
103+
result when result >= 1000 -> {:error, :result_too_large}
104+
end
105+
end
106+
107+
assert process_number.("5") == ___
108+
assert process_number.("-5") == ___
109+
assert process_number.("150") == ___
110+
assert process_number.("abc") == ___
111+
end
112+
113+
koan "With clauses can have side effects and assignments" do
114+
register_user = fn user_data ->
115+
with {:ok, email} <- validate_email(user_data[:email]),
116+
{:ok, password} <- validate_password(user_data[:password]),
117+
hashed_password = hash_password(password),
118+
{:ok, user} <- save_user(email, hashed_password) do
119+
{:ok, user}
120+
else
121+
{:error, reason} -> {:error, reason}
122+
end
123+
end
124+
125+
user_data = %{email: "test@example.com", password: "secure123"}
126+
assert register_user.(user_data) == ___
127+
end
128+
129+
defp validate_email(email) when is_binary(email) and byte_size(email) > 0 do
130+
if String.contains?(email, "@"), do: {:ok, email}, else: {:error, :invalid_email}
131+
end
132+
133+
defp validate_email(_), do: {:error, :invalid_email}
134+
135+
defp validate_password(password) when is_binary(password) and byte_size(password) >= 6 do
136+
{:ok, password}
137+
end
138+
139+
defp validate_password(_), do: {:error, :weak_password}
140+
141+
defp hash_password(password), do: "hashed_" <> password
142+
143+
defp save_user(email, hashed_password) do
144+
{:ok, %{id: 1, email: email, password: hashed_password}}
145+
end
146+
147+
koan "With can be used without an else clause for simpler cases" do
148+
simple_calculation = fn x, y ->
149+
with num1 when is_number(num1) <- x,
150+
num2 when is_number(num2) <- y do
151+
num1 + num2
152+
end
153+
end
154+
155+
assert simple_calculation.(5, 3) == ___
156+
# When pattern doesn't match and no else, returns the non-matching value
157+
assert simple_calculation.("5", 3) == ___
158+
end
159+
160+
# TODO: Is there any reason the mix the case and with here? Can't they be
161+
# combined? I'm not sure what the point is.
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)