Error Handling
What is Error Handling?
Error handling in Baba Yaga is based on functional programming principles - instead of throwing exceptions, we use pattern matching and return values to handle errors gracefully.
Basic Error Handling
Using Pattern Matching
/* Handle division by zero */
safe_divide : x y ->
when y = 0 then "Error: Division by zero"
_ then x / y;
/* Test the function */
..out safe_divide 10 2; /* 5 */
..out safe_divide 10 0; /* Error: Division by zero */
Return Error Values
/* Return structured error information */
divide_with_error : x y ->
when y = 0 then {error: true, message: "Division by zero", dividend: x}
_ then {error: false, result: x / y};
/* Handle the result */
result : divide_with_error 10 0;
when result.error is
true then ..out "Error: " + result.message
false then ..out "Result: " + result.result;
Assertions for Validation
Input Validation
/* Validate function inputs */
factorial : n ->
..assert "n must be non-negative" n >= 0;
when n is
0 then 1
_ then n * (factorial (n - 1));
/* Test validation */
..out factorial 5; /* 120 */
/* factorial -1; */ /* Would fail assertion */
Data Validation
/* Validate table structure */
validate_user : user ->
..assert "user must have name" t.has user "name";
..assert "user must have age" t.has user "age";
..assert "age must be positive" user.age > 0;
user;
/* Test validation */
valid_user : {name: "Alice", age: 30};
invalid_user : {name: "Bob"}; /* Missing age */
validated : validate_user valid_user;
/* validate_user invalid_user; */ /* Would fail assertion */
Error Patterns
Maybe Pattern
/* Maybe pattern for optional values */
find_user : id users ->
when t.has users id then {just: true, value: t.get users id}
_ then {just: false};
/* Handle maybe results */
users : {
alice: {name: "Alice", age: 30},
bob: {name: "Bob", age: 25}
};
result : find_user "alice" users;
when result.just is
true then ..out "Found: " + result.value.name
false then ..out "User not found";
not_found : find_user "charlie" users;
when not_found.just is
true then ..out "Found: " + not_found.value.name
false then ..out "User not found";
Either Pattern
/* Either pattern for success/error */
parse_number : input ->
parsed : parseInt input;
when parsed = NaN then {left: "Invalid number: " + input}
_ then {right: parsed};
/* Handle either results */
valid : parse_number "42";
when valid.left is
_ then ..out "Error: " + valid.left
_ then ..out "Success: " + valid.right;
invalid : parse_number "abc";
when invalid.left is
_ then ..out "Error: " + invalid.left
_ then ..out "Success: " + invalid.right;
Error Recovery
Fallback Values
/* Provide fallback values */
get_config : key default_value config ->
when t.has config key then t.get config key
_ then default_value;
/* Use with fallbacks */
config : {debug: true, timeout: 30};
debug_mode : get_config "debug" false config; /* true */
retries : get_config "retries" 3 config; /* 3 (fallback) */
Retry Logic
/* Simple retry with exponential backoff */
retry_operation : operation max_attempts ->
attempt_operation : attempt ->
when attempt > max_attempts then {error: "Max attempts exceeded"}
_ then
result : operation;
when result.error is
true then
delay : power 2 attempt; /* Exponential backoff */
..out "Attempt " + attempt + " failed, retrying in " + delay + "ms";
attempt_operation (attempt + 1)
false then result;
attempt_operation 1;
Error Propagation
Chaining Error Handling
/* Chain operations that might fail */
process_user_data : user_id ->
/* Step 1: Find user */
user_result : find_user user_id users;
when user_result.just is
false then {error: "User not found: " + user_id}
_ then
user : user_result.value;
/* Step 2: Validate user */
validation_result : validate_user user;
when validation_result.error is
true then {error: "Invalid user data"}
_ then
/* Step 3: Process user */
processed : process_user user;
{success: true, data: processed};
Testing Error Conditions
Test Error Cases
/* Test both success and error cases */
test_safe_divide : ->
/* Test successful division */
..assert "10 / 2 = 5" safe_divide 10 2 = 5;
/* Test division by zero */
error_result : safe_divide 10 0;
..assert "Division by zero returns error" error_result = "Error: Division by zero";
..out "All tests passed";
/* Run the tests */
test_safe_divide;
Property-Based Testing
/* Test properties of error handling */
test_divide_properties : ->
/* Property: safe_divide x 1 = x */
..assert "x / 1 = x" safe_divide 42 1 = 42;
/* Property: safe_divide x 0 always returns error */
..assert "x / 0 always errors" safe_divide 5 0 = "Error: Division by zero";
..assert "x / 0 always errors" safe_divide -3 0 = "Error: Division by zero";
/* Property: safe_divide 0 x = 0 (when x ≠ 0) */
..assert "0 / x = 0" safe_divide 0 5 = 0;
..out "All properties verified";
Best Practices
Keep Error Handling Explicit
/* Good: Explicit error handling */
process_data : data ->
when data = null then {error: "No data provided"}
_ then
result : transform data;
when result.error is
true then result
false then {success: true, data: result.data};
/* Avoid: Silent failures */
bad_process : data ->
transform data; /* What if this fails? */
Use Descriptive Error Messages
/* Good: Descriptive errors */
validate_age : age ->
when age < 0 then "Age cannot be negative: " + age
when age > 150 then "Age seems unrealistic: " + age
_ then age;
/* Avoid: Generic errors */
bad_validate : age ->
when age < 0 then "Invalid input" /* Too generic */
_ then age;
Handle Errors at the Right Level
/* Handle errors where you have context */
process_user : user_id ->
user : find_user user_id;
when user.just is
false then
..emit "user_not_found" {user_id: user_id, timestamp: now()};
"User not found"
_ then
process_user_data user.value;
Next Steps
Now that you understand error handling, explore:
- Integration Patterns for external system error handling
- Advanced Combinators for error handling patterns
- Best Practices for writing robust code