# Delegation in Lyng Delegation is a powerful pattern that allows you to outsource the logic of properties (`val`, `var`) and functions (`fun`) to another object. This enables code reuse, separation of concerns, and the implementation of common patterns like lazy initialization, observable properties, and remote procedure calls (RPC) with minimal boilerplate. ## The `by` Keyword Delegation is triggered using the `by` keyword in a declaration. The expression following `by` is evaluated once when the member is initialized, and the resulting object becomes the **delegate**. ```lyng val x by MyDelegate() var y by MyDelegate() fun f by MyDelegate() ``` ## The Unified Delegate Model A delegate object can implement any of the following methods to intercept member access. All methods receive the `thisRef` (the instance containing the member) and the `name` of the member. ```lyng interface Delegate { // Called when a 'val' or 'var' is read fun getValue(thisRef, name) // Called when a 'var' is assigned fun setValue(thisRef, name, newValue) // Called when a 'fun' is invoked fun invoke(thisRef, name, args...) // Optional: Called once during initialization to "bind" the delegate // Can be used for validation or to return a different delegate instance fun bind(name, access, thisRef) = this } ``` ### Delegate Access Types The `bind` method receives an `access` parameter of type `DelegateAccess`, which can be one of: - `DelegateAccess.Val` - `DelegateAccess.Var` - `DelegateAccess.Callable` (for `fun`) ## Usage Cases and Examples ### 1. Lazy Initialization The classic `lazy` pattern ensures a value is computed only when first accessed and then cached. In Lyng, `lazy` is implemented as a class that follows this pattern. While classes typically start with an uppercase letter, `lazy` is an exception to make its usage feel like a native language feature. ```lyng class lazy(val creator) : Delegate { private var value = Unset override fun bind(name, access, thisRef) { if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'" this } override fun getValue(thisRef, name) { if (value == Unset) { // calculate value using thisRef as this: value = with(thisRef) creator() } value } } // Usage: val expensiveData by lazy { println("Performing expensive computation...") 42 } println(expensiveData) // Computes and prints 42 println(expensiveData) // Returns 42 immediately ``` ### 2. Observable Properties Delegates can be used to react to property changes. ```lyng class Observable(initialValue, val onChange) { private var value = initialValue fun getValue(thisRef, name) = value fun setValue(thisRef, name, newValue) { val oldValue = value value = newValue onChange(name, oldValue, newValue) } } class User { var name by Observable("Guest") { name, old, new -> println("Property %s changed from %s to %s"(name, old, new)) } } val u = User() u.name = "Alice" // Prints: Property name changed from Guest to Alice ``` ### 3. Function Delegation (Proxies) You can delegate an entire function to an object. This is particularly useful for implementing decorators or RPC clients. ```lyng object LoggerDelegate { fun invoke(thisRef, name, args...) { println("Calling function: " + name + " with args: " + args) // Logic here... "Result of " + name } } fun remoteAction by LoggerDelegate println(remoteAction(1, 2, 3)) // Prints: Calling function: remoteAction with args: [1, 2, 3] // Prints: Result of remoteAction ``` ### 4. Stateless Delegates (Shared Singletons) Because `getValue`, `setValue`, and `invoke` receive `thisRef`, a single object can act as a delegate for multiple properties across many instances without any per-property memory overhead. ```lyng object Constant42 { fun getValue(thisRef, name) = 42 } class Foo { val a by Constant42 val b by Constant42 } val f = Foo() assertEquals(42, f.a) assertEquals(42, f.b) ``` ### 5. Local Delegation Delegation is not limited to class members; you can also use it for local variables inside functions. ```lyng fun test() { val x by LocalProxy(123) println(x) } ``` ### 6. Map as a Delegate Maps can be used as delegates for `val` and `var` properties. When a map is used as a delegate, it uses the property name as a key to read from or write to the map. ```lyng val m = { "a": 1, "b": 2 } val a by m var b by m println(a) // 1 println(b) // 2 b = 42 println(m["b"]) // 42 ``` Because `Map` implements `getValue` and `setValue`, it works seamlessly with any object that needs to store its properties in a map (e.g., when implementing dynamic schemas or JSON-backed objects). ## The `bind` Hook The `bind(name, access, thisRef)` method is called exactly once when the member is being initialized. It allows the delegate to: 1. **Validate usage**: Throw an error if the delegate is used with the wrong member type (e.g., `lazy` on a `var`). 2. **Initialize state**: Set up internal state based on the property name or the containing instance. 3. **Substitute itself**: Return a different object that will act as the actual delegate. ```lyng class ValidatedDelegate() { fun bind(name, access, thisRef) { if (access == DelegateAccess.Var) { throw "This delegate cannot be used with 'var'" } this } fun getValue(thisRef, name) = "Validated" } ``` ## Summary Delegation in Lyng combines the elegance of Kotlin-style properties with the flexibility of dynamic function interception. By unifying `val`, `var`, and `fun` delegation into a single model, Lyng provides a consistent and powerful tool for meta-programming and code reuse.