diff options
Diffstat (limited to 'js/baba-yaga/docs/06_error-handling.md')
-rw-r--r-- | js/baba-yaga/docs/06_error-handling.md | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/js/baba-yaga/docs/06_error-handling.md b/js/baba-yaga/docs/06_error-handling.md new file mode 100644 index 0000000..7bc2a78 --- /dev/null +++ b/js/baba-yaga/docs/06_error-handling.md @@ -0,0 +1,632 @@ +# 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. |