diff options
Diffstat (limited to 'js/scripting-lang/tutorials')
24 files changed, 2310 insertions, 2379 deletions
diff --git a/js/scripting-lang/tutorials/00_Introduction.md b/js/scripting-lang/tutorials/00_Introduction.md index 7050320..cfd2c80 100644 --- a/js/scripting-lang/tutorials/00_Introduction.md +++ b/js/scripting-lang/tutorials/00_Introduction.md @@ -85,7 +85,7 @@ result : increment (double 5); ..out result; /* Output: 11 */ ``` -**Key Point**: Parentheses are needed for negative numbers: `f (-5)` not `f -5`. See the [Juxtaposition tutorial](01_Juxtaposition_Function_Application.md#negative-numbers-and-parentheses) for detailed information about negative number handling. +**Key Point**: Unary minus works without parentheses: `f -5` applies `f` to `negate(5)`. Use spaces around binary operators for clarity: `5 - 3` for subtraction. See the [Juxtaposition tutorial](01_Juxtaposition_Function_Application.md#negative-numbers-and-spacing) for detailed information about operator spacing. ## Pattern Matching with `when` diff --git a/js/scripting-lang/tutorials/01_Function_Calls.md b/js/scripting-lang/tutorials/01_Function_Calls.md new file mode 100644 index 0000000..b251386 --- /dev/null +++ b/js/scripting-lang/tutorials/01_Function_Calls.md @@ -0,0 +1,176 @@ +# Function Calls + +## What is Juxtaposition? + +In Baba Yaga you call functions by putting them next to each other. + +```plaintext +/* + JavaScript: f(x, y) + Baba Yaga: f x y +*/ +``` + +## Basic Examples + +```plaintext +/* Simple function calls */ +add 5 3; /* Instead of add(5, 3) */ +multiply 4 7; /* Instead of multiply(4, 7) */ +subtract 10 3; /* Instead of subtract(10, 3) */ + +/* Function calls with tables */ +/* ...we'll talk more about @ in a bit */ +map @double {1, 2, 3, 4, 5}; +filter @is_even {1, 2, 3, 4, 5, 6}; +reduce @add 0 {1, 2, 3, 4, 5}; +``` + +## How It Works + +The parser automatically translates juxtaposition into nested calls to `apply`, so that + +```plaintext +/* f x y becomes: apply(apply(f, x), y) */ +/* map double {1, 2, 3} becomes: apply(apply(map, double), {1, 2, 3}) */ +``` + +## Precedence Rules + +Juxtaposition has lower precedence than operators, + +```plaintext +result : add 5 multiply 3 4; +/* Parsed as: add 5 (multiply 3 4) */ +/* Result: 5 + (3 * 4) = 17 */ +/* Not as: (add 5 multiply) 3 4 */ +``` +With Baba Yaga you'll use juxtaposition when you + +- call functions with arguments +- build function composition chains +- work with combinators like `map`, `filter`, `reduce` + +You won't use it, exactly, when you are + +- defining functions (use `:` and `->`) +- assigning values (use `:`) +- using operators (use `+`, `-`, `*`, etc.) + +## Common Patterns + +```plaintext +/* Data processing pipeline */ +data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +is_even : x -> x % 2 = 0; +double : x -> x * 2; +sum : x -> reduce add 0 x; + +/* Pipeline using juxtaposition */ +result : sum map double filter is_even data; +/* Reads: sum (map double (filter is_even data)) */ +/* Result: 60 */ +``` + +## Using Parentheses for Control + +Juxtaposition eliminates the need for parentheses in most cases, parentheses are available for when you need explicit control over precedence or grouping. + +```plaintext +/* Without parentheses - left-associative */ +result1 : add 5 multiply 3 4; +/* Parsed as: add 5 (multiply 3 4) */ +/* Result: 5 + (3 * 4) = 17 */ + +/* With parentheses - explicit grouping */ +result2 : add (add 1 2) (multiply 3 4); +/* Explicitly: (1 + 2) + (3 * 4) = 3 + 12 = 15 */ + +/* Complex nested operations */ +result3 : map double (filter is_even (map increment {1, 2, 3, 4, 5})); +/* Step by step: + 1. map increment {1, 2, 3, 4, 5} → {2, 3, 4, 5, 6} + 2. filter is_even {2, 3, 4, 5, 6} → {2, 4, 6} + 3. map double {2, 4, 6} → {4, 8, 12} +*/ + +/* Hard to read without parentheses */ +complex : map double filter is_even map increment {1, 2, 3, 4, 5}; + +/* Much clearer with parentheses */ +complex : map double (filter is_even (map increment {1, 2, 3, 4, 5})); + +/* Or break it into steps for maximum clarity */ +step1 : map increment {1, 2, 3, 4, 5}; +step2 : filter is_even step1; +step3 : map double step2; +``` + +Parentheses are also helpful for debugging because they let you isolate specific pieces of a program or chain. + +```plaintext +data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +/* Test each step separately */ +filtered : filter @is_even data; +doubled : map @double filtered; +final : reduce @add 0 doubled; + +/* Or use parentheses to test intermediate results */ +test1 : filter is_even data; /* {2, 4, 6, 8, 10} */ +test2 : map double (filter is_even data); /* {4, 8, 12, 16, 20} */ +``` + +## Spacing Rules + +Baba Yaga uses spacing to distinguish between unary and binary operators...mostly just minus. + +- **Unary minus**: `-5` (no leading space) → `negate(5)` +- **Binary minus**: `5 - 3` (spaces required) → `subtract(5, 3)` +- **Legacy fallback**: `5-3` → `subtract(5, 3)` (but spaces are recommended) + +The parser distinguishes between these scenarios based off of spaces, and kinda best guess heuristics. It *should* work as expected in most cases. + +- **Unary minus** (negative numbers): `-5` → `negate(5)` +- **Binary minus** (subtraction): `5 - 3` → `subtract(5, 3)` + +Spacing makes expressions less ambiguous. + +### Common Patterns + +```plaintext +/* Function calls with negative numbers */ +double : x -> x * 2; +result : double -5; /* unary minus */ +result2 : double (-5); /* explicit grouping */ + +/* Comparisons with negative numbers */ +is_negative : x -> x < 0; +test1 : is_negative -5; /* unary minus */ + +/* Complex expressions with negative numbers */ +validate_age : age -> (age >= 0) and (age <= 120); +test2 : validate_age -5; /* unary minus */ + +/* Arithmetic with proper spacing */ +result3 : -5 + 3; /* unary minus + binary plus */ +result4 : 5 - 3; /* binary minus with spaces */ +result5 : (-5) + 3; /* explicit grouping */ +``` + +#### Best Practices + +- **Use spaces around binary operators**: `5 - 3`, `5 + 3`, `5 * 3` +- **Unary minus works without parentheses**: `-5`, `f -5` +- **Legacy syntax still works**: `(-5)`, `5-3` (but spaces are recommended) +- **When in doubt, use spaces**: It makes code more readable and follows conventions + +#### When You Might Encounter This + +- **Arithmetic operations**: `-5 + 3`, `5 - 3`, `(-5) + 3` +- **Comparisons**: `-5 >= 0`, `5 - 3 >= 0` +- **Function calls**: `f -5`, `f (-5)`, `map double -3` +- **Logical expressions**: `(-5 >= 0) and (-5 <= 120)` +- **Pattern matching**: `when x is -5 then "negative five"` + +To make everyone's life easier, use spaces around binary operators. \ No newline at end of file diff --git a/js/scripting-lang/tutorials/01_Juxtaposition_Function_Application.md b/js/scripting-lang/tutorials/01_Juxtaposition_Function_Application.md deleted file mode 100644 index 7a253aa..0000000 --- a/js/scripting-lang/tutorials/01_Juxtaposition_Function_Application.md +++ /dev/null @@ -1,241 +0,0 @@ -# Juxtaposition-Based Function Application - -## What is Juxtaposition? - -Juxtaposition means "placing side by side" - in our language, this means you can call functions by simply placing the function name next to its arguments, **without parentheses**. - -```plaintext -/* Traditional syntax: f(x, y) */ -/* Our syntax: f x y */ -``` - -## Why is This Esoteric? - -Most programming languages require parentheses for function calls. Our language eliminates them entirely, making function application look like mathematical notation. - -## Basic Examples - -```plaintext -/* Simple function calls */ -add 5 3; /* Instead of add(5, 3) */ -multiply 4 7; /* Instead of multiply(4, 7) */ -subtract 10 3; /* Instead of subtract(10, 3) */ - -/* Function calls with tables */ -map double {1, 2, 3, 4, 5}; -filter is_even {1, 2, 3, 4, 5, 6}; -reduce add 0 {1, 2, 3, 4, 5}; -``` - -## How It Works - -The parser automatically translates juxtaposition into nested `apply` calls: - -```plaintext -/* f x y becomes: apply(apply(f, x), y) */ -/* map double {1, 2, 3} becomes: apply(apply(map, double), {1, 2, 3}) */ -``` - -## Precedence Rules - -Juxtaposition has **lower precedence** than operators: - -```plaintext -/* This works as expected */ -result : add 5 multiply 3 4; -/* Parsed as: add 5 (multiply 3 4) */ -/* Result: 5 + (3 * 4) = 17 */ - -/* Not as: (add 5 multiply) 3 4 */ -``` - -## Complex Examples - -```plaintext -/* Nested function calls */ -result : map double filter is_even {1, 2, 3, 4, 5, 6}; -/* Parsed as: map double (filter is_even {1, 2, 3, 4, 5, 6}) */ -/* Result: {4, 8, 12} */ - -/* Function composition with juxtaposition */ -double : x -> x * 2; -increment : x -> x + 1; -result : compose double increment 5; -/* Parsed as: (compose double increment) 5 */ -/* Result: double(increment(5)) = double(6) = 12 */ -``` - -## When to Use Juxtaposition - -**Use juxtaposition when:** -- Calling functions with arguments -- Building function composition chains -- Working with combinators like `map`, `filter`, `reduce` - -**Don't use juxtaposition when:** -- Defining functions (use `:` and `->`) -- Assigning values (use `:`) -- Using operators (use `+`, `-`, `*`, etc.) - -## Common Patterns - -```plaintext -/* Data processing pipeline */ -data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -is_even : x -> x % 2 = 0; -double : x -> x * 2; -sum : x -> reduce add 0 x; - -/* Pipeline using juxtaposition */ -result : sum map double filter is_even data; -/* Reads: sum (map double (filter is_even data)) */ -/* Result: 60 */ -``` - -## Using Parentheses for Control - -While juxtaposition eliminates the need for parentheses in most cases, parentheses are still available when you need explicit control over precedence or grouping. - -### When to Use Parentheses - -**Use parentheses when:** -- **Controlling precedence** - when the default left-associative parsing doesn't give you what you want -- **Grouping expressions** - to make complex expressions more readable -- **Breaking ambiguity** - when the parser might misinterpret your intent -- **Debugging** - to isolate and test specific parts of complex expressions - -### Precedence Control Examples - -```plaintext -/* Without parentheses - left-associative */ -result1 : add 5 multiply 3 4; -/* Parsed as: add 5 (multiply 3 4) */ -/* Result: 5 + (3 * 4) = 17 */ - -/* With parentheses - explicit grouping */ -result2 : add (add 1 2) (multiply 3 4); -/* Explicitly: (1 + 2) + (3 * 4) = 3 + 12 = 15 */ - -/* Complex nested operations */ -result3 : map double (filter is_even (map increment {1, 2, 3, 4, 5})); -/* Step by step: - 1. map increment {1, 2, 3, 4, 5} → {2, 3, 4, 5, 6} - 2. filter is_even {2, 3, 4, 5, 6} → {2, 4, 6} - 3. map double {2, 4, 6} → {4, 8, 12} -*/ -``` - -### Readability and Clarity - -```plaintext -/* Hard to read without parentheses */ -complex : map double filter is_even map increment {1, 2, 3, 4, 5}; - -/* Much clearer with parentheses */ -complex : map double (filter is_even (map increment {1, 2, 3, 4, 5})); - -/* Or break it into steps for maximum clarity */ -step1 : map increment {1, 2, 3, 4, 5}; -step2 : filter is_even step1; -step3 : map double step2; -``` - -### Debugging with Parentheses - -```plaintext -/* When debugging, use parentheses to isolate parts */ -data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - -/* Test each step separately */ -filtered : filter is_even data; -doubled : map double filtered; -final : reduce add 0 doubled; - -/* Or use parentheses to test intermediate results */ -test1 : filter is_even data; /* {2, 4, 6, 8, 10} */ -test2 : map double (filter is_even data); /* {4, 8, 12, 16, 20} */ -``` - -### Negative Numbers and Parentheses - -**Important**: Negative numbers **always require parentheses** in expressions to avoid parser precedence issues. - -```plaintext -/* ❌ WRONG - This will cause parsing errors */ -result1 : -5 + 3; -result2 : -5 >= 0; -result3 : f -5; - -/* ✅ CORRECT - Use parentheses around negative numbers */ -result1 : (-5) + 3; -result2 : (-5) >= 0; -result3 : f (-5); -``` - -#### Why This Matters - -The parser needs to distinguish between: -- **Unary minus** (negative numbers): `-5` -- **Binary minus** (subtraction): `5 - 3` - -Without parentheses, expressions like `-5 + 3` are ambiguous and cause parsing errors. - -#### Common Patterns - -```plaintext -/* Function calls with negative numbers */ -double : x -> x * 2; -result : double (-5); /* ✅ Correct */ - -/* Comparisons with negative numbers */ -is_negative : x -> x < 0; -test1 : is_negative (-5); /* ✅ Correct */ - -/* Complex expressions with negative numbers */ -validate_age : age -> (age >= 0) and (age <= 120); -test2 : validate_age (-5); /* ✅ Correct */ - -/* Logical expressions with negative numbers */ -complex_check : x -> ((-5) >= 0) and ((-5) <= 120); /* ✅ Correct */ -``` - -#### When You Might Encounter This - -- **Arithmetic operations**: `(-5) + 3`, `(-10) * 2` -- **Comparisons**: `(-5) >= 0`, `(-10) < 0` -- **Function calls**: `f (-5)`, `map double (-3)` -- **Logical expressions**: `((-5) >= 0) and ((-5) <= 120)` -- **Pattern matching**: `when x is (-5) then "negative five"` - -**Remember**: When in doubt, add parentheses around negative numbers! - -## Debugging Juxtaposition - -If you get unexpected results, check the precedence: - -```plaintext -/* Wrong: This doesn't work as expected */ -result : map double filter is_even {1, 2, 3, 4, 5}; - -/* Right: Use parentheses to control precedence */ -result : map double (filter is_even {1, 2, 3, 4, 5}); -``` - -## Key Takeaways - -1. **No parentheses needed** for function calls -2. **Left-associative** - `f x y` means `(f x) y` -3. **Lower precedence** than operators -4. **Mathematical notation** - looks like `f(x, y)` but written as `f x y` -5. **Nested automatically** - complex calls become nested `apply` calls - -## Why This Matters - -Juxtaposition makes the language feel more mathematical and less like traditional programming. It enables: - -- **Concise syntax** - less punctuation -- **Natural reading** - `map double numbers` reads like "map double over numbers" -- **Functional style** - emphasizes function application over method calls -- **Composition focus** - makes function composition the primary operation - -This is one of the most distinctive features of our language - it completely changes how you think about function calls! 🚀 \ No newline at end of file diff --git a/js/scripting-lang/tutorials/02_Function_Composition.md b/js/scripting-lang/tutorials/02_Function_Composition.md new file mode 100644 index 0000000..a6137b4 --- /dev/null +++ b/js/scripting-lang/tutorials/02_Function_Composition.md @@ -0,0 +1,138 @@ +# Function Composition + +## What is the `via` Operator? + +The `via` operator is a function composition operator that combines functions from right to left. + +```plaintext +/* f via g = compose(f, g) */ +/* f via g via h = compose(f, compose(g, h)) */ +``` + +The `via` operator is right-associative and matches mathematical notation where `(f ∘ g ∘ h)(x) = f(g(h(x)))`. + +```plaintext +/* Define simple functions */ +double : x -> x * 2; +increment : x -> x + 1; +square : x -> x * x; + +/* Using via composition */ +result1 : double via increment 5; +/* Result: 12 (5+1=6, 6*2=12) */ + +/* Chained via composition */ +result2 : double via increment via square 3; +/* Result: 20 (3^2=9, 9+1=10, 10*2=20) */ +``` + +The key insight is that `via` groups from right to left. + +```plaintext +/* This expression: */ +double via increment via square 3 + +/* Groups as: */ +double via (increment via square) 3 + +/* Which translates to: */ +compose(double, compose(increment, square))(3) + +/* With the execution order of: */ +/* 1. square(3) = 9 */ +/* 2. increment(9) = 10 */ +/* 3. double(10) = 20 */ +``` + +## Precedence rules and `via` + +The `via` operator has higher precedence than function application: + +```plaintext +/* via binds tighter than juxtaposition */ +double via increment 5 + +/* This is parsed as: */ +(double via increment) 5 +``` + +## More examples + +```plaintext +/* Data processing pipeline */ +data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +is_even : x -> x % 2 = 0; +double : x -> x * 2; +sum : x -> reduce add 0 x; + +/* Pipeline using via */ +process_pipeline : sum via map double via filter is_even; +result : process_pipeline data; +/* Reads: sum via (map double via filter is_even) */ +/* Result: 60 */ +``` + +You'll note that we don't need to use `@` here -- `via` is kinda special-cased because it is an ergonomic feature. It can work with function names directly because it's specifically for function composition. Higher-order functions like `map`, `filter`, and `reduce` require explicit function references using `@` because they need a way to distinguish between calling a function immediately vs passing it as an argument while `via` only ever takes in functions. + + +A goal with the `via` operator is to align with mathematical function composition: + +```plaintext +/* Mathematical: (f ∘ g ∘ h)(x) = f(g(h(x))) */ +/* Baba Yaga: f via g via h x = f(g(h(x))) */ +``` + +## When to Use `via` + +**Use `via` when you want:** +- Natural reading: `f via g via h` reads as "f then g then h" +- Mathematical notation: Matches `(f ∘ g ∘ h)` notation +- Concise syntax: Shorter than nested `compose` calls +- Right-to-left flow: When you think of data flowing right to left + +**Don't use `via` when:** +- You need left-to-right composition (use `pipe`) +- You want explicit mathematical style (use `compose`) +- You're working with simple function calls (use juxtaposition) +- If you don't wanna + +## Common Patterns + +```plaintext +/* Data transformation pipeline */ +data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +is_even : x -> x % 2 = 0; +double : x -> x * 2; +sum : x -> reduce add 0 x; + +/* Pipeline: filter → map → reduce */ +process_pipeline : sum via map double via filter is_even; +result : process_pipeline data; +/* Result: 60 (filter evens: {2,4,6,8,10}, double: {4,8,12,16,20}, sum: 60) */ + +/* Validation chain */ +validate_positive : x -> x > 0; +validate_even : x -> x % 2 = 0; +validate_small : x -> x < 10; + +/* Chain validations */ +all_validations : validate_small via validate_even via validate_positive; +result : all_validations 6; /* 6 > 0, 6 % 2 = 0, 6 < 10 */ +/* Result: true */ +``` + +## Debugging `via` Chains + +To understand execution order, break down the chain: + +```plaintext +/* Complex chain: */ +result : square via double via increment via square 2; + +/* Break it down: */ +/* 1. square(2) = 4 */ +/* 2. increment(4) = 5 */ +/* 3. double(5) = 10 */ +/* 4. square(10) = 100 */ +/* Result: 100 */ +``` \ No newline at end of file diff --git a/js/scripting-lang/tutorials/02_Right_Associative_Via_Operator.md b/js/scripting-lang/tutorials/02_Right_Associative_Via_Operator.md deleted file mode 100644 index 4e5be0d..0000000 --- a/js/scripting-lang/tutorials/02_Right_Associative_Via_Operator.md +++ /dev/null @@ -1,191 +0,0 @@ -# Right-Associative `via` Operator - -## What is the `via` Operator? - -The `via` operator is a **function composition operator** that combines functions from right to left, matching mathematical function composition notation. - -```plaintext -/* f via g = compose(f, g) */ -/* f via g via h = compose(f, compose(g, h)) */ -``` - -## Why is This Esoteric? - -Most composition operators in programming languages are **left-associative**. Our `via` operator is **right-associative**, which means: - -```plaintext -/* Right-associative: f via g via h = f via (g via h) */ -/* Left-associative: f via g via h = (f via g) via h */ -``` - -This matches mathematical notation where `(f ∘ g ∘ h)(x) = f(g(h(x)))`. - -## Basic Examples - -```plaintext -/* Define simple functions */ -double : x -> x * 2; -increment : x -> x + 1; -square : x -> x * x; - -/* Basic via composition */ -result1 : double via increment 5; -/* Result: 12 (5+1=6, 6*2=12) */ - -/* Chained via composition */ -result2 : double via increment via square 3; -/* Result: 20 (3^2=9, 9+1=10, 10*2=20) */ -``` - -## Right-Associative Behavior Explained - -The key insight is that `via` groups from **right to left**: - -```plaintext -/* This expression: */ -double via increment via square 3 - -/* Groups as: */ -double via (increment via square) 3 - -/* Which translates to: */ -compose(double, compose(increment, square))(3) - -/* Execution order: */ -/* 1. square(3) = 9 */ -/* 2. increment(9) = 10 */ -/* 3. double(10) = 20 */ -``` - -## Comparison with Left-Associative - -```plaintext -/* Right-associative (our via): */ -double via increment via square 3 -/* = double via (increment via square) 3 */ -/* = double(increment(square(3))) = 20 */ - -/* Left-associative (if it were): */ -double via increment via square 3 -/* = (double via increment) via square 3 */ -/* = square(double(increment(3))) = 64 */ -``` - -## Precedence Rules - -The `via` operator has **higher precedence** than function application: - -```plaintext -/* via binds tighter than juxtaposition */ -double via increment 5 - -/* This is parsed as: */ -(double via increment) 5 - -/* NOT as: */ -double via (increment 5) -``` - -## Complex Examples - -```plaintext -/* Data processing pipeline */ -data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -is_even : x -> x % 2 = 0; -double : x -> x * 2; -sum : x -> reduce add 0 x; - -/* Pipeline using via */ -process_pipeline : sum via map double via filter is_even; -result : process_pipeline data; -/* Reads: sum via (map double via filter is_even) */ -/* Result: 60 */ -``` - -## Mathematical Notation Alignment - -The right-associative behavior aligns with mathematical function composition: - -```plaintext -/* Mathematical: (f ∘ g ∘ h)(x) = f(g(h(x))) */ -/* Our language: f via g via h x = f(g(h(x))) */ - -/* Example: */ -complex_math : square via double via increment; -result : complex_math 3; -/* increment(3)=4, double(4)=8, square(8)=64 */ -/* Result: 64 */ -``` - -## When to Use `via` - -**Use `via` when you want:** -- **Natural reading**: `f via g via h` reads as "f then g then h" -- **Mathematical notation**: Matches `(f ∘ g ∘ h)` notation -- **Concise syntax**: Shorter than nested `compose` calls -- **Right-to-left flow**: When you think of data flowing right to left - -**Don't use `via` when:** -- You need left-to-right composition (use `pipe`) -- You want explicit mathematical style (use `compose`) -- You're working with simple function calls (use juxtaposition) - -## Common Patterns - -```plaintext -/* Pattern 1: Data transformation pipeline */ -data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -is_even : x -> x % 2 = 0; -double : x -> x * 2; -sum : x -> reduce add 0 x; - -/* Pipeline: filter → map → reduce */ -process_pipeline : sum via map double via filter is_even; -result : process_pipeline data; -/* Result: 60 (filter evens: {2,4,6,8,10}, double: {4,8,12,16,20}, sum: 60) */ - -/* Pattern 2: Validation chain */ -validate_positive : x -> x > 0; -validate_even : x -> x % 2 = 0; -validate_small : x -> x < 10; - -/* Chain validations */ -all_validations : validate_small via validate_even via validate_positive; -result : all_validations 6; /* 6 > 0, 6 % 2 = 0, 6 < 10 */ -/* Result: true */ -``` - -## Debugging `via` Chains - -To understand execution order, break down the chain: - -```plaintext -/* Complex chain: */ -result : square via double via increment via square 2; - -/* Break it down: */ -/* 1. square(2) = 4 */ -/* 2. increment(4) = 5 */ -/* 3. double(5) = 10 */ -/* 4. square(10) = 100 */ -/* Result: 100 */ -``` - -## Key Takeaways - -1. **Right-associative** - `f via g via h` = `f via (g via h)` -2. **Mathematical alignment** - matches `(f ∘ g ∘ h)` notation -3. **Higher precedence** - binds tighter than function application -4. **Natural reading** - reads from right to left -5. **Concise syntax** - shorter than nested `compose` calls - -## Why This Matters - -The right-associative `via` operator makes function composition feel natural and mathematical: - -- **Intuitive flow** - data flows through the pipeline naturally -- **Mathematical notation** - matches standard mathematical conventions -- **Concise composition** - complex transformations in readable chains -- **Functional thinking** - encourages building complex operations from simple functions - -This is one of the most distinctive features that makes our language feel more like mathematical notation than traditional programming! 🚀 \ No newline at end of file diff --git a/js/scripting-lang/tutorials/03_Automatic_Element_Wise_Table_Operations.md b/js/scripting-lang/tutorials/03_Automatic_Element_Wise_Table_Operations.md deleted file mode 100644 index 0531f82..0000000 --- a/js/scripting-lang/tutorials/03_Automatic_Element_Wise_Table_Operations.md +++ /dev/null @@ -1,224 +0,0 @@ -# Automatic Element-Wise Table Operations - -## What are Element-Wise Operations? - -Element-wise operations automatically apply functions to every element in a table, **without explicit loops or iteration syntax**. - -```plaintext -/* Instead of: for each element in table, apply function */ -/* You write: function table */ -numbers : {1, 2, 3, 4, 5}; -doubled : map double numbers; /* {2, 4, 6, 8, 10} */ -``` - -## Why is This Esoteric? - -Most programming languages require explicit loops or iteration constructs. Our language automatically handles element-wise operations, making it feel more like mathematical notation or APL. - -## Basic Examples - -```plaintext -/* Define a simple function */ -double : x -> x * 2; - -/* Apply to table elements automatically */ -numbers : {1, 2, 3, 4, 5}; -result : map double numbers; -/* Result: {2, 4, 6, 8, 10} */ - -/* Filter elements automatically */ -is_even : x -> x % 2 = 0; -evens : filter is_even numbers; -/* Result: {2, 4} */ - -/* Reduce all elements automatically */ -sum : reduce add 0 numbers; -/* Result: 15 (1+2+3+4+5) */ -``` - -## How It Works - -The language automatically detects when you're applying functions to tables and handles the iteration internally: - -```plaintext -/* map function table */ -/* The language automatically: */ -/* 1. Iterates through each element in the table */ -/* 2. Applies the function to each element */ -/* 3. Returns a new table with the results */ - -/* filter function table */ -/* The language automatically: */ -/* 1. Iterates through each element in the table */ -/* 2. Applies the function to each element */ -/* 3. Returns a new table with elements where function returns true */ - -/* reduce function initial_value table */ -/* The language automatically: */ -/* 1. Starts with the initial value */ -/* 2. Iterates through each element in the table */ -/* 3. Applies the function to the accumulator and current element */ -/* 4. Returns the final accumulated result */ -``` - -## Table-Specific Operations - -The `t.` namespace provides additional element-wise operations: - -```plaintext -/* Table-specific operations */ -data : {a: 1, b: 2, c: 3}; - -/* Get all keys */ -keys : t.keys data; /* {"a", "b", "c"} */ - -/* Get all values */ -values : t.values data; /* {1, 2, 3} */ - -/* Get key-value pairs */ -pairs : t.pairs data; /* {{key: "a", value: 1}, {key: "b", value: 2}, {key: "c", value: 3}} */ - -/* Check if key exists */ -has_a : t.has data "a"; /* true */ -has_d : t.has data "d"; /* false */ - -/* Get value by key */ -value_a : t.get data "a"; /* 1 */ -``` - -## Complex Examples - -```plaintext -/* Data processing pipeline */ -data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - -/* Define helper functions */ -is_even : x -> x % 2 = 0; -double : x -> x * 2; -sum : x -> reduce add 0 x; - -/* Complete pipeline: filter → map → reduce */ -result : sum map double filter is_even data; -/* Step 1: filter is_even data → {2, 4, 6, 8, 10} */ -/* Step 2: map double {2, 4, 6, 8, 10} → {4, 8, 12, 16, 20} */ -/* Step 3: sum {4, 8, 12, 16, 20} → 60 */ -/* Result: 60 */ -``` - -## Nested Tables - -Element-wise operations work with nested table structures: - -```plaintext -/* Nested table */ -people : { - alice: {name: "Alice", age: 30, scores: {85, 90, 88}}, - bob: {name: "Bob", age: 25, scores: {92, 87, 95}}, - charlie: {name: "Charlie", age: 35, scores: {78, 85, 82}} -}; - -/* Extract ages */ -ages : map (x -> x.age) people; -/* Result: {alice: 30, bob: 25, charlie: 35} */ - -/* Calculate average scores for each person */ -get_average : person -> reduce add 0 person.scores / 3; -averages : map get_average people; -/* Result: {alice: 87.67, bob: 91.33, charlie: 81.67} */ -``` - -## The `each` Combinator - -The `each` combinator provides multi-argument element-wise operations: - -```plaintext -/* each for multi-argument operations */ -numbers : {1, 2, 3, 4, 5}; -multipliers : {10, 20, 30, 40, 50}; - -/* Multiply corresponding elements */ -result : each multiply numbers multipliers; -/* Result: {10, 40, 90, 160, 250} */ - -/* Compare corresponding elements */ -is_greater : each greaterThan numbers {3, 3, 3, 3, 3}; -/* Result: {false, false, false, true, true} */ -``` - -## Immutability - -All element-wise operations return **new tables**, never modifying the original: - -```plaintext -/* Original table */ -original : {a: 1, b: 2, c: 3}; - -/* Operations return new tables */ -doubled : map double original; /* {a: 2, b: 4, c: 6} */ -filtered : filter (x -> x > 1) original; /* {b: 2, c: 3} */ - -/* Original is unchanged */ -/* original is still {a: 1, b: 2, c: 3} */ -``` - -## When to Use Element-Wise Operations - -**Use element-wise operations when:** -- Processing collections of data -- Applying the same transformation to multiple values -- Filtering data based on conditions -- Aggregating data (sum, average, etc.) -- Working with table structures - -**Don't use element-wise operations when:** -- You need side effects (they're not supported) -- You need to modify the original data (use immutable operations) -- You need complex control flow (use `when` expressions) - -## Common Patterns - -```plaintext -/* Pattern 1: Data transformation */ -data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -transform : x -> x * x + 1; /* Square and add 1 */ -result : map transform data; -/* Result: {2, 5, 10, 17, 26, 37, 50, 65, 82, 101} */ - -/* Pattern 2: Data validation */ -users : { - alice: {age: 25, email: "alice@test.com"}, - bob: {age: 17, email: "bob@test.com"}, - charlie: {age: 30, email: "invalid"} -}; - -/* Check if all users are adults */ -is_adult : user -> user.age >= 18; -all_adults : reduce logicalAnd true map is_adult users; -/* Result: false (bob is under 18) */ - -/* Pattern 3: Data aggregation */ -scores : {85, 92, 78, 96, 88, 91}; -average : reduce add 0 scores / 6; -max_score : reduce (max x y) 0 scores; -min_score : reduce (min x y) 1000 scores; -``` - -## Key Takeaways - -1. **No explicit loops** - iteration is automatic -2. **Mathematical notation** - feels like mathematical operations -3. **Immutable** - all operations return new tables -4. **Composable** - operations can be chained together -5. **APL-inspired** - similar to array programming languages - -## Why This Matters - -Automatic element-wise operations make data processing feel natural and mathematical: - -- **Concise syntax** - no boilerplate iteration code -- **Mathematical thinking** - operations on entire collections at once -- **Functional style** - emphasis on transformations over loops -- **Composability** - operations can be combined into pipelines -- **Immutability** - prevents bugs from shared mutable state - -This feature makes the language feel more like mathematical notation than traditional programming! 🚀 \ No newline at end of file diff --git a/js/scripting-lang/tutorials/03_Table_Operations.md b/js/scripting-lang/tutorials/03_Table_Operations.md new file mode 100644 index 0000000..b8d349f --- /dev/null +++ b/js/scripting-lang/tutorials/03_Table_Operations.md @@ -0,0 +1,136 @@ +# Table Operations + +## What are Element-Wise Operations? + +Element-wise operations automatically apply functions to every element in a table without explicit loops or iteration syntax like `forEach`. + +```plaintext +/* Instead of for each element in table, apply function */ +/* You write function table */ +numbers : {1, 2, 3, 4, 5}; +doubled : map @double numbers; /* {2, 4, 6, 8, 10} */ +``` + +Most main-stream programming languages require explicit loops or iteration. Baba Yaga takes a clue from array languages like APL, BQN, uiua, K, etc., and automatically handles element-wise operations. + +## Basic Examples + +```plaintext +/* Define a simple function */ +double : x -> x * 2; + +/* Apply to table elements automatically */ +numbers : {1, 2, 3, 4, 5}; +result : map @double numbers; +/* Result: {2, 4, 6, 8, 10} */ + +/* Filter elements automatically */ +is_even : x -> x % 2 = 0; +evens : filter @is_even numbers; +/* Result: {2, 4} */ + +/* Reduce all elements automatically */ +sum : reduce @add 0 numbers; +/* Result: 15 (1+2+3+4+5) */ +``` + +## Table-Specific Operations + +The `t.` namespace provides additional element-wise operations especially meant for tables. + +```plaintext +/* Table-specific operations */ +data : {a: 1, b: 2, c: 3}; + +/* Get all keys */ +keys : t.keys data; /* {"a", "b", "c"} */ + +/* Get all values */ +values : t.values data; /* {1, 2, 3} */ + +/* Get key-value pairs */ +pairs : t.pairs data; /* {{key: "a", value: 1}, {key: "b", value: 2}, {key: "c", value: 3}} */ + +/* Check if key exists */ +has_a : t.has data "a"; /* true */ +has_d : t.has data "d"; /* false */ + +/* Get value by key */ +value_a : t.get data "a"; /* 1 */ +``` + +## Complex Examples + +```plaintext +/* Data processing pipeline */ +data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +/* Define helper functions */ +is_even : x -> x % 2 = 0; +double : x -> x * 2; +sum : x -> reduce @add 0 x; + +/* Complete pipeline: filter → map → reduce */ +result : sum map double filter is_even data; +/* Step 1: filter @is_even data → {2, 4, 6, 8, 10} */ +/* Step 2: map @double {2, 4, 6, 8, 10} → {4, 8, 12, 16, 20} */ +/* Step 3: sum {4, 8, 12, 16, 20} → 60 */ +/* Result: 60 */ +``` + +## Nested Tables + +Element-wise operations work with nested table structures, too + +```plaintext +/* Nested table */ +people : { + alice: {name: "Alice", age: 30, scores: {85, 90, 88}}, + bob: {name: "Bob", age: 25, scores: {92, 87, 95}}, + charlie: {name: "Charlie", age: 35, scores: {78, 85, 82}} +}; + +/* Extract ages */ +ages : map (x -> x.age) people; +/* Result: {alice: 30, bob: 25, charlie: 35} */ + +/* Calculate average scores for each person */ +get_average : person -> reduce add 0 person.scores / 3; +averages : map get_average people; +/* Result: {alice: 87.67, bob: 91.33, charlie: 81.67} */ +``` + +## The `each` Combinator + +The `each` combinator provides multi-argument element-wise operations: + +```plaintext +/* each for multi-argument operations */ +numbers : {1, 2, 3, 4, 5}; +multipliers : {10, 20, 30, 40, 50}; + +/* Multiply corresponding elements */ +result : each @multiply numbers multipliers; +/* Result: {10, 40, 90, 160, 250} */ + +/* Compare corresponding elements */ +is_greater : each @greaterThan numbers {3, 3, 3, 3, 3}; +/* Result: {false, false, false, true, true} */ +``` + +## Immutability + +All element-wise operations return new tables. In Baba Yaga all values, including tables are immutable. + +```plaintext +/* Original table */ +original : {a: 1, b: 2, c: 3}; + +/* Operations return new tables */ +doubled : map @double original; /* {a: 2, b: 4, c: 6} */ +greater_then : x -> x > 1; +filtered : filter @greater_then original; /* {b: 2, c: 3} */ + +/* Original is unchanged */ +/* original is still {a: 1, b: 2, c: 3} */ +``` \ No newline at end of file diff --git a/js/scripting-lang/tutorials/04_Currying.md b/js/scripting-lang/tutorials/04_Currying.md new file mode 100644 index 0000000..55bd3bf --- /dev/null +++ b/js/scripting-lang/tutorials/04_Currying.md @@ -0,0 +1,167 @@ +# Currying + +## What is Partial Application? + +Partial application means that functions automatically return new functions when called with fewer arguments than they expect. This is also called currying. + +```plaintext +/* Functions automatically return new functions when partially applied */ +add : x y -> x + y; +add_five : add 5; /* Returns a function that adds 5 */ +result : add_five 3; /* 8 */ +``` + +Most programming languages require explicit syntax for partial application or currying. When using Baba Yagay, every function is automatically curried. + +## Basic Examples + +```plaintext +/* Define a two-argument function */ +add : x y -> x + y; + +/* Call with both arguments */ +result1 : add 5 3; /* 8 */ + +/* Call with one argument - returns a new function */ +add_five : add 5; /* Returns: y -> 5 + y */ + +/* Call the returned function */ +result2 : add_five 3; /* 8 */ + +/* Chain partial applications */ +add_ten : add 10; /* y -> 10 + y */ +add_ten_five : add_ten 5; /* 15 */ +``` + +## How It Works + +Partial application happens automatically with nested function returns. + +```plaintext +/* When you define: add : x y -> x + y; */ +/* Baba Yaga creates: add = x -> (y -> x + y) */ + +/* When you call: add 5 */ +/* It returns: y -> 5 + y */ + +/* When you call: add 5 3 */ +/* It calls: (y -> 5 + y)(3) = 5 + 3 = 8 */ +``` + +Partial application works with any number of arguments. + +```plaintext +/* Three-argument function */ +multiply_add : x y z -> x * y + z; + +/* Partial application examples */ +multiply_by_two : multiply_add 2; /* y z -> 2 * y + z */ +multiply_by_two_add_ten : multiply_add 2 5; /* z -> 2 * 5 + z */ + +/* Full application */ +result1 : multiply_add 2 5 3; /* 2 * 5 + 3 = 13 */ +result2 : multiply_by_two 5 3; /* 2 * 5 + 3 = 13 */ +result3 : multiply_by_two_add_ten 3; /* 2 * 5 + 3 = 13 */ +``` + +All standard library functions support partial application, too! + +```plaintext +/* Arithmetic functions */ +double : multiply 2; /* x -> 2 * x */ +increment : add 1; /* x -> x + 1 */ +decrement : subtract 1; /* x -> x - 1 */ + +/* Comparison functions */ +is_positive : greaterThan 0; /* x -> x > 0 */ +is_even : equals 0; /* This won't work as expected - see below */ + +/* Logical functions */ +always_true : logicalOr true; /* x -> true || x */ +always_false : logicalAnd false; /* x -> false && x */ +``` + +## Common Patterns + +```plaintext +/* Pattern 1: Creating specialized functions */ +numbers : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +/* Create specialized filters */ +is_even : x -> x % 2 = 0; +is_odd : x -> x % 2 = 1; +is_greater_than_five : x -> x > 5; + +/* Use with map and filter - note the @ operator for higher-order functions */ +evens : filter @is_even numbers; /* {2, 4, 6, 8, 10} */ +odds : filter @is_odd numbers; /* {1, 3, 5, 7, 9} */ +large_numbers : filter @is_greater_than_five numbers; /* {6, 7, 8, 9, 10} */ + +/* Pattern 2: Creating transformation functions */ +double : multiply 2; +triple : multiply 3; +add_ten : add 10; + +/* Apply transformations - @ operator required for map */ +doubled : map @double numbers; /* {2, 4, 6, 8, 10, 12, 14, 16, 18, 20} */ +tripled : map @triple numbers; /* {3, 6, 9, 12, 15, 18, 21, 24, 27, 30} */ +plus_ten : map @add_ten numbers; /* {11, 12, 13, 14, 15, 16, 17, 18, 19, 20} */ +``` + +You can use partial application with function composition. + +```plaintext +/* Create specialized functions */ +double : multiply 2; +increment : add 1; +square : x -> x * x; + +/* Compose partially applied functions - @ operator required for compose */ +double_then_increment : compose @increment @double; +increment_then_square : compose @square @increment; + +/* Use in pipelines */ +result1 : double_then_increment 5; /* double(5)=10, increment(10)=11 */ +result2 : increment_then_square 5; /* increment(5)=6, square(6)=36 */ +``` + +## Table Operations with Partial Application + +The `t.` namespace functions also support partial application: + +```plaintext +/* Create specialized table operations */ +get_name : t.get "name"; +get_age : t.get "age"; +has_admin : t.has "admin"; + +/* Use with map - @ operator required for higher-order functions */ +people : { + alice: {name: "Alice", age: 30, admin: true}, + bob: {name: "Bob", age: 25, admin: false}, + charlie: {name: "Charlie", age: 35, admin: true} +}; + +names : map @get_name people; /* {alice: "Alice", bob: "Bob", charlie: "Charlie"} */ +ages : map @get_age people; /* {alice: 30, bob: 25, charlie: 35} */ +admins : map @has_admin people; /* {alice: true, bob: false, charlie: true} */ +``` + +## The `each` Combinator with Partial Application + +The `each` combinator works well with partial application: + +```plaintext +/* Create specialized comparison functions */ +is_greater_than_three : x -> x > 3; +is_less_than_seven : x -> x < 7; + +/* Use with each for element-wise comparison - @ operator required for each */ +numbers : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +greater_than_three : each @is_greater_than_three numbers; +/* Result: {false, false, false, true, true, true, true, true, true, true} */ + +less_than_seven : each @is_less_than_seven numbers; +/* Result: {true, true, true, true, true, true, false, false, false, false} */ +``` \ No newline at end of file diff --git a/js/scripting-lang/tutorials/04_Partial_Application_by_Default.md b/js/scripting-lang/tutorials/04_Partial_Application_by_Default.md deleted file mode 100644 index 80ec0d0..0000000 --- a/js/scripting-lang/tutorials/04_Partial_Application_by_Default.md +++ /dev/null @@ -1,233 +0,0 @@ -# Partial Application by Default (Currying) - -## What is Partial Application? - -Partial application means that functions automatically return new functions when called with fewer arguments than they expect. This is also called **currying**. - -```plaintext -/* Functions automatically return new functions when partially applied */ -add : x y -> x + y; -add_five : add 5; /* Returns a function that adds 5 */ -result : add_five 3; /* 8 */ -``` - -## Why is This Esoteric? - -Most programming languages require explicit syntax for partial application or currying. In our language, **every function is automatically curried** - no special syntax needed. - -## Basic Examples - -```plaintext -/* Define a two-argument function */ -add : x y -> x + y; - -/* Call with both arguments */ -result1 : add 5 3; /* 8 */ - -/* Call with one argument - returns a new function */ -add_five : add 5; /* Returns: y -> 5 + y */ - -/* Call the returned function */ -result2 : add_five 3; /* 8 */ - -/* Chain partial applications */ -add_ten : add 10; /* y -> 10 + y */ -add_ten_five : add_ten 5; /* 15 */ -``` - -## How It Works - -The language automatically handles partial application through nested function returns: - -```plaintext -/* When you define: add : x y -> x + y; */ -/* The language creates: add = x -> (y -> x + y) */ - -/* When you call: add 5 */ -/* It returns: y -> 5 + y */ - -/* When you call: add 5 3 */ -/* It calls: (y -> 5 + y)(3) = 5 + 3 = 8 */ -``` - -## Multi-Argument Functions - -Partial application works with any number of arguments: - -```plaintext -/* Three-argument function */ -multiply_add : x y z -> x * y + z; - -/* Partial application examples */ -multiply_by_two : multiply_add 2; /* y z -> 2 * y + z */ -multiply_by_two_add_ten : multiply_add 2 5; /* z -> 2 * 5 + z */ - -/* Full application */ -result1 : multiply_add 2 5 3; /* 2 * 5 + 3 = 13 */ -result2 : multiply_by_two 5 3; /* 2 * 5 + 3 = 13 */ -result3 : multiply_by_two_add_ten 3; /* 2 * 5 + 3 = 13 */ -``` - -## Standard Library Functions - -All standard library functions support partial application: - -```plaintext -/* Arithmetic functions */ -double : multiply 2; /* x -> 2 * x */ -increment : add 1; /* x -> x + 1 */ -decrement : subtract 1; /* x -> x - 1 */ - -/* Comparison functions */ -is_positive : greaterThan 0; /* x -> x > 0 */ -is_even : equals 0; /* This won't work as expected - see below */ - -/* Logical functions */ -always_true : logicalOr true; /* x -> true || x */ -always_false : logicalAnd false; /* x -> false && x */ -``` - -## Common Patterns - -```plaintext -/* Pattern 1: Creating specialized functions */ -numbers : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - -/* Create specialized filters */ -is_even : x -> x % 2 = 0; -is_odd : x -> x % 2 = 1; -is_greater_than_five : greaterThan 5; - -/* Use with map and filter */ -evens : filter is_even numbers; /* {2, 4, 6, 8, 10} */ -odds : filter is_odd numbers; /* {1, 3, 5, 7, 9} */ -large_numbers : filter is_greater_than_five numbers; /* {6, 7, 8, 9, 10} */ - -/* Pattern 2: Creating transformation functions */ -double : multiply 2; -triple : multiply 3; -add_ten : add 10; - -/* Apply transformations */ -doubled : map double numbers; /* {2, 4, 6, 8, 10, 12, 14, 16, 18, 20} */ -tripled : map triple numbers; /* {3, 6, 9, 12, 15, 18, 21, 24, 27, 30} */ -plus_ten : map add_ten numbers; /* {11, 12, 13, 14, 15, 16, 17, 18, 19, 20} */ -``` - -## Function Composition with Partial Application - -Partial application works seamlessly with function composition: - -```plaintext -/* Create specialized functions */ -double : multiply 2; -increment : add 1; -square : x -> x * x; - -/* Compose partially applied functions */ -double_then_increment : compose increment double; -increment_then_square : compose square increment; - -/* Use in pipelines */ -result1 : double_then_increment 5; /* double(5)=10, increment(10)=11 */ -result2 : increment_then_square 5; /* increment(5)=6, square(6)=36 */ -``` - -## Table Operations with Partial Application - -The `t.` namespace functions also support partial application: - -```plaintext -/* Create specialized table operations */ -get_name : t.get "name"; -get_age : t.get "age"; -has_admin : t.has "admin"; - -/* Use with map */ -people : { - alice: {name: "Alice", age: 30, admin: true}, - bob: {name: "Bob", age: 25, admin: false}, - charlie: {name: "Charlie", age: 35, admin: true} -}; - -names : map get_name people; /* {alice: "Alice", bob: "Bob", charlie: "Charlie"} */ -ages : map get_age people; /* {alice: 30, bob: 25, charlie: 35} */ -admins : map has_admin people; /* {alice: true, bob: false, charlie: true} */ -``` - -## The `each` Combinator with Partial Application - -The `each` combinator works well with partial application: - -```plaintext -/* Create specialized comparison functions */ -is_greater_than_three : greaterThan 3; -is_less_than_seven : lessThan 7; - -/* Use with each for element-wise comparison */ -numbers : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - -greater_than_three : each is_greater_than_three numbers; -/* Result: {false, false, false, true, true, true, true, true, true, true} */ - -less_than_seven : each is_less_than_seven numbers; -/* Result: {true, true, true, true, true, true, false, false, false, false} */ -``` - -## When to Use Partial Application - -**Use partial application when:** -- Creating specialized functions from general ones -- Building function composition chains -- Working with combinators like `map`, `filter`, `reduce` -- Creating reusable function components -- Simplifying complex function calls - -**Don't use partial application when:** -- You need to call functions with all arguments immediately -- You're working with simple, single-purpose functions -- You need to modify the function behavior significantly - -## Common Patterns - -```plaintext -/* Pattern 1: Function factories */ -create_multiplier : factor -> multiply factor; -double : create_multiplier 2; -triple : create_multiplier 3; -quadruple : create_multiplier 4; - -/* Pattern 2: Specialized validators */ -create_range_validator : min max -> x -> x >= min && x <= max; -is_valid_age : create_range_validator 0 120; -is_valid_score : create_range_validator 0 100; - -/* Pattern 3: Configuration functions */ -create_formatter : prefix suffix -> x -> prefix + x + suffix; -format_name : create_formatter "Name: " "!"; -format_age : create_formatter "Age: " " years"; - -/* Usage */ -result1 : format_name "Alice"; /* "Name: Alice!" */ -result2 : format_age "30"; /* "Age: 30 years" */ -``` - -## Key Takeaways - -1. **Automatic currying** - every function is automatically curried -2. **No special syntax** - just call with fewer arguments -3. **Nested functions** - partial application creates nested function calls -4. **Composable** - works seamlessly with function composition -5. **Reusable** - create specialized functions from general ones - -## Why This Matters - -Partial application by default makes the language more functional and composable: - -- **Function factories** - create specialized functions easily -- **Composition focus** - encourages building complex functions from simple ones -- **Reusability** - general functions can be specialized for specific use cases -- **Mathematical thinking** - functions are treated as mathematical objects -- **Concise syntax** - no explicit currying syntax needed - -This feature makes the language feel more like mathematical function theory than traditional programming! 🚀 \ No newline at end of file diff --git a/js/scripting-lang/tutorials/05_When_Expressions_Pattern_Matching.md b/js/scripting-lang/tutorials/05_Pattern_Matching.md index 3ce0cee..c6097c3 100644 --- a/js/scripting-lang/tutorials/05_When_Expressions_Pattern_Matching.md +++ b/js/scripting-lang/tutorials/05_Pattern_Matching.md @@ -2,7 +2,7 @@ ## What are `when` Expressions? -`when` expressions provide pattern matching functionality, allowing you to match values against patterns and execute different code based on the match. +This is kinda where the whole idea for Baba Yaga started. Pattern matching is an approach to flow control. We do this in Baba Yaga using the `when` expression. It provides pattern matching functionality, allowing you to match values against patterns and execute different code based on the match. ```plaintext /* Pattern matching with when expressions */ @@ -12,9 +12,7 @@ result : when x is _ then "other"; ``` -## Why is This Esoteric? - -Pattern matching is common in functional languages but rare in imperative/OOP languages. The `when` syntax with `then` clauses and `_` wildcards is unique to our language. +Baba Yaga's pattern matching syntax has a lot of insporations, but especially `cond` patterns, Gleam's pattern matching, and Roc's, too. ## Basic Examples @@ -60,16 +58,21 @@ result : when x is ### Function Reference Patterns ```plaintext -/* Match function references */ +/* Match function references using @ operator */ double : x -> x * 2; square : x -> x * x; -result : when function is +which : x -> when x is @double then "doubling function" @square then "squaring function" _ then "other function"; + +test1 : which double; +test2 : which square; ``` +As is called out elsewhere, too, the `@` operator is required when matching function references in patterns. This distinguishes between calling a function and matching against the function itself. + ### Boolean Patterns ```plaintext /* Match boolean values */ @@ -104,8 +107,6 @@ result : when x is ## Advanced Pattern Matching -### Multi-value Patterns with Expressions - You can match multiple values with complex expressions: ```plaintext @@ -124,8 +125,6 @@ result3 : fizzbuzz 5; /* "Buzz" */ result4 : fizzbuzz 7; /* 7 */ ``` -### Table Access in Patterns - You can access table properties directly in patterns: ```plaintext @@ -139,9 +138,7 @@ access_level : when user.role is /* Result: "full access" */ ``` -### Function Calls in Patterns - -You can use function calls in patterns, but they require parentheses for clarity and consistency: +You can use function calls in patterns. Be warned, though -- they require parentheses to help disambiguate them from other references, though. ```plaintext /* Even/odd classification */ @@ -157,38 +154,19 @@ result1 : classify 4; /* "even number" */ result2 : classify 7; /* "odd number" */ ``` -**Important:** Function calls in patterns must be wrapped in parentheses: +Function calls in patterns must be wrapped in parentheses! -✅ **Correct:** +This'll work: ```plaintext when (is_even n) is true then "even" when (complex_func x y) is result then "matched" ``` -❌ **Incorrect:** +This won't work: ```plaintext when is_even n is true then "even" /* Ambiguous parsing */ ``` -This design choice ensures clarity and consistency with other disambiguation patterns in the language. - -### Complex Multi-value Validation - -```plaintext -/* Multi-field validation */ -validate_name : name -> name != ""; -validate_age : age -> age >= 0 and age <= 120; - -validate_user : name age -> - when (validate_name name) (validate_age age) is - true true then "valid user" - true false then "invalid age" - false true then "invalid name" - false false then "invalid user"; -``` - -## Nested `when` Expressions - You can nest `when` expressions for complex logic: ```plaintext @@ -214,10 +192,10 @@ result : when x is ```plaintext /* Function that uses pattern matching */ classify_number : x -> when x is - 0 then "zero" - when x % 2 = 0 then "even" - when x % 2 = 1 then "odd" - _ then "unknown"; + 0 then "zero" + (x % 2 = 0) then "even" + (x % 2 = 1) then "odd" + _ then "unknown"; /* Use the function */ result1 : classify_number 0; /* "zero" */ @@ -228,28 +206,29 @@ result3 : classify_number 7; /* "odd" */ ## Common Patterns ```plaintext -/* Pattern 1: Value classification */ +/* Value classification */ classify_age : age -> when age is - when age < 13 then "child" - when age < 20 then "teenager" - when age < 65 then "adult" + (age < 13) then "child" + (age < 20) then "teenager" + (age < 65) then "adult" _ then "senior"; -/* Pattern 2: Error handling */ +/* Error handling */ safe_divide : x y -> when y is 0 then "error: division by zero" _ then x / y; -/* Pattern 3: Status mapping */ +/* Status mapping */ status_code : 404; -status_message : when status_code is - 200 then "OK" - 404 then "Not Found" - 500 then "Internal Server Error" - _ then "Unknown Error"; +status_message : x -> + when x is + 200 then "OK" + 404 then "Not Found" + 500 then "Internal Server Error" + _ then "Unknown Error"; ``` -## When to Use `when` Expressions +## When to Use `when` pattern matching **Use `when` expressions when:** - You need to match values against multiple patterns @@ -261,46 +240,8 @@ status_message : when status_code is - **You want to access table properties in patterns** (table access) - **You need to use function results in patterns** (function calls with parentheses) - **You're implementing complex validation logic** (multi-field validation) +- **You need to match function references** (using `@` operator) **Don't use `when` expressions when:** - You only have a simple true/false condition (use logical operators) -- You need to perform side effects (use regular expressions) -- You're working with complex nested conditions (consider breaking into functions) -- **You need function calls without parentheses** (not supported for clarity) - -## Comparison with Traditional Conditionals - -```plaintext -/* Traditional if/else approach (not available in our language) */ -/* if x = 0 then "zero" else if x = 1 then "one" else "other" */ - -/* Our when expression approach */ -result : when x is - 0 then "zero" - 1 then "one" - _ then "other"; -``` - -## Key Takeaways - -1. **Pattern matching** - match values against specific patterns -2. **Wildcard support** - `_` matches anything -3. **Exhaustive matching** - all cases must be covered -4. **Nested support** - can nest `when` expressions -5. **Functional style** - expressions return values -6. **Multi-value patterns** - match multiple values simultaneously with expressions -7. **Table access** - access table properties directly in patterns -8. **Function calls** - use function results in patterns (with parentheses) -9. **Complex validation** - implement multi-field validation with tuple-like patterns - -## Why This Matters - -`when` expressions make conditional logic more functional and readable: - -- **Pattern-based thinking** - focus on what values match, not how to test them -- **Exhaustive coverage** - ensures all cases are handled -- **Functional style** - expressions return values rather than performing actions -- **Readable syntax** - clear separation between patterns and results -- **Composable** - can be used in function composition - -This feature makes the language feel more like functional programming languages like Haskell or ML! 🚀 \ No newline at end of file +- You're working with complex nested conditions (consider breaking into functions) \ No newline at end of file diff --git a/js/scripting-lang/tutorials/06_Immutable_Tables_with_Functional_Operations.md b/js/scripting-lang/tutorials/06_Immutable_Tables.md index 0439934..8502603 100644 --- a/js/scripting-lang/tutorials/06_Immutable_Tables_with_Functional_Operations.md +++ b/js/scripting-lang/tutorials/06_Immutable_Tables.md @@ -84,13 +84,15 @@ has_job : t.has data "job"; /* false */ All element-wise operations return new tables: ```plaintext -/* map returns new table */ +/* map returns new table - @ operator required for higher-order functions */ numbers : {a: 1, b: 2, c: 3}; -doubled : map double numbers; /* {a: 2, b: 4, c: 6} */ +double : x -> x * 2; +doubled : map @double numbers; /* {a: 2, b: 4, c: 6} */ /* numbers unchanged: {a: 1, b: 2, c: 3} */ -/* filter returns new table */ -filtered : filter (x -> x > 1) numbers; /* {b: 2, c: 3} */ +/* filter returns new table - @ operator required for higher-order functions */ +is_greater_than_one : x -> x > 1; +filtered : filter @is_greater_than_one numbers; /* {b: 2, c: 3} */ /* numbers unchanged: {a: 1, b: 2, c: 3} */ ``` @@ -189,7 +191,7 @@ prod_config : t.merge base_config { user_data : {name: "Alice", age: 30, scores: {85, 90, 88}}; /* Transform user data */ -with_average : t.set user_data "average_score" (reduce add 0 user_data.scores / 3); +with_average : t.set user_data "average_score" (reduce @add 0 user_data.scores / 3); with_grade : t.set with_average "grade" (when with_average.average_score is when with_average.average_score >= 90 then "A" when with_average.average_score >= 80 then "B" @@ -234,6 +236,7 @@ batch_update : table -> t.merge table { 3. **Original unchanged** - source tables are never modified 4. **Functional patterns** - enables pure functional programming 5. **Composable operations** - operations can be chained safely +6. **@ operator required** - for higher-order functions like `map`, `filter`, `reduce` ## Why This Matters diff --git a/js/scripting-lang/tutorials/07_Function_References_with_At_Symbol.md b/js/scripting-lang/tutorials/07_Function_References.md index 4ca1616..4ca1616 100644 --- a/js/scripting-lang/tutorials/07_Function_References_with_At_Symbol.md +++ b/js/scripting-lang/tutorials/07_Function_References.md diff --git a/js/scripting-lang/tutorials/08_Combinator_Based_Architecture.md b/js/scripting-lang/tutorials/08_Combinators.md index 7fe2db9..7fe2db9 100644 --- a/js/scripting-lang/tutorials/08_Combinator_Based_Architecture.md +++ b/js/scripting-lang/tutorials/08_Combinators.md diff --git a/js/scripting-lang/tutorials/09_No_Explicit_Return_Statements.md b/js/scripting-lang/tutorials/09_Expression_Based.md index f699390..f699390 100644 --- a/js/scripting-lang/tutorials/09_No_Explicit_Return_Statements.md +++ b/js/scripting-lang/tutorials/09_Expression_Based.md diff --git a/js/scripting-lang/tutorials/10_Table_Literals_as_Primary_Data_Structure.md b/js/scripting-lang/tutorials/10_Tables_Deep_Dive.md index b7abcdf..9d66d1b 100644 --- a/js/scripting-lang/tutorials/10_Table_Literals_as_Primary_Data_Structure.md +++ b/js/scripting-lang/tutorials/10_Tables_Deep_Dive.md @@ -146,15 +146,17 @@ length : t.length data; /* 3 */ Tables work seamlessly with element-wise operations: ```plaintext -/* Map over table values */ +/* Map over table values - @ operator required for higher-order functions */ numbers : {a: 1, b: 2, c: 3, d: 4, e: 5}; -doubled : map double numbers; /* {a: 2, b: 4, c: 6, d: 8, e: 10} */ +double : x -> x * 2; +doubled : map @double numbers; /* {a: 2, b: 4, c: 6, d: 8, e: 10} */ -/* Filter table values */ -evens : filter is_even numbers; /* {b: 2, d: 4} */ +/* Filter table values - @ operator required for higher-order functions */ +is_even : x -> x % 2 = 0; +evens : filter @is_even numbers; /* {b: 2, d: 4} */ -/* Reduce table values */ -sum : reduce add 0 numbers; /* 15 */ +/* Reduce table values - @ operator required for higher-order functions */ +sum : reduce @add 0 numbers; /* 15 */ ``` ## Common Patterns @@ -194,10 +196,10 @@ raw_data : { transform_user : user -> { name: user.name, age: user.age, - average_score: reduce add 0 user.scores / 3 + average_score: reduce @add 0 user.scores / 3 }; -transformed_users : map transform_user raw_data.users; +transformed_users : map @transform_user raw_data.users; /* Result: { alice: {name: "Alice", age: 30, average_score: 87.67}, bob: {name: "Bob", age: 25, average_score: 91.33} @@ -223,9 +225,9 @@ company_data : { } }; -/* Extract all employee names */ -get_names : dept -> map (emp -> emp.name) dept.employees; -all_names : map get_names company_data.departments; +/* Extract all employee names - @ operator required for higher-order functions */ +get_names : dept -> map @(emp -> emp.name) dept.employees; +all_names : map @get_names company_data.departments; /* Result: { engineering: {"Alice", "Bob"}, marketing: {"Charlie"} @@ -253,7 +255,7 @@ all_names : map get_names company_data.departments; 1. **Unified structure** - one data type for all collections 2. **Flexible syntax** - supports both key-value pairs and array elements 3. **Nested support** - can contain other tables -4. **Element-wise operations** - works with `map`, `filter`, `reduce` +4. **Element-wise operations** - works with `map`, `filter`, `reduce` (using `@` operator) 5. **Immutable operations** - all operations return new tables ## Why This Matters diff --git a/js/scripting-lang/tutorials/11_Combinators_Deep_Dive.md b/js/scripting-lang/tutorials/11_Combinators_Deep_Dive.md deleted file mode 100644 index e73382b..0000000 --- a/js/scripting-lang/tutorials/11_Combinators_Deep_Dive.md +++ /dev/null @@ -1,812 +0,0 @@ -# Problem Solving with Functional Programming - -This tutorial takes you deep into the world of combinators and functional programming patterns. We'll explore how to think about problems functionally and solve them using the language's combinator-based architecture. - -## Prerequisites - -This tutorial assumes you've completed the Introduction tutorial and are comfortable with: - -- Basic function definitions and application -- Pattern matching with `when` expressions -- Working with tables -- Function references using `@` - -## The Combinator Mindset - -### What Are Combinators? - -Combinators are functions that combine other functions to create new behaviors. In our language, **everything is a function call** - even operators like `+` and `*` are translated to combinator functions like `add` and `multiply`. - -```plaintext -/* Understanding the translation */ -x + y * z - -/* Becomes internally: */ -add(x, multiply(y, z)) -``` - -This approach gives us: -- **Zero ambiguity**: Every expression has exactly one interpretation -- **Functional foundation**: Everything is a function call -- **Composability**: Functions can be combined in powerful ways -- **Extensibility**: New operations are just new functions - -## Core Combinators: Building Blocks - -### Map: Transform Everything - -`map` applies a function to every element in a collection: - -```plaintext -/* Basic map usage */ -double : x -> x * 2; -numbers : {1, 2, 3, 4, 5}; -doubled : map @double numbers; -/* Result: {2, 4, 6, 8, 10} */ - -/* Map with tables */ -person : {name: "Alice", age: 30, city: "NYC"}; -get_age : x -> x.age; -ages : map @get_age person; -/* Result: {name: 30, age: 30, city: 30} */ - -/* Map with complex transformations */ -format_person : person -> { - name: person.name, - age: person.age + " years old", - city: "📍 " + person.city -}; -formatted : map @format_person {alice: person}; -/* Result: {alice: {name: "Alice", age: "30 years old", city: "📍 NYC"}} */ -``` - -### Filter: Select What You Want - -`filter` keeps only elements that satisfy a predicate: - -```plaintext -/* Basic filtering */ -is_even : x -> x % 2 = 0; -numbers : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -evens : filter @is_even numbers; -/* Result: {2, 4, 6, 8, 10} */ - -/* Filter with complex predicates */ -is_adult : person -> person.age >= 18; -people : { - alice: {name: "Alice", age: 25}, - bob: {name: "Bob", age: 16}, - charlie: {name: "Charlie", age: 30} -}; -adults : filter @is_adult people; -/* Result: {alice: {name: "Alice", age: 25}, charlie: {name: "Charlie", age: 30}} */ - -/* Chaining filter conditions */ -is_high_value : x -> x > 5; -is_low_value : x -> x < 15; -numbers : {1, 3, 7, 12, 18, 22}; -medium : filter @is_high_value (filter @is_low_value numbers); -/* Result: {7, 12} */ -``` - -### Reduce: Accumulate Results - -`reduce` combines all elements into a single value: - -```plaintext -/* Basic reduction */ -numbers : {1, 2, 3, 4, 5}; -sum : reduce @add 0 numbers; -/* Result: 15 */ - -/* Complex reduction */ -people : { - alice: {name: "Alice", salary: 50000}, - bob: {name: "Bob", salary: 60000}, - charlie: {name: "Charlie", salary: 45000} -}; -get_salary : person -> person.salary; -total_salary : reduce @add 0 (map @get_salary people); -/* Result: 155000 */ - -/* Building complex objects with reduce */ -entries : {name: "Alice", age: 30, city: "NYC"}; -to_list : key value -> {key: key, value: value}; -pairs : reduce @to_list {} entries; -/* This would create a list-like structure from key-value pairs */ -``` - -## Function Composition: The Power of Combination - -### Compose: Right-to-Left Composition - -`compose` combines functions so the output of one becomes the input of another: - -```plaintext -/* Basic composition */ -double : x -> x * 2; -increment : x -> x + 1; -square : x -> x * x; - -/* Mathematical style: g then f */ -double_then_increment : compose @increment @double; -result : double_then_increment 5; -/* Result: 11 (5*2=10, then 10+1=11) */ - -/* Complex composition chain */ -double_then_increment : compose @increment @double; -process_number : compose @double_then_increment @square; -result : process_number 3; -/* Result: 19 (3^2=9, 9*2=18, 18+1=19) */ - -/* Composition with different types */ -add_exclamation : x -> x + "!"; -format_number : compose @add_exclamation @double; -result : format_number 5; -/* Result: "10!" */ -``` - -### Pipe: Left-to-Right Composition - -`pipe` is the opposite of `compose` - it flows left to right: - -```plaintext -/* Pipeline style: f then g */ -increment_then_double : pipe @increment @double; -result : increment_then_double 5; -/* Result: 12 (5+1=6, then 6*2=12) */ - -/* Data processing pipeline */ -data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -is_even : x -> x % 2 = 0; -double : x -> x * 2; -sum : x -> reduce @add 0 x; - -/* Pipeline: filter -> map -> reduce */ -filter_evens : filter @is_even; -double_evens : map @double; -sum_all : reduce @add 0; -process_pipeline : pipe @sum_all @double_evens @filter_evens; -result : process_pipeline data; -/* Result: 60 (filter evens: {2,4,6,8,10}, double: {4,8,12,16,20}, sum: 60) */ -``` - -### Via: Natural Composition Syntax - -The `via` operator provides a more natural composition syntax: - -```plaintext -/* Using via for composition */ -double : x -> x * 2; -increment : x -> x + 1; -square : x -> x * x; - -/* Natural reading: double via increment via square */ -complex_transform : double via increment via square; -result : complex_transform 3; -/* Result: 19 (3^2=9, 9+1=10, 10*2=20) */ - -/* Via with multiple functions */ -format_pipeline : double via increment via square; -result : format_pipeline 2; -/* Result: 10 (2^2=4, 4+1=5, 5*2=10) */ -``` - -#### Understanding the `via` Operator - -The `via` operator is a **right-associative function composition operator** that translates to `compose` calls: - -```plaintext -/* Translation examples */ -f via g → compose(f, g) -f via g via h → compose(f, compose(g, h)) -f via g via h via i → compose(f, compose(g, compose(h, i))) -``` - -#### Right-Associative Behavior - -The `via` operator is **right-associative**, meaning it groups from right to left: - -```plaintext -/* Right-associative grouping */ -double via increment via square - -/* Groups as: */ -double via (increment via square) - -/* Which translates to: */ -compose(double, compose(increment, square)) - -/* Execution order: square → increment → double */ -``` - -This matches mathematical function composition notation where `(f ∘ g ∘ h)(x) = f(g(h(x)))`. - -#### Precedence Rules - -The `via` operator has **higher precedence** than function application: - -```plaintext -/* via binds tighter than function application */ -double via increment 5 - -/* This is parsed as: */ -(double via increment) 5 - -/* NOT as: */ -double via (increment 5) -``` - -#### Comparison with Other Composition Methods - -```plaintext -/* Three ways to compose functions */ - -/* 1. via operator (natural syntax) */ -result1 : double via increment via square 3; -/* Result: 20 (3^2=9, 9+1=10, 10*2=20) */ - -/* 2. compose function (mathematical style) */ -double_then_increment : compose @increment @double; -complex_transform : compose @double_then_increment @square; -result2 : complex_transform 3; -/* Result: 19 (3^2=9, 9*2=18, 18+1=19) */ - -/* 3. pipe function (pipeline style) */ -square_then_double : pipe @square @double; -pipeline_transform : pipe @square_then_double @increment; -result3 : pipeline_transform 3; -/* Result: 19 (3^2=9, 9*2=18, 18+1=19) */ -``` - -#### When to Use `via` - -Use the `via` operator when you want: - -1. **Natural reading**: `f via g via h` reads as "f then g then h" -2. **Mathematical notation**: Matches `(f ∘ g ∘ h)` notation -3. **Concise syntax**: Shorter than nested `compose` calls -4. **Right-to-left flow**: When you think of data flowing right to left - -```plaintext -/* Good use cases for via */ - -/* Data transformation pipeline */ -process_data : filter @is_even via map @double via reduce @add 0; -result : process_data {1, 2, 3, 4, 5, 6}; -/* Reads: filter evens, then double each, then sum all */ - -/* String processing pipeline */ -format_text : to_upper via trim via replace_spaces; -result : format_text " hello world "; -/* Reads: replace spaces, then trim, then uppercase */ - -/* Mathematical composition */ -complex_math : square via double via increment; -result : complex_math 3; -/* Reads: increment, then double, then square */ -``` - -#### Common Patterns - -```plaintext -/* Pattern 1: Data processing pipeline */ -data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; -is_even : x -> x % 2 = 0; -double : x -> x * 2; -sum : x -> reduce @add 0 x; - -/* Pipeline: filter → map → reduce */ -process_pipeline : sum via map @double via filter @is_even; -result : process_pipeline data; -/* Result: 60 (filter evens: {2,4,6,8,10}, double: {4,8,12,16,20}, sum: 60) */ - -/* Pattern 2: Validation chain */ -validate_name : user -> user.name != ""; -validate_age : user -> user.age > 0; -validate_email : user -> user.email.contains "@"; - -/* Chain validations */ -all_validations : validate_email via validate_age via validate_name; -result : all_validations {name: "Alice", age: 25, email: "alice@test.com"}; -/* Result: true (all validations pass) */ - -/* Pattern 3: Formatting pipeline */ -to_upper : x -> x.toUpperCase(); -add_prefix : prefix -> x -> prefix + ": " + x; -format_label : add_prefix "Name" via to_upper; -result : format_label "alice"; -/* Result: "Name: ALICE" */ -``` - -#### Debugging `via` Chains - -```plaintext -/* Add tracing to understand execution order */ -trace : name value -> { - ..out "[" + name + "]: " + value; - value -}; - -/* Trace each step in the pipeline */ -traced_pipeline : trace "final" via trace "double" via trace "filter" via trace "input"; -result : traced_pipeline {1, 2, 3, 4, 5}; -/* Output: [input]: {1,2,3,4,5}, [filter]: {2,4}, [double]: {4,8}, [final]: 12 */ -``` - -#### Best Practices - -1. **Use `via` for natural reading**: When you want `f via g via h` to read as "f then g then h" -2. **Use `compose` for mathematical style**: When you want explicit mathematical notation -3. **Use `pipe` for pipeline style**: When you want left-to-right data flow -4. **Keep chains readable**: Break long chains into named intermediate functions -5. **Consider precedence**: Remember that `via` binds tighter than function application - -```plaintext -/* Good: Clear and readable */ -process_data : sum via map @double via filter @is_even; - -/* Better: Named intermediate steps */ -filter_evens : filter @is_even; -double_values : map @double; -sum_all : reduce @add 0; -process_data : sum_all via double_values via filter_evens; -``` - -### Advanced Composition Patterns - -Complex composition chains can also be built using nested `compose` and `pipe`: - -```plaintext -/* Mathematical style: g then f then h */ -double_then_increment : compose @increment @double; -complex_transform : compose @double_then_increment @square; -result : complex_transform 3; -/* Result: 19 (3^2=9, 9*2=18, 18+1=19) */ - -/* Pipeline style: f then g then h */ -square_then_double : pipe @square @double; -pipeline_transform : pipe @square_then_double @increment; -result : pipeline_transform 3; -/* Result: 19 (3^2=9, 9*2=18, 18+1=19) */ -``` - -## Each: Multi-Argument Element-Wise Operations - -`each` is designed for combining multiple collections element-wise: - -```plaintext -/* Element-wise addition */ -table1 : {a: 1, b: 2, c: 3}; -table2 : {a: 10, b: 20, c: 30}; -sum : each @add table1 table2; -/* Result: {a: 11, b: 22, c: 33} */ - -/* Adding scalar to each element */ -numbers : {1, 2, 3, 4, 5}; -incremented : each @add numbers 10; -/* Result: {11, 12, 13, 14, 15} */ - -/* Complex element-wise operations */ -people : { - alice: {name: "Alice", age: 25}, - bob: {name: "Bob", age: 30} -}; -bonuses : { - alice: 1000, - bob: 1500 -}; -add_bonus : person bonus -> { - name: person.name, - age: person.age, - salary: person.age * 1000 + bonus -}; -with_bonuses : each @add_bonus people bonuses; -/* Result: {alice: {name: "Alice", age: 25, salary: 26000}, ...} */ -``` - -## Problem-Solving Patterns - -### Pattern 1: Data Transformation Pipeline - -Transform data through multiple stages: - -```plaintext -/* Problem: Process a list of numbers */ -/* 1. Filter out negative numbers */ -/* 2. Double each remaining number */ -/* 3. Sum all results */ - -data : {-3, -1, 0, 2, 4, 6, -2, 8}; - -/* Step-by-step approach */ -is_positive : x -> x > 0; -double : x -> x * 2; -sum : x -> reduce @add 0 x; - -positive : filter @is_positive data; -doubled : map @double positive; -total : sum doubled; - -/* Or as a composition */ -process_data : compose @sum @map @double @filter @is_positive; -total : process_data data; -/* Result: 40 (positive: {2,4,6,8}, doubled: {4,8,12,16}, sum: 40) */ -``` - -### Pattern 2: Table Aggregation - -Extract and aggregate data from complex structures: - -```plaintext -/* Problem: Calculate average salary by department */ -employees : { - alice: {name: "Alice", dept: "Engineering", salary: 80000}, - bob: {name: "Bob", dept: "Sales", salary: 60000}, - charlie: {name: "Charlie", dept: "Engineering", salary: 90000}, - diana: {name: "Diana", dept: "Sales", salary: 65000}, - eve: {name: "Eve", dept: "Engineering", salary: 85000} -}; - -/* Extract department and salary */ -get_dept_salary : emp -> {dept: emp.dept, salary: emp.salary}; -dept_salaries : map @get_dept_salary employees; - -/* Group by department (simplified) */ -engineering : filter @is_engineering dept_salaries; -sales : filter @is_sales dept_salaries; - -is_engineering : entry -> entry.dept = "Engineering"; -is_sales : entry -> entry.sales = "Sales"; - -/* Calculate averages */ -get_salary : entry -> entry.salary; -eng_salaries : map @get_salary engineering; -sales_salaries : map @get_salary sales; -eng_total : reduce @add 0 eng_salaries; -sales_total : reduce @add 0 sales_salaries; -/* Note: Division would require additional arithmetic functions */ -``` - -### Pattern 3: Conditional Transformation - -Apply different transformations based on conditions: - -```plaintext -/* Problem: Format different types of data */ -data : { - user1: {type: "admin", name: "Alice", level: 5}, - user2: {type: "user", name: "Bob", level: 2}, - user3: {type: "guest", name: "Charlie", level: 1} -}; - -/* Define transformation based on type */ -format_user : user -> - when user.type is - "admin" then { - display: "👑 " + user.name, - access: "full", - level: user.level * 10 - } - "user" then { - display: "👤 " + user.name, - access: "limited", - level: user.level * 5 - } - _ then { - display: "👻 " + user.name, - access: "readonly", - level: 1 - }; - -/* Apply transformation to all users */ -formatted : map @format_user data; -``` - -### Pattern 4: Recursive Combinators - -Build recursive patterns using combinators: - -```plaintext -/* Problem: Flatten nested tables */ -nested : { - level1: { - a: {value: 1}, - b: {value: 2} - }, - level2: { - c: {value: 3} - } -}; - -/* Recursive flattening function */ -flatten : table -> - when (t.has table "value") is - true then table - _ then reduce @t.merge {} (map @flatten_entry table); - -flatten_entry : entry -> - when (t.has entry "value") is - true then entry - _ then flatten entry; - -/* Apply flattening */ -flat : flatten nested; -/* Result: {a: {value: 1}, b: {value: 2}, c: {value: 3}} */ -``` - -## Advanced Combinator Patterns - -### Partial Application and Currying - -Create specialized functions from general ones: - -```plaintext -/* Basic partial application */ -add : x y -> x + y; -add_ten : add 10; -result : add_ten 5; -/* Result: 15 */ - -/* Complex partial application */ -format_with_prefix : prefix value -> prefix + ": " + value; -format_name : format_with_prefix "Name"; -format_age : format_with_prefix "Age"; - -person : {name: "Alice", age: 30}; -formatted_name : format_name person.name; -formatted_age : format_age person.age; -/* Results: "Name: Alice", "Age: 30" */ - -/* Currying with combinators */ -multiply_by : x y -> x * y; -double : multiply_by 2; -triple : multiply_by 3; - -numbers : {1, 2, 3, 4, 5}; -doubled : map @double numbers; -tripled : map @triple numbers; -``` - -### Higher-Order Combinators - -Combinators that work with other combinators: - -```plaintext -/* Apply a combinator to multiple collections */ -apply_to_all : combinator collections -> - reduce @t.merge {} (map @combinator collections); - -/* Example usage */ -add_one : x -> x + 1; -collections : {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; -all_incremented : apply_to_all @map @add_one collections; - -/* Compose multiple functions */ -compose_many : functions -> - reduce @compose @identity functions; - -/* Example usage */ -double_then_increment : compose @increment @double; -complex_transform : compose @double_then_increment @square; -result : complex_transform 3; -/* Result: 19 (3^2=9, 9*2=18, 18+1=19) */ -``` - -### Memoization Pattern - -Cache function results for performance: - -```plaintext -/* Simple memoization */ -memoize : f -> { - cache: {}, - compute: x -> - when (t.has cache x) is - true then t.get cache x "not_found" - _ then { - result: f x, - new_cache: t.set cache x (f x) - } -}; - -/* Using memoized function */ -expensive_calc : x -> { - /* Simulate expensive computation */ - x * x * x -}; - -memoized_calc : memoize @expensive_calc; -result1 : memoized_calc.compute 5; -result2 : memoized_calc.compute 5; /* Uses cached result */ -``` - -## Real-World Problem Solving - -### Example 1: E-commerce Order Processing - -```plaintext -/* Process customer orders */ -orders : { - order1: {customer: "Alice", items: {book: 2, pen: 5}, status: "pending"}, - order2: {customer: "Bob", items: {laptop: 1}, status: "shipped"}, - order3: {customer: "Charlie", items: {book: 1, pen: 3}, status: "pending"} -}; - -prices : {book: 15, pen: 2, laptop: 800}; - -/* Calculate order totals */ -calculate_total : order -> { - customer: order.customer, - total: reduce @add 0 (map @calculate_item_total order.items), - status: order.status -}; - -calculate_item_total : item quantity -> - when item is - "book" then 15 * quantity - "pen" then 2 * quantity - "laptop" then 800 * quantity - _ then 0; - -/* Process all orders */ -processed_orders : map @calculate_total orders; - -/* Filter pending orders */ -is_pending : order -> order.status = "pending"; -pending_orders : filter @is_pending processed_orders; - -/* Get total revenue from pending orders */ -get_total : order -> order.total; -total_revenue : reduce @add 0 (map @get_total pending_orders); -``` - -### Example 2: Data Validation Pipeline - -```plaintext -/* Validate user input data */ -users : { - user1: {name: "Alice", email: "alice@example.com", age: 25}, - user2: {name: "", email: "invalid-email", age: -5}, - user3: {name: "Charlie", email: "charlie@test.com", age: 30} -}; - -/* Simple validation example */ -is_valid_name : user -> - when user.name = "" is - true then false - _ then true; - -is_valid_age : user -> - when user.age > 0 is - true then true - _ then false; - -/* Apply validation to all users */ -valid_names : map @is_valid_name users; -valid_ages : map @is_valid_age users; -``` - -## Performance Considerations - -### Lazy Evaluation Patterns - -```plaintext -/* Process large datasets efficiently */ -large_dataset : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - -/* Process in chunks */ -chunk_size : 3; -process_chunk : chunk -> map @double chunk; - -/* Process data in smaller batches */ -batch1 : {1, 2, 3}; -batch2 : {4, 5, 6}; -batch3 : {7, 8, 9, 10}; - -processed_batch1 : process_chunk batch1; -processed_batch2 : process_chunk batch2; -processed_batch3 : process_chunk batch3; -/* Combine results manually since we don't have array operations */ -``` - -### Memory-Efficient Patterns - -```plaintext -/* Stream processing pattern */ -stream_process : data -> - compose @reduce @add 0 @map @double @filter @is_even data; - -/* This processes each element once through the pipeline */ -/* No intermediate results are stored */ -``` - -## Debugging Combinator Chains - -### Tracing Function Applications - -```plaintext -/* Add tracing to functions */ -trace : name value -> { - ..out "[" + name + "]: " + value; - value -}; - -/* Use in composition */ -traced_double : compose @trace "double" @double; -traced_increment : compose @trace "increment" @increment; -traced_square : compose @trace "square" @square; - -/* Trace the entire pipeline */ -traced_pipeline : compose @traced_increment @traced_double @traced_square; -result : traced_pipeline 3; -/* Output: [square]: 9, [double]: 18, [increment]: 19 */ -``` - -### Visualizing Data Flow - -```plaintext -/* Create visualization of data transformations */ -visualize_step : step_name data -> { - ..out "=== " + step_name + " ==="; - ..out "Input: " + data; - result: data -}; - -/* Use in pipeline */ -visualized_pipeline : compose - @visualize_step "Final Result" - @map @double - @visualize_step "After Filter" - @filter @is_even - @visualize_step "Initial Data"; - -result : visualized_pipeline {1, 2, 3, 4, 5, 6}; -``` - -## Best Practices Summary - -### 1. Think in Transformations -```plaintext -/* Instead of: "Loop through and modify" */ -/* Think: "Transform this into that" */ -``` - -### 2. Compose, Don't Nest -```plaintext -/* Good: Clear composition */ -pipeline : compose @step3 @step2 @step1; - -/* Avoid: Deep nesting */ -result : step3(step2(step1(data))); -``` - -### 3. Use Partial Application -```plaintext -/* Create specialized functions */ -specialized : general_function specific_value; -``` - -### 4. Leverage Immutability -```plaintext -/* Always return new data, never modify existing */ -new_data : transform_function old_data; -``` - -### 5. Build Reusable Patterns -```plaintext -/* Create patterns you can reuse */ -validation_pipeline : compose @validate3 @validate2 @validate1; -``` - -## Next Steps - -You now have a deep understanding of combinators and functional problem-solving! To continue your journey: - -1. **Practice**: Try implementing the examples above -2. **Experiment**: Create your own combinator patterns -3. **Optimize**: Look for opportunities to compose functions -4. **Extend**: Build your own specialized combinators -5. **Share**: Document patterns you discover - -The power of combinators comes from their composability. Start simple, build up complexity through composition, and always think in terms of data transformations rather than step-by-step instructions. \ No newline at end of file diff --git a/js/scripting-lang/tutorials/11_Standard_Library.md b/js/scripting-lang/tutorials/11_Standard_Library.md new file mode 100644 index 0000000..f26828d --- /dev/null +++ b/js/scripting-lang/tutorials/11_Standard_Library.md @@ -0,0 +1,129 @@ +# Standard Library Overview + +## What is the Standard Library? + +The Baba Yaga standard library provides a comprehensive set of functions for common operations. Everything is a function - even operators like `+` and `*` are just functions under the hood. + +## Core Categories + +### Arithmetic Functions +```plaintext +/* Basic arithmetic */ +add 5 3; /* 8 */ +subtract 10 4; /* 6 */ +multiply 6 7; /* 42 */ +divide 20 5; /* 4 */ +modulo 17 5; /* 2 */ +power 2 8; /* 256 */ +negate 42; /* -42 */ +``` + +### Comparison Functions +```plaintext +/* Comparisons return booleans */ +equals 5 5; /* true */ +notEquals 3 7; /* true */ +lessThan 3 7; /* true */ +greaterThan 10 5; /* true */ +lessEqual 5 5; /* true */ +greaterEqual 8 3; /* true */ +``` + +### Logical Functions +```plaintext +/* Logical operations */ +logicalAnd true false; /* false */ +logicalOr true false; /* true */ +logicalXor true true; /* false */ +logicalNot true; /* false */ +``` + +### Higher-Order Functions +```plaintext +/* Function manipulation */ +compose @double @increment 5; /* 12 */ +pipe @increment @double 5; /* 12 */ +apply @add 3 4; /* 7 */ +curry @add 3; /* function that adds 3 */ +``` + +### Collection Functions +```plaintext +/* Working with collections */ +map @double {1, 2, 3}; /* {2, 4, 6} */ +filter @is_even {1, 2, 3, 4}; /* {2, 4} */ +reduce @add 0 {1, 2, 3}; /* 6 */ +each @add {1, 2} {10, 20}; /* {11, 22} */ +``` + +### Enhanced Combinators +```plaintext +/* Utility functions */ +identity 42; /* 42 */ +constant 5 10; /* 5 */ +flip @subtract 5 10; /* 5 (10 - 5) */ +on @length @add "hello" "world"; /* 10 */ +both @is_even @is_positive 6; /* true */ +either @is_even @is_negative 6; /* true */ +``` + +## Table Operations (`t.` namespace) + +All table operations are immutable and return new tables: + +```plaintext +/* Table-specific operations */ +data : {a: 1, b: 2, c: 3}; +doubled : t.map @double data; /* {a: 2, b: 4, c: 6} */ +filtered : t.filter @is_even data; /* {b: 2} */ +updated : t.set data "d" 4; /* {a: 1, b: 2, c: 3, d: 4} */ +removed : t.delete data "b"; /* {a: 1, c: 3} */ +merged : t.merge data {d: 4, e: 5}; /* {a: 1, b: 2, c: 3, d: 4, e: 5} */ +value : t.get data "a"; /* 1 */ +has_key : t.has data "b"; /* true */ +count : t.length data; /* 3 */ +``` + +## When to Use Which Function + +- **Use `map`** for transforming every element in a collection +- **Use `filter`** for selecting elements that match a condition +- **Use `reduce`** for combining all elements into a single value +- **Use `each`** for element-wise operations across multiple collections +- **Use `t.map`/`t.filter`** when you want to emphasize table operations +- **Use `compose`** for mathematical-style function composition (right-to-left) +- **Use `pipe`** for pipeline-style composition (left-to-right) + +## Common Patterns + +```plaintext +/* Data processing pipeline */ +data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +is_even : x -> x % 2 = 0; +double : x -> x * 2; +sum : x -> reduce @add 0 x; + +/* Process: filter evens, double them, sum the result */ +result : sum map @double filter @is_even data; +/* Result: 60 */ + +/* Table transformation */ +users : { + alice: {name: "Alice", age: 25}, + bob: {name: "Bob", age: 30} +}; +get_age : x -> x.age; +is_adult : x -> x >= 18; +format_age : x -> x + " years old"; + +/* Get formatted ages of adult users */ +adult_ages : map @format_age filter @is_adult map @get_age users; +/* Result: {alice: "25 years old", bob: "30 years old"} */ +``` + +## Next Steps + +Now that you understand the standard library, explore: +- [Advanced Combinators](14_Advanced_Combinators.md) for complex patterns +- [IO Operations](12_IO_Operations.md) for input/output +- [Error Handling](13_Error_Handling.md) for robust programs \ No newline at end of file diff --git a/js/scripting-lang/tutorials/12_Functional_Harness_Integration.md b/js/scripting-lang/tutorials/12_Functional_Harness_Integration.md deleted file mode 100644 index 7bc487f..0000000 --- a/js/scripting-lang/tutorials/12_Functional_Harness_Integration.md +++ /dev/null @@ -1,569 +0,0 @@ -# Functional Harness Integration - -The Functional Harness provides a TEA-inspired state management system for integrating scripts with external systems. This tutorial covers the core concepts and practical usage. - -## Overview - -The harness loosely implements The Elm Architecture (TEA): - -- **Model**: Current state (pure table data) -- **Update**: Pure function (State → { model, commands, version }) -- **Commands**: Side effects processed by adapters -- **View**: External system integration - -## Core Concepts - -### State Flow - -``` -External System → Adapter → Harness → Script (..listen/..emit) → Commands → Adapter → External System -``` - -### Pure Functions - -Scripts remain pure functions that: -1. Read state via `..listen` -2. Process state using pattern matching -3. Emit commands via `..emit` -4. Return new state (optional) - -## Basic Usage - -### Simple State Processing - -```javascript -import { FunctionalHarness } from '../scripting-harness/core/harness.js'; - -const script = ` -/* Get current state */ -state : ..listen; - -/* Process based on state */ -result : when state is - { status: "active" } then { action: "continue" } - { status: "paused" } then { action: "resume" } - _ then { action: "stop" }; - -/* Emit result */ -..emit result; -`; - -const harness = new FunctionalHarness(script); -await harness.initialize(); // Important: Initialize the harness first -const result = await harness.update({ status: "active" }); - -console.log(result.commands); -// [{ type: 'emit', value: { action: 'continue' } }] -``` - -### Multiple Commands - -Scripts can emit multiple commands: - -```javascript -const script = ` -state : ..listen; - -/* Emit multiple commands */ -..emit { type: "log", message: "Processing state" }; -..emit { type: "update", data: state }; -..emit { type: "notify", user: state.user }; -`; - -const harness = new FunctionalHarness(script); -await harness.initialize(); -const result = await harness.update({ user: "Alice", data: "test" }); - -console.log(result.commands.length); // 3 commands emitted -``` - -### REPL Integration Example - -The REPL demonstrates practical harness usage: - -```javascript -// From the REPL examples -const counterScript = ` -/* Counter with state management */ -state : ..listen; -count : when state is { count: count } then count _ then 0; -new_count : count + 1; -updated_state : { count: new_count }; -..emit updated_state; -updated_state -`; - -const harness = new FunctionalHarness(counterScript); -await harness.initialize(); - -// Process multiple state updates -await harness.update({ count: 0 }); // Version 1: count = 1 -await harness.update({ count: 1 }); // Version 2: count = 2 -await harness.update({ count: 2 }); // Version 3: count = 3 -``` - -## State Versioning - -### Automatic Versioning - -Each state change creates a new version: - -```javascript -// Process multiple states -await harness.update({ status: "active" }); // Version 1 -await harness.update({ status: "paused" }); // Version 2 -await harness.update({ status: "stopped" }); // Version 3 - -// Get version history -const history = harness.getVersionHistory(); -console.log(history); -// [ -// { version: 1, timestamp: 1234567890, hash: 12345 }, -// { version: 2, timestamp: 1234567891, hash: 67890 }, -// { version: 3, timestamp: 1234567892, hash: 11111 } -// ] -``` - -### Rollback and Replay - -```javascript -// Rollback to specific version -const historicalState = await harness.rollbackToVersion(2); - -// Replay from version with new state -const replayResult = await harness.replayFromVersion(1, { - status: "active", - user: "Bob" -}); -``` - -### Branching (REPL Feature) - -The REPL demonstrates branching capabilities: - -```javascript -// Create a branch from version 2 -const branchHarness = await harness.createBranch(2, "experimental"); - -// Work on the branch -await branchHarness.update({ status: "experimental", feature: "new" }); - -// Original harness continues independently -await harness.update({ status: "stable", feature: "existing" }); -``` - -## Error Handling - -### Graceful Error Recovery - -The harness handles script errors gracefully: - -```javascript -const script = ` -state : ..listen; -/* This will cause an error */ -invalid : apply "not a function" to [1, 2, 3]; -..emit invalid; -`; - -const result = await harness.update({ status: "active" }); - -if (result.commands.some(cmd => cmd.type === 'error')) { - console.log('Script error handled:', result.commands[0].error); - // Script error handled: apply: first argument must be a function -} -``` - -### Error Classification - -Errors are classified for better handling: - -```javascript -const errorCommand = result.commands.find(cmd => cmd.type === 'error'); -console.log(errorCommand.errorType); // 'unknown_error', 'timeout', etc. -``` - -## Configuration - -### Harness Configuration - -```javascript -const config = { - maxVersions: 100, // Maximum versions to keep - timeout: 5000, // Script execution timeout (ms) - logStateChanges: true, // Log state changes - logCommands: true // Log commands -}; - -const harness = new FunctionalHarness(script, config); -``` - -### State History Configuration - -```javascript -// Clean up old versions -harness.stateHistory.cleanupOldVersions(50); // Keep only 50 versions - -// Get diff between versions -const diff = harness.stateHistory.getDiff(1, 5); -console.log(diff); -// { -// added: { newProperty: "value" }, -// removed: { oldProperty: "value" }, -// changed: { modifiedProperty: { from: "old", to: "new" } } -// } -``` - -## Integration Patterns - -### Adapter Pattern (REPL Implementation) - -The REPL demonstrates the adapter pattern with Console, File, and Network adapters: - -```javascript -// Console Adapter - handles general output -const consoleAdapter = { - process: async (command) => { - if (command.type === 'log') { - console.log('[Console]', command.message); - } - } -}; - -// File Adapter - handles file operations -const fileAdapter = { - process: async (command) => { - if (command.action === 'save_file') { - console.log('[File] Would save:', command.filename, command.content); - } - } -}; - -// Network Adapter - handles HTTP requests -const networkAdapter = { - process: async (command) => { - if (command.action === 'http_request') { - console.log('[Network] Would make', command.method, 'request to:', command.url); - } - } -}; - -// Register adapters with harness -const adapters = [consoleAdapter, fileAdapter, networkAdapter]; -``` - -### Network Integration Example - -The REPL includes a PokéAPI integration example: - -```javascript -const networkScript = ` -/* Network API integration example */ -/* Using PokéAPI to fetch Pokémon data */ - -/* Get current state to see if we have a Pokémon name */ -state : ..listen; - -/* Determine which Pokémon to fetch */ -pokemon_name : when state is - { pokemon: name } then name - _ then "ditto"; /* Default to ditto */ - -/* Emit network request to PokéAPI */ -..emit { - action: "http_request", - method: "GET", - url: "https://pokeapi.co/api/v2/pokemon/" + pokemon_name -}; - -/* Also fetch a list of Pokémon */ -..emit { - action: "http_request", - method: "GET", - url: "https://pokeapi.co/api/v2/pokemon?limit=5" -}; - -/* Return the request configuration */ -{ - pokemon_name, - requests: [ - { method: "GET", url: "https://pokeapi.co/api/v2/pokemon/" + pokemon_name }, - { method: "GET", url: "https://pokeapi.co/api/v2/pokemon?limit=5" } - ] -} -`; - -const harness = new FunctionalHarness(networkScript); -await harness.initialize(); - -// Process with network adapter -const result = await harness.update({ pokemon: "pikachu" }); -// Output: [Network] Would make GET request to: https://pokeapi.co/api/v2/pokemon/pikachu -// Output: [Network] Would make GET request to: https://pokeapi.co/api/v2/pokemon?limit=5 -``` - -## Advanced Patterns - -### State Composition (REPL Examples) - -The REPL demonstrates complex state processing: - -```javascript -// Pattern matching example from REPL -const patternScript = ` -/* Pattern matching with complex state */ -state : ..listen; - -/* Extract user data */ -user : state.user; -age : user.age; - -/* Process based on age */ -status : when age is - age < 18 then "minor" - age < 65 then "adult" - _ then "senior"; - -/* Process based on user type */ -access : when user is - { role: "admin" } then "full" - { role: "user" } then "limited" - _ then "guest"; - -/* Compose final result */ -result : { - user: user.name, - age: age, - status: status, - access: access, - timestamp: state.timestamp -}; - -..emit result; -result -`; - -const harness = new FunctionalHarness(patternScript); -await harness.initialize(); - -const result = await harness.update({ - user: { name: "Alice", age: 25, role: "admin" }, - timestamp: Date.now() -}); -``` - -### Conditional Command Emission - -```javascript -const conditionalScript = ` -state : ..listen; - -/* Only emit commands under certain conditions */ -when state.status is "active" then - ..emit { type: "process", data: state.data }; - -when state.user.score > 100 then - ..emit { type: "achievement", score: state.user.score }; - -when state.game.level > 10 then - ..emit { type: "levelup", level: state.game.level }; -`; -``` - -## Best Practices - -### 1. Keep Scripts Pure - -```javascript -// ✅ Good: Pure function -const script = ` -state : ..listen; -result : process state; -..emit result; -`; - -// ❌ Avoid: Side effects in scripts -const script = ` -state : ..listen; -/* Don't do this - keep side effects in adapters */ -..out "banana"; -..emit state; -`; -``` - -### 2. Use Pattern Matching - -```javascript -// ✅ Good: Clear pattern matching -const script = ` -state : ..listen; -action : when state is - { type: "user_input" } then processUserInput state - { type: "system_event" } then processSystemEvent state - _ then { error: "unknown state type" }; -..emit action; -`; -``` - -### 3. Emit Structured Commands - -```javascript -// ✅ Good: Structured commands -..emit { - type: "game_action", - action: "move", - direction: "north", - player: state.player.id -}; - -// ❌ Avoid: Unstructured data -..emit "move north"; -``` - -### 4. Handle Errors Gracefully - -```javascript -const script = ` -state : ..listen; - -/* Always provide fallback patterns */ -result : when state is - { data: validData } then process validData - _ then { error: "invalid state", received: state }; - -..emit result; -`; -``` - -## Testing - -### Unit Testing Scripts - -```javascript -// Test script in isolation -const testScript = ` -state : ..listen; -result : when state is - { input: "test" } then { output: "processed" } - _ then { output: "default" }; -..emit result; -`; - -const harness = new FunctionalHarness(testScript); -await harness.initialize(); - -// Test cases -const testCases = [ - { input: { input: "test" }, expected: { output: "processed" } }, - { input: { input: "other" }, expected: { output: "default" } }, - { input: {}, expected: { output: "default" } } -]; - -for (const testCase of testCases) { - const result = await harness.update(testCase.input); - const command = result.commands[0]; - assert.deepEqual(command.value, testCase.expected); -} -``` - -### Integration Testing (REPL Style) - -```javascript -// Test full integration with adapters -class TestAdapter { - constructor(harness) { - this.harness = harness; - this.commands = []; - } - - async handleEvent(event) { - const result = await this.harness.update(event); - this.commands.push(...result.commands); - return result.model; - } - - getCommands() { - return this.commands; - } - - clearCommands() { - this.commands = []; - } -} - -// Test with network adapter -const networkTestScript = ` -state : ..listen; -..emit { - action: "http_request", - method: "GET", - url: "https://api.example.com/test" -}; -`; - -const testHarness = new FunctionalHarness(networkTestScript); -await testHarness.initialize(); - -const testAdapter = new TestAdapter(testHarness); -const result = await testAdapter.handleEvent({ test: "data" }); - -console.log(testAdapter.getCommands()); -// [{ type: 'emit', value: { action: 'http_request', method: 'GET', url: '...' } }] -``` - -## Summary - -The Functional Harness provides: - -- **Pure Functions**: Scripts remain functional and side-effect free -- **Automatic Versioning**: State history with rollback capabilities -- **Error Handling**: Graceful error recovery and classification -- **Command Processing**: Atomic command collection and processing -- **Integration Ready**: Foundation for building adapters -- **REPL Integration**: Interactive development environment with examples - -## Key Learnings from REPL Implementation - -### 1. Initialization is Critical -Always call `await harness.initialize()` before using the harness. This loads the language interpreter and sets up the environment. - -### 2. Use `update()` Instead of `processState()` -The correct method is `harness.update(state)` for processing state changes. The `processState()` method is for internal use. - -### 3. Adapter Pattern Works -The REPL demonstrates that the adapter pattern is effective for handling different types of commands (Console, File, Network). - -### 4. State Persistence -The harness maintains state across multiple calls, making it suitable for interactive applications like the REPL. - -### 5. Versioning is Powerful -Branching and rollback capabilities provide powerful debugging and experimentation tools. - -## Known Limitations and Considerations - -### 1. Harness Initialization Issue -Currently, there's a known issue where `harness.initialize()` may hang during the `lang.js` import process. This affects script execution in some environments and prevents proper command processing. - -### 2. Language Syntax Requirements -- Scripts must be assigned to variables: `result : myFunction arg1 arg2;` -- Function calls require assignment: `output : add 3 4;` (not just `add 3 4;`) -- Multi-line scripts must end with semicolon - -### 3. Adapter System Limitations -- Only basic adapters (Console, File, Network) are currently implemented -- Advanced adapters (WebSocket, HTTP, Game) are planned for future development -- Adapter command processing requires proper harness initialization - -### 4. State Management Constraints -- State is maintained in memory (not persisted to disk by default) -- Version history is limited by `maxVersions` configuration -- Large state objects may impact performance -- State translation between JS and script formats may lose some metadata - -### 5. Error Handling -- Script errors are captured and returned as error commands -- Timeout protection prevents infinite loops -- Error classification helps with debugging -- Error states don't crash the harness but may affect subsequent operations - -This architecture enables building complex applications while maintaining functional purity and providing robust state management capabilities. The REPL serves as a practical demonstration of these concepts in action. \ No newline at end of file diff --git a/js/scripting-lang/tutorials/12_IO_Operations.md b/js/scripting-lang/tutorials/12_IO_Operations.md new file mode 100644 index 0000000..de22f0a --- /dev/null +++ b/js/scripting-lang/tutorials/12_IO_Operations.md @@ -0,0 +1,208 @@ +# IO Operations + +## What are IO Operations? + +IO (Input/Output) operations allow your functional programs to interact with the outside world. Baba Yaga provides a minimal set of IO operations that keep side effects contained and explicit. + +## Basic Output + +### Simple Output +```plaintext +/* Output values to console */ +..out "Hello, World!"; +..out 42; +..out true; +..out {name: "Alice", age: 30}; +``` + +### Output with Expressions +```plaintext +/* Output computed values */ +result : 5 + 3 * 2; +..out result; /* Output: 11 */ + +/* Output function results */ +double : x -> x * 2; +..out double 7; /* Output: 14 */ + +/* Output table operations */ +numbers : {1, 2, 3, 4, 5}; +doubled : map @double numbers; +..out doubled; /* Output: {2, 4, 6, 8, 10} */ +``` + +## Assertions + +Assertions help you verify your program's behavior: + +```plaintext +/* Basic assertions */ +..assert 5 = 5; /* Passes */ +..assert 3 + 2 = 5; /* Passes */ +..assert true; /* Passes */ +..assert false; /* Fails with error */ + +/* Assertions with messages */ +..assert "5 equals 5" 5 = 5; /* Passes */ +..assert "3 + 2 equals 5" 3 + 2 = 5; /* Passes */ +..assert "This will fail" 1 = 2; /* Fails with message */ +``` + +### Testing Functions +```plaintext +/* Test function behavior */ +factorial : n -> + when n is + 0 then 1 + _ then n * (factorial (n - 1)); + +/* Test cases */ +..assert "factorial 0 = 1" factorial 0 = 1; +..assert "factorial 1 = 1" factorial 1 = 1; +..assert "factorial 5 = 120" factorial 5 = 120; +``` + +## Emit and Listen Pattern + +The `..emit` and `..listen` pattern provides a way to interface functional code with external systems: + +### Emitting Events +```plaintext +/* Emit events with data */ +..emit "user_created" {id: 123, name: "Alice"}; +..emit "data_processed" {count: 42, success: true}; +..emit "error_occurred" {message: "Invalid input", code: 400}; +``` + +### Listening for Events +```plaintext +/* Listen for specific events */ +..listen "user_created" handle_user_created; +..listen "data_processed" handle_data_processed; +..listen "error_occurred" handle_error; +``` + +### Event Handlers +```plaintext +/* Define event handlers */ +handle_user_created : user_data -> + ..out "New user created:"; + ..out user_data.name; + +handle_data_processed : result -> + when result.success is + true then ..out "Processing successful: " + result.count + " items" + false then ..out "Processing failed"; + +handle_error : error -> + ..out "Error: " + error.message; + ..out "Code: " + error.code; +``` + +## Input Operations + +### Reading Input +```plaintext +/* Read input from user */ +name : ..in "Enter your name: "; +..out "Hello, " + name + "!"; + +/* Read and process input */ +age_input : ..in "Enter your age: "; +age : parseInt age_input; +..out "You are " + age + " years old"; +``` + +## IO Best Practices + +### Keep Side Effects Explicit +```plaintext +/* Good: Clear IO operations */ +process_data : data -> + result : transform data; + ..out "Processing complete"; + ..emit "data_processed" result; + result; + +/* Avoid: Hidden side effects in pure functions */ +bad_transform : data -> + ..out "Processing..."; /* Side effect in "pure" function */ + data * 2; +``` + +### Use Assertions for Testing +```plaintext +/* Test your functions thoroughly */ +is_even : x -> x % 2 = 0; +double : x -> x * 2; + +/* Test individual functions */ +..assert "0 is even" is_even 0 = true; +..assert "1 is not even" is_even 1 = false; +..assert "double 5 = 10" double 5 = 10; + +/* Test composed functions */ +doubled_evens : compose @double @is_even; +..assert "doubled_evens 6 = true" doubled_evens 6 = true; +``` + +### Structured Output +```plaintext +/* Use tables for structured output */ +user : {name: "Alice", age: 30, city: "NYC"}; +..out "User Profile:"; +..out " Name: " + user.name; +..out " Age: " + user.age; +..out " City: " + user.city; + +/* Or output the entire structure */ +..out user; +``` + +## Common Patterns + +### Data Processing Pipeline +```plaintext +/* Process data with IO feedback */ +data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +..out "Processing " + t.length data + " items"; + +is_even : x -> x % 2 = 0; +double : x -> x * 2; +sum : x -> reduce @add 0 x; + +/* Process with progress updates */ +evens : filter @is_even data; +..out "Found " + t.length evens + " even numbers"; + +doubled : map @double evens; +..out "Doubled values:"; +..out doubled; + +total : sum doubled; +..out "Sum of doubled evens: " + total; + +/* Emit final result */ +..emit "processing_complete" {input_count: t.length data, result: total}; +``` + +### Error Handling +```plaintext +/* Handle potential errors gracefully */ +safe_divide : x y -> + when y = 0 then + ..emit "division_error" {dividend: x, divisor: y}; + "Error: Division by zero" + _ then x / y; + +/* Test error handling */ +..out safe_divide 10 2; /* 5 */ +..out safe_divide 10 0; /* Error: Division by zero */ +``` + +## Next Steps + +Now that you understand IO operations, explore: +- [Error Handling](13_Error_Handling.md) for robust error management +- [Integration Patterns](15_Integration_Patterns.md) for external system integration +- [Best Practices](16_Best_Practices.md) for writing clean code \ No newline at end of file diff --git a/js/scripting-lang/tutorials/13_Error_Handling.md b/js/scripting-lang/tutorials/13_Error_Handling.md new file mode 100644 index 0000000..07aff5a --- /dev/null +++ b/js/scripting-lang/tutorials/13_Error_Handling.md @@ -0,0 +1,256 @@ +# 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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 +```plaintext +/* 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](15_Integration_Patterns.md) for external system error handling +- [Advanced Combinators](14_Advanced_Combinators.md) for error handling patterns +- [Best Practices](16_Best_Practices.md) for writing robust code \ No newline at end of file diff --git a/js/scripting-lang/tutorials/14_Advanced_Combinators.md b/js/scripting-lang/tutorials/14_Advanced_Combinators.md new file mode 100644 index 0000000..28937d1 --- /dev/null +++ b/js/scripting-lang/tutorials/14_Advanced_Combinators.md @@ -0,0 +1,295 @@ +# Advanced Combinators + +## What are Advanced Combinators? + +Advanced combinators are powerful patterns that combine multiple functions and operations to solve complex problems. They build on the basic combinators you've already learned. + +## Partial Application and Currying + +### Creating Specialized Functions +```plaintext +/* Basic partial application */ +add : x y -> x + y; +add_ten : add 10; +result : add_ten 5; /* 15 */ + +/* Complex partial application */ +format_with_prefix : prefix value -> prefix + ": " + value; +format_name : format_with_prefix "Name"; +format_age : format_with_prefix "Age"; + +person : {name: "Alice", age: 30}; +formatted_name : format_name person.name; /* "Name: Alice" */ +formatted_age : format_age person.age; /* "Age: 30" */ +``` + +### Currying with Combinators +```plaintext +/* Create specialized functions */ +multiply_by : x y -> x * y; +double : multiply_by 2; +triple : multiply_by 3; + +numbers : {1, 2, 3, 4, 5}; +doubled : map @double numbers; /* {2, 4, 6, 8, 10} */ +tripled : map @triple numbers; /* {3, 6, 9, 12, 15} */ +``` + +## Higher-Order Combinators + +### Combinators that Work with Other Combinators +```plaintext +/* Apply a combinator to multiple collections */ +apply_to_all : combinator collections -> + reduce @t.merge {} (map @combinator collections); + +/* Example usage */ +add_one : x -> x + 1; +collections : {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; +all_incremented : apply_to_all @map @add_one collections; +/* Result: {1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10} */ +``` + +### Composing Multiple Functions +```plaintext +/* Compose many functions together */ +compose_many : functions -> + reduce @compose @identity functions; + +/* Example usage */ +double_then_increment : compose @increment @double; +complex_transform : compose @double_then_increment @square; +result : complex_transform 3; +/* Result: 19 (3^2=9, 9*2=18, 18+1=19) */ +``` + +## Memoization Pattern + +### Caching Function Results +```plaintext +/* Simple memoization */ +memoize : f -> { + cache: {}, + compute: x -> + when t.has cache x then t.get cache x + _ then { + result: f x, + new_cache: t.set cache x (f x) + } +}; + +/* Using memoized function */ +expensive_calc : x -> x * x * x; /* Simulate expensive computation */ +memoized_calc : memoize @expensive_calc; +result1 : memoized_calc.compute 5; /* Computes 125 */ +result2 : memoized_calc.compute 5; /* Uses cached result */ +``` + +## Real-World Problem Solving + +### E-commerce Order Processing +```plaintext +/* Process customer orders */ +orders : { + order1: {customer: "Alice", items: {book: 2, pen: 5}, status: "pending"}, + order2: {customer: "Bob", items: {laptop: 1}, status: "shipped"}, + order3: {customer: "Charlie", items: {book: 1, pen: 3}, status: "pending"} +}; + +prices : {book: 15, pen: 2, laptop: 800}; + +/* Calculate order totals */ +calculate_total : order -> { + customer: order.customer, + total: reduce @add 0 (map @calculate_item_total order.items), + status: order.status +}; + +calculate_item_total : item quantity -> + when item is + "book" then 15 * quantity + "pen" then 2 * quantity + "laptop" then 800 * quantity + _ then 0; + +/* Process all orders */ +processed_orders : map @calculate_total orders; +..out processed_orders; +``` + +### Data Transformation Pipeline +```plaintext +/* Transform user data through multiple stages */ +users : { + alice: {name: "Alice", age: 25, city: "NYC", active: true}, + bob: {name: "Bob", age: 30, city: "LA", active: false}, + charlie: {name: "Charlie", age: 35, city: "NYC", active: true} +}; + +/* Pipeline stages */ +filter_active : users -> filter @is_active users; +add_greeting : users -> map @add_greeting_to_user users; +format_output : users -> map @format_user_output users; + +is_active : user -> user.active; +add_greeting_to_user : user -> t.merge user {greeting: "Hello, " + user.name}; +format_user_output : user -> { + name: user.name, + greeting: user.greeting, + location: user.city +}; + +/* Execute pipeline */ +active_users : filter_active users; +greeted_users : add_greeting active_users; +formatted_users : format_output greeted_users; + +..out formatted_users; +``` + +## Advanced Patterns + +### Lazy Evaluation +```plaintext +/* Lazy evaluation with thunks */ +lazy : computation -> { + compute: computation, + evaluated: false, + result: null, + get: -> + when evaluated then result + _ then { + computed_result: compute, + new_lazy: { + compute: computation, + evaluated: true, + result: computed_result, + get: -> computed_result + } + } +}; + +/* Use lazy evaluation */ +expensive_operation : -> { + /* Simulate expensive computation */ + ..out "Computing..."; + 42 +}; + +lazy_result : lazy expensive_operation; +/* Computation hasn't happened yet */ + +actual_result : lazy_result.get; +/* Now computation happens */ +``` + +### Continuation-Passing Style +```plaintext +/* Continuation-passing style for complex control flow */ +process_with_continuation : data success_cont error_cont -> + when data = null then error_cont "No data provided" + _ then + processed : transform data; + when processed.error is + true then error_cont processed.message + false then success_cont processed.result; + +/* Use continuations */ +success_handler : result -> ..out "Success: " + result; +error_handler : error -> ..out "Error: " + error; + +process_with_continuation "valid data" success_handler error_handler; +process_with_continuation null success_handler error_handler; +``` + +## Performance Optimization + +### Avoiding Redundant Computations +```plaintext +/* Cache expensive computations */ +expensive_transform : data -> + /* Simulate expensive operation */ + data * data * data; + +/* With caching */ +transform_with_cache : { + cache: {}, + transform: data -> + when t.has cache data then t.get cache data + _ then { + result: expensive_transform data, + new_cache: t.set cache data (expensive_transform data) + } +}; + +/* Use cached version */ +result1 : transform_with_cache.transform 5; /* Computes */ +result2 : transform_with_cache.transform 5; /* Uses cache */ +``` + +### Lazy Collections +```plaintext +/* Lazy collection processing */ +lazy_map : f collection -> { + f: f, + collection: collection, + get: index -> + when index >= t.length collection then null + _ then f (t.get collection index) +}; + +/* Use lazy mapping */ +numbers : {1, 2, 3, 4, 5}; +expensive_double : x -> { + /* Simulate expensive operation */ + ..out "Doubling " + x; + x * 2 +}; + +lazy_doubled : lazy_map @expensive_double numbers; +/* No computation yet */ + +first_result : lazy_doubled.get 0; /* Only computes for index 0 */ +``` + +## Best Practices + +### Keep Combinators Focused +```plaintext +/* Good: Single responsibility */ +filter_by_age : min_age users -> + filter @(is_older_than min_age) users; + +is_older_than : min_age user -> user.age >= min_age; + +/* Avoid: Multiple responsibilities */ +bad_filter : min_age max_age users -> + filter @(complex_age_check min_age max_age) users; +``` + +### Use Descriptive Names +```plaintext +/* Good: Clear intent */ +process_active_users : users -> + filter @is_active (map @add_user_id users); + +/* Avoid: Generic names */ +process : data -> + filter @check (map @transform data); +``` + +### Compose, Don't Nest +```plaintext +/* Good: Composed functions */ +pipeline : compose @format_output (compose @add_metadata (filter @is_valid data)); + +/* Avoid: Deep nesting */ +nested : format_output (add_metadata (filter @is_valid data)); +``` + +## Next Steps + +Now that you understand advanced combinators, explore: +- [Integration Patterns](15_Integration_Patterns.md) for external system integration +- [Error Handling](13_Error_Handling.md) for robust error management +- [Best Practices](16_Best_Practices.md) for writing clean code \ No newline at end of file diff --git a/js/scripting-lang/tutorials/15_Integration_Patterns.md b/js/scripting-lang/tutorials/15_Integration_Patterns.md new file mode 100644 index 0000000..72e31ca --- /dev/null +++ b/js/scripting-lang/tutorials/15_Integration_Patterns.md @@ -0,0 +1,386 @@ +# Integration Patterns + +## What are Integration Patterns? + +Integration patterns show how to connect Baba Yaga programs with external systems, APIs, and other services while maintaining functional purity through the `..emit` and `..listen` pattern. + +## Basic Integration Concepts + +### Emit and Listen Pattern +```plaintext +/* Emit events to external systems */ +..emit "user_created" {id: 123, name: "Alice"}; +..emit "data_processed" {count: 42, success: true}; + +/* Listen for external events */ +..listen "user_created" handle_user_created; +..listen "data_processed" handle_data_processed; +``` + +### State Management +```plaintext +/* Get current state from external system */ +current_state : ..listen; + +/* Process based on state */ +user_id : current_state.user_id; +user_data : current_state.user_data; + +/* Emit processed result */ +..emit "user_processed" { + id: user_id, + processed_data: transform user_data +}; +``` + +## API Integration + +### HTTP Request Pattern +```plaintext +/* Emit HTTP requests */ +..emit { + action: "http_request", + method: "GET", + url: "https://api.example.com/users/123" +}; + +/* Emit POST request with data */ +..emit { + action: "http_request", + method: "POST", + url: "https://api.example.com/users", + data: {name: "Alice", email: "alice@example.com"} +}; +``` + +### API Response Handling +```plaintext +/* Listen for API responses */ +..listen "api_response" handle_api_response; + +handle_api_response : response -> + when response.success is + true then + ..out "API call successful:"; + ..out response.data + false then + ..out "API call failed:"; + ..out response.error; +``` + +## Database Integration + +### Database Operations +```plaintext +/* Emit database queries */ +..emit { + action: "db_query", + type: "select", + table: "users", + where: {id: 123} +}; + +/* Emit insert operation */ +..emit { + action: "db_query", + type: "insert", + table: "users", + data: {name: "Bob", email: "bob@example.com"} +}; +``` + +### Database Response Processing +```plaintext +/* Process database results */ +..listen "db_result" handle_db_result; + +handle_db_result : result -> + when result.type = "select" then + users : result.data; + processed_users : map @format_user users; + ..out "Found " + t.length users + " users"; + processed_users + _ then result.data; +``` + +## File System Integration + +### File Operations +```plaintext +/* Emit file operations */ +..emit { + action: "file_operation", + type: "read", + path: "/data/users.json" +}; + +/* Emit write operation */ +..emit { + action: "file_operation", + type: "write", + path: "/output/processed.json", + content: processed_data +}; +``` + +### File Processing +```plaintext +/* Process file contents */ +..listen "file_result" handle_file_result; + +handle_file_result : result -> + when result.type = "read" then + data : parse_json result.content; + processed : transform_data data; + processed + _ then result; +``` + +## Event-Driven Architecture + +### Event Processing Pipeline +```plaintext +/* Process incoming events */ +process_event : event -> + when event.type = "user_created" then + user : event.data; + validated_user : validate_user user; + when validated_user.valid is + true then + ..emit "user_validated" validated_user.data; + validated_user.data + false then + ..emit "validation_failed" validated_user.errors; + null + _ then event.data; +``` + +### Event Handlers +```plaintext +/* Register event handlers */ +..listen "user_created" process_event; +..listen "order_placed" process_event; +..listen "payment_received" process_event; +``` + +## External Service Integration + +### Third-Party API Integration +```plaintext +/* Integrate with external service */ +integrate_payment : order -> + payment_data : { + amount: order.total, + currency: "USD", + customer_id: order.customer_id + }; + + ..emit { + action: "external_api", + service: "stripe", + endpoint: "/payments", + method: "POST", + data: payment_data + }; + + payment_data; +``` + +### Service Response Handling +```plaintext +/* Handle external service responses */ +..listen "external_api_response" handle_external_response; + +handle_external_response : response -> + when response.service = "stripe" then + when response.success is + true then + ..emit "payment_successful" response.data; + response.data + false then + ..emit "payment_failed" response.error; + null + _ then response; +``` + +## Real-World Integration Example + +### E-commerce Order Processing +```plaintext +/* Complete order processing pipeline */ +process_order : order -> + /* Step 1: Validate order */ + validation_result : validate_order order; + when validation_result.valid is + false then + ..emit "order_invalid" validation_result.errors; + null + _ then + /* Step 2: Check inventory */ + ..emit { + action: "db_query", + type: "select", + table: "inventory", + where: {product_id: order.product_id} + }; + + /* Step 3: Process payment */ + payment_result : integrate_payment order; + + /* Step 4: Update inventory */ + ..emit { + action: "db_query", + type: "update", + table: "inventory", + where: {product_id: order.product_id}, + data: {quantity: decrement_quantity order.quantity} + }; + + /* Step 5: Send confirmation */ + ..emit { + action: "email", + to: order.customer_email, + subject: "Order Confirmed", + template: "order_confirmation", + data: order + }; + + {order_id: order.id, status: "processed"}; +``` + +## Error Handling in Integration + +### Graceful Degradation +```plaintext +/* Handle integration failures */ +safe_api_call : api_request -> + ..emit api_request; + + /* Set timeout for response */ + timeout_result : wait_for_response 5000; + when timeout_result.timeout is + true then + ..emit "api_timeout" api_request; + {error: "API timeout", fallback: true} + _ then timeout_result.response; +``` + +### Retry Logic +```plaintext +/* Retry failed operations */ +retry_operation : operation max_retries -> + attempt_operation : attempt -> + when attempt > max_retries then + ..emit "max_retries_exceeded" operation; + {error: "Max retries exceeded"} + _ then + result : operation; + when result.error is + true then + delay : power 2 attempt; /* Exponential backoff */ + ..emit "retry_attempt" {attempt: attempt, delay: delay}; + retry_operation operation max_retries + false then result; + + attempt_operation 1; +``` + +## Testing Integration + +### Mock External Services +```plaintext +/* Test integration without real services */ +test_payment_integration : -> + /* Mock order */ + test_order : { + id: "test_123", + total: 100, + customer_id: "cust_456" + }; + + /* Test payment integration */ + result : integrate_payment test_order; + + /* Verify emitted events */ + ..assert "Payment data emitted" result.amount = 100; + ..assert "Payment data emitted" result.currency = "USD"; + + ..out "Payment integration test passed"; +``` + +### Integration Test Patterns +```plaintext +/* Test complete integration flow */ +test_order_flow : -> + /* Test order */ + test_order : { + id: "test_123", + product_id: "prod_789", + quantity: 2, + customer_email: "test@example.com", + total: 50 + }; + + /* Process order */ + result : process_order test_order; + + /* Verify result */ + ..assert "Order processed successfully" result.status = "processed"; + ..assert "Order ID preserved" result.order_id = "test_123"; + + ..out "Order flow test passed"; +``` + +## Best Practices + +### Keep Integration Pure +```plaintext +/* Good: Pure function with explicit side effects */ +process_data : data -> + transformed : transform data; + ..emit "data_processed" transformed; + transformed; + +/* Avoid: Hidden side effects */ +bad_process : data -> + ..emit "processing_started"; /* Hidden side effect */ + transform data; +``` + +### Use Structured Events +```plaintext +/* Good: Structured event data */ +..emit { + type: "user_created", + timestamp: now(), + data: {id: 123, name: "Alice"}, + metadata: {source: "web_form", version: "1.0"} +}; + +/* Avoid: Unstructured events */ +..emit "user_created Alice 123"; /* Hard to parse */ +``` + +### Handle Errors Gracefully +```plaintext +/* Good: Explicit error handling */ +safe_integration : request -> + when request.valid is + false then + ..emit "integration_error" {request: request, error: "Invalid request"}; + null + _ then + result : call_external_service request; + when result.error is + true then + ..emit "service_error" result; + result.fallback_value + false then result.data; +``` + +## Next Steps + +Now that you understand integration patterns, explore: +- [Error Handling](13_Error_Handling.md) for robust error management +- [Advanced Combinators](14_Advanced_Combinators.md) for complex integration patterns +- [Best Practices](16_Best_Practices.md) for writing maintainable code \ No newline at end of file diff --git a/js/scripting-lang/tutorials/16_Best_Practices.md b/js/scripting-lang/tutorials/16_Best_Practices.md new file mode 100644 index 0000000..8a6b246 --- /dev/null +++ b/js/scripting-lang/tutorials/16_Best_Practices.md @@ -0,0 +1,236 @@ +# Operator Spacing Best Practices + +## Why Spacing Matters + +The language uses spacing to distinguish between different types of operators and make expressions unambiguous. Proper spacing follows functional language conventions and makes your code more readable. + +## Minus Operator Spacing + +### Unary Minus (Negative Numbers) + +Unary minus works without parentheses and requires no leading space: + +```plaintext +/* ✅ CORRECT - Unary minus without parentheses */ +-5; /* negate(5) */ +-3.14; /* negate(3.14) */ +-x; /* negate(x) */ +f -5; /* f(negate(5)) */ +map double -3; /* map(double, negate(3)) */ +``` + +### Binary Minus (Subtraction) + +Binary minus requires spaces on both sides: + +```plaintext +/* ✅ CORRECT - Binary minus with spaces */ +5 - 3; /* subtract(5, 3) */ +10 - 5; /* subtract(10, 5) */ +x - y; /* subtract(x, y) */ +3.14 - 1.5; /* subtract(3.14, 1.5) */ +``` + +### Legacy Syntax (Still Works) + +Legacy syntax continues to work for backward compatibility: + +```plaintext +/* ✅ CORRECT - Legacy syntax still works */ +(-5); /* negate(5) - explicit grouping */ +f (-5); /* f(negate(5)) - explicit grouping */ +5-3; /* subtract(5, 3) - legacy fallback */ +``` + +### Complex Expressions + +Complex expressions with mixed operators work correctly: + +```plaintext +/* ✅ CORRECT - Complex expressions */ +-5 + 3; /* add(negate(5), 3) */ +-5 - 3; /* subtract(negate(5), 3) */ +-5 * 3; /* multiply(negate(5), 3) */ +-5 + 3 - 2; /* subtract(add(negate(5), 3), 2) */ +``` + +## General Operator Spacing + +### Binary Operators + +All binary operators should have spaces around them: + +```plaintext +/* ✅ CORRECT - Binary operators with spaces */ +5 + 3; /* add(5, 3) */ +5 * 3; /* multiply(5, 3) */ +5 / 3; /* divide(5, 3) */ +5 % 3; /* modulo(5, 3) */ +5 ^ 3; /* power(5, 3) */ +``` + +### Comparison Operators + +Comparison operators require spaces: + +```plaintext +/* ✅ CORRECT - Comparison operators with spaces */ +5 = 3; /* equals(5, 3) */ +5 != 3; /* notEquals(5, 3) */ +5 < 3; /* lessThan(5, 3) */ +5 > 3; /* greaterThan(5, 3) */ +5 <= 3; /* lessEqual(5, 3) */ +5 >= 3; /* greaterEqual(5, 3) */ +``` + +### Logical Operators + +Logical operators require spaces: + +```plaintext +/* ✅ CORRECT - Logical operators with spaces */ +true and false; /* logicalAnd(true, false) */ +true or false; /* logicalOr(true, false) */ +true xor false; /* logicalXor(true, false) */ +``` + +### Unary Operators + +Unary operators (except minus) don't require special spacing: + +```plaintext +/* ✅ CORRECT - Unary operators */ +not true; /* logicalNot(true) */ +not false; /* logicalNot(false) */ +``` + +## When to Use Parentheses + +### Explicit Grouping + +Use parentheses when you need explicit control over precedence: + +```plaintext +/* ✅ CORRECT - Explicit grouping */ +(-5) + 3; /* add(negate(5), 3) - explicit grouping */ +f (-5); /* f(negate(5)) - explicit grouping */ +(5 + 3) * 2; /* multiply(add(5, 3), 2) - explicit grouping */ +``` + +### Complex Expressions + +Use parentheses to make complex expressions more readable: + +```plaintext +/* ✅ CORRECT - Complex expressions with parentheses */ +(-5 + 3) * 2; /* multiply(add(negate(5), 3), 2) */ +(-5) * (3 + 2); /* multiply(negate(5), add(3, 2)) */ +``` + +## Common Patterns + +### Function Calls with Negative Numbers + +```plaintext +/* ✅ CORRECT - Function calls with negative numbers */ +double -5; /* double(negate(5)) */ +map double -3; /* map(double, negate(3)) */ +filter is_negative {-5, 0, 5}; /* filter(is_negative, {-5, 0, 5}) */ +``` + +### Comparisons with Negative Numbers + +```plaintext +/* ✅ CORRECT - Comparisons with negative numbers */ +-5 >= 0; /* greaterEqual(negate(5), 0) */ +-5 < 0; /* lessThan(negate(5), 0) */ +is_negative -5; /* is_negative(negate(5)) */ +``` + +### Arithmetic with Mixed Operators + +```plaintext +/* ✅ CORRECT - Mixed arithmetic */ +-5 + 3 - 2; /* subtract(add(negate(5), 3), 2) */ +5 * -3 + 2; /* add(multiply(5, negate(3)), 2) */ +(-5) * 3 + 2; /* add(multiply(negate(5), 3), 2) */ +``` + +## Best Practices Summary + +### Do's + +- ✅ **Use spaces around binary operators**: `5 - 3`, `5 + 3`, `5 * 3` +- ✅ **Unary minus works without parentheses**: `-5`, `f -5` +- ✅ **Use parentheses for explicit grouping**: `(-5)`, `(5 + 3) * 2` +- ✅ **Use spaces around comparison operators**: `5 = 3`, `5 < 3` +- ✅ **Use spaces around logical operators**: `true and false` + +### Don'ts + +- ❌ **Don't omit spaces around binary operators**: `5-3`, `5+3` (legacy fallback) +- ❌ **Don't add spaces after unary minus**: `- 5` (legacy fallback) +- ❌ **Don't use inconsistent spacing**: `5- 3`, `5 -3` (legacy fallback) + +### When in Doubt + +- **Use spaces around binary operators** - it's always correct and more readable +- **Unary minus works without parentheses** - `-5` is the preferred syntax +- **Use parentheses for explicit grouping** - when you need to control precedence +- **Follow functional language conventions** - spaces around operators are standard + +## Examples in Context + +### Data Processing + +```plaintext +/* Process data with proper spacing */ +data : {-5, 0, 5, 10, 15}; +is_positive : x -> x > 0; +double : x -> x * 2; +sum : x -> reduce add 0 x; + +/* Pipeline with proper spacing */ +result : sum map double filter is_positive data; +/* Reads: sum (map double (filter is_positive data)) */ +/* Result: 60 (positive: {5,10,15}, doubled: {10,20,30}, sum: 60) */ +``` + +### Validation Logic + +```plaintext +/* Validation with proper spacing */ +validate_age : age -> (age >= 0) and (age <= 120); +validate_salary : salary -> (salary >= 0) and (salary <= 1000000); + +/* Test validation */ +test1 : validate_age -5; /* false */ +test2 : validate_age 25; /* true */ +test3 : validate_salary 50000; /* true */ +``` + +### Mathematical Expressions + +```plaintext +/* Mathematical expressions with proper spacing */ +calculate_discount : price discount_rate -> + price - (price * discount_rate); + +apply_tax : price tax_rate -> + price + (price * tax_rate); + +/* Use the functions */ +final_price : apply_tax (calculate_discount 100 0.1) 0.08; +/* Result: 97.2 (discount: 90, tax: 7.2) */ +``` + +## Key Takeaways + +1. **Spacing distinguishes operators** - unary vs binary minus +2. **Unary minus works without parentheses** - `-5` is preferred +3. **Binary operators need spaces** - `5 - 3`, `5 + 3`, `5 * 3` +4. **Legacy syntax still works** - but spaces are recommended +5. **Parentheses for explicit grouping** - when you need control +6. **Follow functional conventions** - spaces around operators are standard + +**Remember**: Proper spacing makes your code more readable and follows functional language conventions! 🚀 \ No newline at end of file diff --git a/js/scripting-lang/tutorials/README.md b/js/scripting-lang/tutorials/README.md new file mode 100644 index 0000000..30c03dd --- /dev/null +++ b/js/scripting-lang/tutorials/README.md @@ -0,0 +1,128 @@ +# Baba Yaga Tutorials + +Welcome to the Baba Yaga tutorials! These tutorials will guide you through learning this functional programming language step by step. + +## Getting Started + +Start with the **Introduction** tutorial to learn the basics, then follow the numbered sequence for a complete learning path. + +## Tutorial Sequence + +### 🚀 **Beginner Level** + +1. **[00_Introduction.md](00_Introduction.md)** - Basic concepts, functions, and pattern matching +2. **[01_Function_Calls.md](01_Function_Calls.md)** - Function calls without parentheses (juxtaposition) +3. **[02_Function_Composition.md](02_Function_Composition.md)** - Function composition with `via`, `compose`, and `pipe` +4. **[03_Table_Operations.md](03_Table_Operations.md)** - Working with tables and element-wise operations +5. **[04_Currying.md](04_Currying.md)** - Partial function application by default +6. **[05_Pattern_Matching.md](05_Pattern_Matching.md)** - Pattern matching with `when` expressions +7. **[06_Immutable_Tables.md](06_Immutable_Tables.md)** - Immutable table operations and functional programming +8. **[07_Function_References.md](07_Function_References.md)** - Function references with `@` symbol + +### 🔧 **Intermediate Level** + +9. **[08_Combinators.md](08_Combinators.md)** - Understanding the combinator-based architecture +10. **[09_Expression_Based.md](09_Expression_Based.md)** - Expression-based programming without explicit returns +11. **[10_Tables_Deep_Dive.md](10_Tables_Deep_Dive.md)** - Advanced table usage and data structures +12. **[11_Standard_Library.md](11_Standard_Library.md)** - Overview of available functions and combinators +13. **[12_IO_Operations.md](12_IO_Operations.md)** - Input/output operations and assertions +14. **[13_Error_Handling.md](13_Error_Handling.md)** - Error handling patterns and validation + +### 🎯 **Advanced Level** + +15. **[14_Advanced_Combinators.md](14_Advanced_Combinators.md)** - Advanced combinator patterns and optimization +16. **[15_Integration_Patterns.md](15_Integration_Patterns.md)** - External system integration and APIs +17. **[16_Best_Practices.md](16_Best_Practices.md)** - Best practices and coding guidelines + +## Key Concepts Covered + +- **Functional Programming**: Pure functions, immutability, composition +- **Pattern Matching**: `when` expressions for conditional logic +- **Tables**: Immutable data structures with functional operations +- **Combinators**: Higher-order functions for data transformation +- **IO Operations**: Input/output, assertions, and event handling +- **Error Handling**: Functional error patterns and validation +- **Integration**: External system integration patterns +- **Best Practices**: Operator spacing, syntax guidelines, and code organization + +## REPL Integration Documentation + +For comprehensive integration patterns and harness architecture documentation, see the **[REPL Documentation](../docs/repl/scripting-lang/0.0.1/repl.js.html)** which is generated directly from the REPL source code and contains extensive JSDoc comments about: + +- Architecture overview and TEA-inspired patterns +- Harness integration examples +- Adapter pattern implementation +- State management and versioning +- Error handling and recovery +- Command routing strategies +- Complete integration examples + +## Quick Reference + +### Essential Syntax + +```plaintext +/* Function definition */ +function_name : param1 param2 -> expression; + +/* Function application */ +function_name arg1 arg2; + +/* Pattern matching */ +when value is + pattern1 then result1 + pattern2 then result2 + _ then default_result; + +/* Table literals */ +{key1: value1, key2: value2}; + +/* Function references */ +map @function_name collection; + +/* IO operations */ +..out "Hello, World!"; +..assert "test" 5 = 5; +..emit "event" data; +..listen "event" handler; +``` + +### Best Practices + +- ✅ **Use spaces around binary operators**: `5 - 3`, `5 + 3`, `5 * 3` +- ✅ **Unary minus works without parentheses**: `-5`, `f -5` +- ✅ **Use parentheses for explicit grouping**: `(-5)`, `(5 + 3) * 2` +- ✅ **Follow functional conventions**: Immutable data, pure functions +- ✅ **Keep functions focused**: Single responsibility principle +- ✅ **Use descriptive names**: Clear intent and purpose +- ✅ **Handle errors explicitly**: Pattern matching over exceptions + +## Running Examples + +To run examples from these tutorials: + +1. Create a `.txt` or `.baba` file with the example code +2. Run: `node lang.js your_file.txt` + +Example: +```bash +# Create test.txt with tutorial code +echo "result : 5 - 3;" > test.txt + +# Run the example +node lang.js test.txt +``` + +## File Extensions + +Baba Yaga files should use either the `.txt` file extension, or the `.baba` extension. + +## Need Help? + +- Check the [main README](../README.md) for language overview +- Review [Best Practices](16_Best_Practices.md) for syntax guidelines +- Run the test suite: `./run_tests.sh` to see working examples +- Explore [Advanced Combinators](14_Advanced_Combinators.md) for complex patterns +- Check [Integration Patterns](15_Integration_Patterns.md) for external system integration + +Happy learning! 🚀 \ No newline at end of file |