about summary refs log tree commit diff stats
path: root/js/baba-yaga/docs/06_error-handling.md
diff options
context:
space:
mode:
Diffstat (limited to 'js/baba-yaga/docs/06_error-handling.md')
-rw-r--r--js/baba-yaga/docs/06_error-handling.md632
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.