# Error Handling Baba Yaga takes a functional approach to error handling, emphasizing explicit error representation and fail-fast programming practices. Instead of exceptions, the language uses the `Result` type for recoverable errors and assertions for programming errors. ## Philosophy - **No Exceptions**: All errors are values that must be handled explicitly - **Result Type**: Use `Ok`/`Err` variants for operations that might fail - **Assertions**: Use `assert` for programming errors that should never happen - **Validation First**: Validate inputs early with the `validate.*` namespace - **Rich Debugging**: Use `debug.*` tools for development and troubleshooting ## The Result Type The `Result` type represents either success (`Ok value`) or failure (`Err message`). This forces explicit handling of potential failures. ### Basic Result Usage ```baba // Function that might fail divide : x y -> when y is 0 then Err "Division by zero" _ then Ok (x / y); // Using Result with pattern matching handleDivision : x y -> when (divide x y) is Ok result then result Err message then 0; result1 : handleDivision 10 2; // 5 result2 : handleDivision 10 0; // Returns 0 ``` ### Result Type Patterns ```baba // Parsing with Result parsePositiveInt : str -> when str is "0" then Err "Zero is not positive" "1" then Ok 1 "2" then Ok 2 "5" then Ok 5 "10" then Ok 10 _ then Err "Invalid or unsupported number"; // Chaining Result operations processNumber : input -> when (parsePositiveInt input) is Err msg then Err msg Ok num then when (num > 100) is true then Err "Number too large" false then Ok (num * 2); // Usage examples result1 : processNumber "5"; // Ok 10 result2 : processNumber "0"; // Err "Zero is not positive" result3 : processNumber "200"; // Err "Number too large" ``` ## Input Validation Use the `validate.*` namespace to check inputs early and prevent errors downstream. ### Validation Patterns ```baba // Basic validation validateUserInput : input -> when (validate.notEmpty input) is false then Err "Input cannot be empty" true then when (validate.type "String" input) is false then Err "Input must be a string" true then Ok input; // Multiple validation checks validateAge : age -> when (validate.type "Int" age) is false then Err "Age must be an integer" true then when (validate.range 0 150 age) is false then Err "Age must be between 0 and 150" true then Ok age; // Email validation validateEmail : email -> when (validate.notEmpty email) is false then Err "Email cannot be empty" true then when (validate.email email) is false then Err "Invalid email format" true then Ok email; ``` ### Combining Validations ```baba // Validate user registration data validateUser : userData -> with ( nameResult : validateUserInput userData.name; emailResult : validateEmail userData.email; ageResult : validateAge userData.age; ) -> when nameResult is Err msg then Err ("Name error: " .. msg) Ok name then when emailResult is Err msg then Err ("Email error: " .. msg) Ok email then when ageResult is Err msg then Err ("Age error: " .. msg) Ok age then Ok {name: name, email: email, age: age}; // Usage validUser : validateUser {name: "Alice", email: "alice@example.com", age: 25}; invalidUser : validateUser {name: "", email: "bad-email", age: 200}; ``` ## Error Chaining and Propagation Handle sequences of operations that might fail at any step. ### Sequential Operations ```baba // Chain of operations that might fail processUserData : rawData -> when (validateUser rawData) is Err msg then Err ("Validation failed: " .. msg) Ok user then when (checkUserExists user.email) is Err msg then Err ("User check failed: " .. msg) Ok exists then when exists is true then Err "User already exists" false then when (saveUser user) is Err msg then Err ("Save failed: " .. msg) Ok savedUser then Ok savedUser; // Simulated helper functions checkUserExists : email -> when email is "admin@example.com" then Ok true _ then Ok false; saveUser : user -> when (validate.notEmpty user.name) is false then Err "Cannot save user with empty name" true then Ok user; ``` ### Error Recovery Strategies ```baba // Try multiple approaches parseNumberWithFallback : input -> when (parsePositiveInt input) is Ok num then Ok num Err _ then when input is "zero" then Ok 0 "one" then Ok 1 "two" then Ok 2 _ then Err "Could not parse number"; // Provide default values getConfigValue : key defaultValue -> when (loadConfig key) is Ok value then value Err _ then defaultValue; // Simulated config loader loadConfig : key -> when key is "timeout" then Ok 30 "retries" then Ok 3 _ then Err "Config key not found"; ``` ## Assertions vs Results Use `assert` for programming errors (bugs) and `Result` for expected failures. ### When to Use Assert ```baba // Programming errors - should never happen in correct code calculateArea : width height -> // Assert preconditions assert (width > 0) "Width must be positive"; assert (height > 0) "Height must be positive"; assert (validate.type "Number" width) "Width must be a number"; assert (validate.type "Number" height) "Height must be a number"; width * height; // Array bounds checking getElement : list index -> assert (validate.type "List" list) "First argument must be a list"; assert (validate.type "Int" index) "Index must be an integer"; assert (index >= 0) "Index must be non-negative"; assert (index < (length list)) "Index out of bounds"; list.index; ``` ### When to Use Result ```baba // Expected failures - user input, external resources, business logic safeGetElement : list index -> when (validate.type "List" list) is false then Err "Not a list" true then when (validate.type "Int" index) is false then Err "Index must be integer" true then when (validate.range 0 ((length list) - 1) index) is false then Err "Index out of bounds" true then Ok list.index; // File operations (simulated) readUserFile : filename -> when (validate.notEmpty filename) is false then Err "Filename cannot be empty" true then when filename is "config.txt" then Ok "timeout=30,retries=3" "users.json" then Ok "Alice" _ then Err ("File not found: " .. filename); ``` ## Debugging and Development Use the `debug.*` namespace for troubleshooting and development. ### Debug Printing ```baba // Debug intermediate values in error-prone operations complexCalculation : input -> with ( step1 : input * 2; _ : debug.print "Step1" step1; step2 : step1 + 10; _ : debug.print "Step2" step2; result : step2 / 3; _ : debug.print "Final" result; ) -> result; // Debug error paths parseWithDebug : input -> debug.print "Parsing input" input; when (validate.notEmpty input) is false then with (_ : debug.print "Empty input detected";) -> Err "Empty input" true then when (parsePositiveInt input) is Err msg then with (_ : debug.print "Parse error" msg;) -> Err msg Ok result then with (_ : debug.print "Parse success" result;) -> Ok result; ``` ### Value Inspection ```baba // Inspect complex data structures during debugging analyzeUserData : userData -> with ( inspection : debug.inspect userData; _ : debug.print "User data structure:"; _ : debug.print inspection; validation : validateUser userData; _ : debug.print "Validation result" validation; ) -> validation; // Debug function behavior debugFunction : fn input -> with ( fnInfo : debug.inspect fn; _ : debug.print "Function info" fnInfo; _ : debug.print "Input" input; result : fn input; _ : debug.print "Result" result; ) -> result; ``` ## Real-World Error Handling Patterns ### Game State Validation ```baba // Validate game state for consistency validateGameState : state -> with ( playerValid : when (validate.range 0 100 state.playerHealth) is false then Err "Player health out of range" true then Ok state.playerHealth; levelValid : when (validate.range 1 10 state.currentLevel) is false then Err "Invalid level" true then Ok state.currentLevel; inventoryValid : when (validate.notEmpty state.inventory) is false then Err "Inventory cannot be empty" true then Ok state.inventory; ) -> when playerValid is Err msg then Err msg Ok _ then when levelValid is Err msg then Err msg Ok _ then when inventoryValid is Err msg then Err msg Ok _ then Ok state; // Game action with error handling performAction : gameState action -> when (validateGameState gameState) is Err msg then Err ("Invalid game state: " .. msg) Ok validState then when action is "heal" then when (validState.playerHealth < 100) is false then Err "Player already at full health" true then Ok (set validState "playerHealth" 100) "attack" then when (length validState.inventory > 0) is false then Err "No weapons available" true then Ok (set validState "playerHealth" (validState.playerHealth - 10)) _ then Err ("Unknown action: " .. action); ``` ### Data Processing Pipeline ```baba // Process data through multiple stages with error handling processDataPipeline : rawData -> when (validate.notEmpty rawData) is false then Err "No data to process" true then when (cleanData rawData) is Err msg then Err ("Cleaning failed: " .. msg) Ok cleaned then when (transformData cleaned) is Err msg then Err ("Transform failed: " .. msg) Ok transformed then when (validateOutput transformed) is Err msg then Err ("Validation failed: " .. msg) Ok validated then Ok validated; // Simulated pipeline stages cleanData : data -> when (validate.type "List" data) is false then Err "Data must be a list" true then with (filtered : filter (x -> validate.notEmpty x) data;) -> when (validate.notEmpty filtered) is false then Err "No valid data after cleaning" true then Ok filtered; transformData : data -> when (validate.notEmpty data) is false then Err "Cannot transform empty data" true then Ok (map (x -> x * 2) data); validateOutput : data -> when (length data < 1) is true then Err "Output too small" false then when (length data > 1000) is true then Err "Output too large" false then Ok data; ``` ### Configuration Loading ```baba // Load and validate configuration with fallbacks loadConfiguration : configFile -> when (readUserFile configFile) is Err msg then debug.print "Config load failed, using defaults" msg; Ok {timeout: 30, retries: 3, debug: false} Ok content then when (parseConfig content) is Err msg then debug.print "Config parse failed, using defaults" msg; Ok {timeout: 30, retries: 3, debug: false} Ok config then when (validateConfig config) is Err msg then Err ("Invalid config: " .. msg) Ok validConfig then Ok validConfig; // Simulated config parsing parseConfig : content -> when content is "timeout=30,retries=3" then Ok {timeout: 30, retries: 3, debug: false} "timeout=60,retries=5,debug=true" then Ok {timeout: 60, retries: 5, debug: true} _ then Err "Unrecognized config format"; validateConfig : config -> when (validate.range 1 300 config.timeout) is false then Err "Timeout must be 1-300 seconds" true then when (validate.range 1 10 config.retries) is false then Err "Retries must be 1-10" true then Ok config; ``` ## Error Handling Best Practices ### 1. Fail Fast with Validation ```baba // Good: Validate early processUser : userData -> when (validateUser userData) is Err msg then Err msg Ok user then expensiveOperation user; // Avoid: Validate late // processUser : userData -> // result : expensiveOperation userData; // when (validateUser userData) is // Err msg then Err msg // Ok _ then result; ``` ### 2. Provide Meaningful Error Messages ```baba // Good: Specific error messages validatePassword : password -> when (validate.notEmpty password) is false then Err "Password cannot be empty" true then when (str.length password < 8) is true then Err "Password must be at least 8 characters" false then when (validate.email password) is true then Err "Password cannot be an email address" false then Ok password; // Avoid: Generic error messages // validatePassword : password -> // when (someValidation password) is // false then Err "Invalid password" // true then Ok password; ``` ### 3. Use Assertions for Programming Errors ```baba // Good: Assert impossible conditions fibonacci : n -> assert (n >= 0) "Fibonacci input must be non-negative"; when n is 0 then 0 1 then 1 _ then (fibonacci (n - 1)) + (fibonacci (n - 2)); // Good: Use Result for user errors safeFibonacci : n -> when (validate.type "Int" n) is false then Err "Input must be an integer" true then when (validate.range 0 40 n) is false then Err "Input must be between 0 and 40" true then Ok (fibonacci n); ``` ### 4. Debug Complex Error Flows ```baba // Use debug.print to trace error paths complexValidation : data -> debug.print "Starting validation" data; when (validate.notEmpty data) is false then with (_ : debug.print "Failed: empty data";) -> Err "Empty data" true then with (_ : debug.print "Passed: not empty";) -> when (validate.type "List" data) is false then with (_ : debug.print "Failed: not a list";) -> Err "Must be list" true then with (_ : debug.print "Passed: is list";) -> when ((length data) > 100) is true then with (_ : debug.print "Failed: too large";) -> Err "Too large" false then with (_ : debug.print "Success: validation complete";) -> Ok data; ``` ### 5. Compose Error Handling ```baba // Create reusable error handling combinators mapResult : fn result -> when result is Err msg then Err msg Ok value then Ok (fn value); chainResult : fn result -> when result is Err msg then Err msg Ok value then fn value; // Compose operations by nesting function calls processUserChain : userData -> mapResult saveUser (chainResult checkUserExists (chainResult validateUser (Ok userData))); // Or use intermediate variables for clarity processUserStep : userData -> with ( step1 : chainResult validateUser (Ok userData); step2 : chainResult checkUserExists step1; step3 : mapResult saveUser step2; ) -> step3; ``` ## Summary Baba Yaga's error handling approach emphasizes: 1. **Explicit Error Values**: Use `Result` type instead of exceptions 2. **Early Validation**: Check inputs with `validate.*` functions 3. **Clear Distinction**: `assert` for bugs, `Result` for expected failures 4. **Rich Debugging**: Use `debug.*` tools during development 5. **Meaningful Messages**: Provide specific, actionable error information 6. **Composition**: Chain operations while preserving error information This approach leads to more robust, predictable code where error handling is an explicit part of the program's logic rather than an afterthought. ## Array Programming Error Handling Array programming operations include specific error cases that should be handled appropriately: ### Index Bounds Errors ```baba // Safe array access with bounds checking safeAt : indices data -> with ( validIndices : filter (i -> i >= 0 and i < length data) indices; ) -> when (length validIndices = length indices) is true then Ok (at validIndices data) _ then Err "One or more indices out of bounds"; // Usage data : [1, 2, 3]; result1 : safeAt [0, 2] data; // Ok [1, 3] result2 : safeAt [0, 5] data; // Err "One or more indices out of bounds" ``` ### Reshape Dimension Errors ```baba // Safe reshape with dimension validation safeReshape : dimensions flatArray -> with ( totalElements : reduce (acc x -> acc * x) 1 dimensions; arrayLength : length flatArray; ) -> when (totalElements = arrayLength) is true then Ok (reshape dimensions flatArray) _ then Err ("Cannot reshape array of length " .. arrayLength .. " to dimensions " .. dimensions); // Usage data : [1, 2, 3, 4, 5, 6]; result1 : safeReshape [2, 3] data; // Ok (2x3 matrix) result2 : safeReshape [2, 4] data; // Err "Cannot reshape array of length 6 to dimensions [2, 4]" ``` ### Function Type Validation ```baba // Validate function arguments for array operations safeScan : func initial array -> when func is Function then Ok (scan func initial array) _ then Err "scan expects a function as first argument"; // Usage addFunc : acc x -> acc + x; numbers : [1, 2, 3]; result1 : safeScan addFunc 0 numbers; // Ok [0, 1, 3, 6] result2 : safeScan 42 0 numbers; // Err "scan expects a function as first argument" ``` ### Negative Count Validation ```baba // Safe take/drop with non-negative validation safeTake : n array -> when n is count if (count >= 0) then Ok (take count array) _ then Err "take expects a non-negative number"; safeDrop : n array -> when n is count if (count >= 0) then Ok (drop count array) _ then Err "drop expects a non-negative number"; // Usage data : [1, 2, 3, 4, 5]; result1 : safeTake 3 data; // Ok [1, 2, 3] result2 : safeTake -1 data; // Err "take expects a non-negative number" ``` These patterns demonstrate how to wrap array programming operations in safe functions that return `Result` types, allowing graceful error handling in data processing pipelines.