13_Error_Handling

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: