Added delegation support: Delegate, lazy, "by" keyword for valr/var/fun and object {} singletons
This commit is contained in:
1 parent
8e766490d9
commit
5f819dc87a
20 files changed
+1183
-754
No files matched your search
+62
@@ -42,6 +42,35 @@ a _constructor_ that requires two parameters for fields. So when creating it wit
|
||||
Form now on `Point` is a class, it's type is `Class`, and we can create instances with it as in the
|
||||
example above.
|
||||
|
||||
## Singleton Objects
|
||||
|
||||
Singleton objects are declared using the `object` keyword. An `object` declaration defines both a class and a single instance of that class at the same time. This is perfect for stateless utilities, global configuration, or shared delegates.
|
||||
|
||||
```lyng
|
||||
object Config {
|
||||
val version = "1.0.0"
|
||||
val debug = true
|
||||
|
||||
fun printInfo() {
|
||||
println("App version: " + version)
|
||||
}
|
||||
}
|
||||
|
||||
// Usage:
|
||||
println(Config.version)
|
||||
Config.printInfo()
|
||||
```
|
||||
|
||||
Objects can also inherit from classes or interfaces:
|
||||
|
||||
```lyng
|
||||
object DefaultLogger : Logger("Default") {
|
||||
override fun log(msg) {
|
||||
println("[DEFAULT] " + msg)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
Properties allow you to define member accessors that look like fields but execute code when read or written. Unlike regular fields, properties in Lyng do **not** have automatic backing fields; they are pure accessors.
|
||||
@@ -122,6 +151,39 @@ println(service.data()) // Returns "Record 42" immediately (no second fetch)
|
||||
|
||||
Note that `cached` returns a lambda, so you access the value by calling it like a method: `service.data()`. This is a powerful pattern for lazy-loading resources, caching results of database queries, or delaying expensive computations until they are truly needed.
|
||||
|
||||
## Delegation
|
||||
|
||||
Delegation allows you to hand over the logic of a property or function to another object. This is done using the `by` keyword.
|
||||
|
||||
### Property Delegation
|
||||
|
||||
Instead of providing `get()` and `set()` accessors, you can delegate them to an object that implements the `getValue` and `setValue` methods.
|
||||
|
||||
```lyng
|
||||
class User {
|
||||
var name by MyDelegate()
|
||||
}
|
||||
```
|
||||
|
||||
### Function Delegation
|
||||
|
||||
You can also delegate a whole function to an object. When the function is called, it will invoke the delegate's `invoke` method.
|
||||
|
||||
```lyng
|
||||
fun remoteAction by RemoteProxy("actionName")
|
||||
```
|
||||
|
||||
### The Unified Delegate Interface
|
||||
|
||||
A delegate is any object that provides the following methods (all optional depending on usage):
|
||||
|
||||
- `getValue(thisRef, name)`: Called when a delegated `val` or `var` is read.
|
||||
- `setValue(thisRef, name, newValue)`: Called when a delegated `var` is written.
|
||||
- `invoke(thisRef, name, args...)`: Called when a delegated `fun` is invoked.
|
||||
- `bind(name, access, thisRef)`: Called once during initialization to configure or validate the delegate.
|
||||
|
||||
For more details and advanced patterns (like `lazy`, `observable`, and shared stateless delegates), see the [Delegation Guide](delegation.md).
|
||||
|
||||
## Instance initialization: init block
|
||||
|
||||
In addition to the primary constructor arguments, you can provide an `init` block that runs on each instance creation. This is useful for more complex initializations, side effects, or setting up fields that depend on multiple constructor parameters.
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# 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) {
|
||||
value = 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)
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
@@ -87,6 +87,27 @@ Lyng supports simple enums for a fixed set of named constants. Declare with `enu
|
||||
|
||||
For more details (usage patterns, `when` switching, serialization), see OOP notes: [Enums in detail](OOP.md#enums).
|
||||
|
||||
## Singleton Objects
|
||||
|
||||
Singleton objects are declared using the `object` keyword. They define a class and create its single instance immediately.
|
||||
|
||||
object Logger {
|
||||
fun log(msg) { println("[LOG] " + msg) }
|
||||
}
|
||||
|
||||
Logger.log("Hello singleton!")
|
||||
|
||||
## Delegation (briefly)
|
||||
|
||||
You can delegate properties and functions to other objects using the `by` keyword. This is perfect for patterns like `lazy` initialization.
|
||||
|
||||
val expensiveData by lazy {
|
||||
// computed only once on demand
|
||||
"computed"
|
||||
}
|
||||
|
||||
For more details on these features, see [Delegation in Lyng](delegation.md) and [OOP notes](OOP.md).
|
||||
|
||||
When putting multiple statments in the same line it is convenient and recommended to use `;`:
|
||||
|
||||
var from; var to
|
||||
|
||||
@@ -93,6 +93,36 @@ let d = Derived()
|
||||
println((d as B).foo()) // Disambiguation via cast
|
||||
```
|
||||
|
||||
### Singleton Objects
|
||||
Singleton objects are declared using the `object` keyword. They provide a convenient way to define a class and its single instance in one go.
|
||||
|
||||
```lyng
|
||||
object Config {
|
||||
val version = "1.2.3"
|
||||
fun show() = println("Config version: " + version)
|
||||
}
|
||||
|
||||
Config.show()
|
||||
```
|
||||
|
||||
### Unified Delegation Model
|
||||
A powerful new delegation system allows `val`, `var`, and `fun` members to delegate their logic to other objects using the `by` keyword.
|
||||
|
||||
```lyng
|
||||
// Property delegation
|
||||
val lazyValue by lazy { "expensive" }
|
||||
|
||||
// Function delegation
|
||||
fun remoteAction by myProxy
|
||||
|
||||
// Observable properties
|
||||
var name by Observable("initial") { n, old, new ->
|
||||
println("Changed!")
|
||||
}
|
||||
```
|
||||
|
||||
The system features a unified interface (`getValue`, `setValue`, `invoke`) and a `bind` hook for initialization-time validation and configuration. See the [Delegation Guide](delegation.md) for more.
|
||||
|
||||
## Tooling and Infrastructure
|
||||
|
||||
### CLI: Formatting Command
|
||||
|
||||
Reference in new issue
Block a user