diff options
Diffstat (limited to 'js/scripting-lang/tutorials/12_Functional_Harness_Integration.md')
-rw-r--r-- | js/scripting-lang/tutorials/12_Functional_Harness_Integration.md | 569 |
1 files changed, 0 insertions, 569 deletions
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 |