+ Ma is now Delegate
+ way to add pure Lyng parents when declaring kotlin bindings
This commit is contained in:
parent
3ef68d8bb4
commit
3941ddee40
13
docs/OOP.md
13
docs/OOP.md
@ -224,6 +224,19 @@ A delegate is any object that provides the following methods (all optional depen
|
||||
- `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.
|
||||
|
||||
### Map as a Delegate
|
||||
|
||||
Maps can also be used as delegates. When delegated to a property, the map uses the property name as the key:
|
||||
|
||||
```lyng
|
||||
val settings = { "theme": "dark", "fontSize": 14 }
|
||||
val theme by settings
|
||||
var fontSize by settings
|
||||
|
||||
println(theme) // "dark"
|
||||
fontSize = 16 // Updates settings["fontSize"]
|
||||
```
|
||||
|
||||
For more details and advanced patterns (like `lazy`, `observable`, and shared stateless delegates), see the [Delegation Guide](delegation.md).
|
||||
|
||||
## Instance initialization: init block
|
||||
|
||||
@ -151,6 +151,24 @@ fun test() {
|
||||
}
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
@ -61,7 +61,8 @@ open class Obj {
|
||||
@Suppress("SuspiciousEqualsCombination")
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass ||
|
||||
objClass.allParentsSet.contains(someClass) ||
|
||||
someClass == rootObjectType
|
||||
someClass == rootObjectType ||
|
||||
(someClass is ObjClass && objClass.allImplementingNames.contains(someClass.className))
|
||||
|
||||
|
||||
suspend fun invokeInstanceMethod(scope: Scope, name: String, vararg args: Obj): Obj =
|
||||
|
||||
@ -124,6 +124,24 @@ open class ObjClass(
|
||||
/** Direct parents in declaration order (kept deterministic). */
|
||||
val directParents: List<ObjClass> = parents.toList()
|
||||
|
||||
/**
|
||||
* Names of additional interfaces this class implements, but they are not (yet) available
|
||||
* as [ObjClass] instances. This is used for "implementing existing interfaces" feature.
|
||||
*/
|
||||
val implementingNames = mutableSetOf<String>()
|
||||
|
||||
/**
|
||||
* Combined set of [implementingNames] from this class and all its ancestors.
|
||||
*/
|
||||
val allImplementingNames: Set<String> by lazy {
|
||||
buildSet {
|
||||
addAll(implementingNames)
|
||||
for (p in allParentsSet) {
|
||||
addAll(p.implementingNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Optional constructor argument specs for each direct parent (set by compiler for user classes). */
|
||||
open val directParentArgs: MutableMap<ObjClass, List<ParsedArgument>> = mutableMapOf()
|
||||
|
||||
|
||||
@ -193,7 +193,6 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
return map
|
||||
}
|
||||
|
||||
|
||||
val type = object : ObjClass("Map", ObjCollection) {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
return ObjMap(listToMap(scope, scope.args.list))
|
||||
@ -206,6 +205,25 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
return ObjMap(keys.zip(values).toMap().toMutableMap())
|
||||
}
|
||||
}.apply {
|
||||
implementingNames.add("Delegate")
|
||||
addFn("getValue") {
|
||||
val self = thisAs<ObjMap>()
|
||||
val key = requiredArg<Obj>(1)
|
||||
self.map[key] ?: ObjNull
|
||||
}
|
||||
addFn("setValue") {
|
||||
val self = thisAs<ObjMap>()
|
||||
val key = requiredArg<Obj>(1)
|
||||
val value = requiredArg<Obj>(2)
|
||||
self.map[key] = value
|
||||
self
|
||||
}
|
||||
addFn("bind") {
|
||||
val mode = requiredArg<ObjEnumEntry>(1)
|
||||
if( mode.ordinal.value > 1)
|
||||
raiseIllegalArgument("Map can be delegated only to val or var, got ${mode.name.value}")
|
||||
thisObj
|
||||
}
|
||||
addFnDoc(
|
||||
name = "getOrNull",
|
||||
doc = "Get value by key or return null if the key is absent.",
|
||||
|
||||
@ -3024,6 +3024,24 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMapAsDelegate() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val m = { a: 1, b: 2 }
|
||||
assert(m is Delegate)
|
||||
val a by m
|
||||
var b by m
|
||||
assertEquals(1, a)
|
||||
assertEquals(2, b)
|
||||
b = 42
|
||||
assertEquals(42, m["b"])
|
||||
assertEquals(1, a)
|
||||
assertEquals(1, m["a"])
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMapWithNonStringKeys() = runTest {
|
||||
eval(
|
||||
|
||||
48
proposals/implementing_existing_interfaces.md
Normal file
48
proposals/implementing_existing_interfaces.md
Normal file
@ -0,0 +1,48 @@
|
||||
> under cosntruction
|
||||
> note to AI: ignore this file for now
|
||||
|
||||
The problem is, sometimes we want to implement an interface enhancing existing classes. For example, we want ti implement Delegate for Map. We can add extension methods to Map that do the work, but we can add Delegate to the inheritance chain.
|
||||
|
||||
The problem is not trivial: while adding interfaces in other languages is easy, adding the while
|
||||
class with a state to existing one should be done carefully.
|
||||
|
||||
Proposed syntax:
|
||||
|
||||
```lyng
|
||||
extend Map with Delegate {
|
||||
fun getValue(thisRef, key) = this[key]
|
||||
fun setValue(thisRef, key, value) = this[key] = value
|
||||
}
|
||||
```
|
||||
|
||||
And now we can use Map as a Delegate:
|
||||
```lyng
|
||||
val map = { foo: 1. bar: 2 }
|
||||
val foo by map
|
||||
assertEquals(1, foo)
|
||||
```
|
||||
|
||||
The syntax is similar to the one used for inheritance. But while Delegate has no state and it is actually simple. Much harder task is ti implement some class with state (trait):
|
||||
|
||||
```lyng
|
||||
// the class we will use as a trait must have on constructor parameters
|
||||
// or only parameters with default values
|
||||
class MyTraitClass(initValue=100) {
|
||||
private var field
|
||||
fun traitField get() = field + initValue
|
||||
set(value) { field = value }
|
||||
}
|
||||
|
||||
extend Map with MyTraitClass
|
||||
|
||||
assertEquals(100, Map().traitField)
|
||||
val m = Map()
|
||||
m.traitField = 1000
|
||||
assertEquals(1100,m.traitField)
|
||||
```
|
||||
|
||||
We limit extension to module scope level, e.g., not in functions, not in classes, but at the "global level", probably ModuleScope.
|
||||
|
||||
The course of action could be:
|
||||
|
||||
- when constructing a class instance, compiler search in the ModuleScope extensions for it, and if found, add them to MI parent list to the end in the order of appearance in code (e.g. random ;)), them construct the instance as usual.
|
||||
Loading…
x
Reference in New Issue
Block a user