improved lazy delegate; added with(newThis) {} to stdlib

This commit is contained in:
Sergey Chernov 2026-01-05 22:25:46 +01:00
parent f792c73b8f
commit 41a3617850
6 changed files with 85 additions and 8 deletions

View File

@ -2,6 +2,8 @@
### Unreleased ### Unreleased
- Language: stdlib improvements
- Added `with(self, block)` function to `root.lyng` which executes a block with `this` set to the provided object.
- Language: Abstract Classes and Interfaces - Language: Abstract Classes and Interfaces
- Support for `abstract` modifier on classes, methods, and variables. - Support for `abstract` modifier on classes, methods, and variables.
- Introduced `interface` as a synonym for `abstract class`, supporting full state (constructors, fields, `init` blocks) and implementation by parts via MI. - Introduced `interface` as a synonym for `abstract class`, supporting full state (constructors, fields, `init` blocks) and implementation by parts via MI.

View File

@ -298,6 +298,16 @@ It works much like `also`, but is executed in the context of the source object:
assertEquals(p, Point(2,3)) assertEquals(p, Point(2,3))
>>> void >>> void
## with
Sets `this` to the first argument and executes the block. Returns the value returned by the block:
class Point(x,y)
val p = Point(1,2)
val sum = with(p) { x + y }
assertEquals(3, sum)
>>> void
## run ## run
Executes a block after it returning the value passed by the block. for example, can be used with elvis operator: Executes a block after it returning the value passed by the block. for example, can be used with elvis operator:
@ -1519,7 +1529,7 @@ See [math functions](math.md). Other general purpose functions are:
| flow {} | create flow sequence, see [parallelism] | | flow {} | create flow sequence, see [parallelism] |
| delay, launch, yield | see [parallelism] | | delay, launch, yield | see [parallelism] |
| cached(builder) | [Lazy evaluation with `cached`](#lazy-evaluation-with-cached) | | cached(builder) | [Lazy evaluation with `cached`](#lazy-evaluation-with-cached) |
| let, also, apply, run | see above, flow controls | | let, also, apply, run, with | see above, flow controls |
(1) (1)
: `fn` is optional lambda returning string message to add to exception string. : `fn` is optional lambda returning string message to add to exception string.

View File

@ -17,6 +17,7 @@
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
@ -65,11 +66,11 @@ kotlin {
browser() browser()
nodejs() nodejs()
} }
// @OptIn(ExperimentalWasmDsl::class) @OptIn(ExperimentalWasmDsl::class)
// wasmJs() { wasmJs() {
// browser() browser()
// nodejs() nodejs()
// } }
// Suppress Beta warning for expect/actual classes across all targets // Suppress Beta warning for expect/actual classes across all targets
targets.configureEach { targets.configureEach {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -113,4 +113,22 @@ class StdlibTest {
assertEquals(5, (1..10).toList().count { it % 2 == 1 } ) assertEquals(5, (1..10).toList().count { it % 2 == 1 } )
""") """)
} }
@Test
fun testWith() = runTest {
eval("""
class Person(val name, var age)
val p = Person("Alice", 30)
val result = with(p) {
assertEquals("Alice", name)
assertEquals(30, age)
age = 31
"done"
}
assertEquals("done", result)
assertEquals(31, p.age)
""".trimIndent())
}
} }

View File

@ -287,4 +287,40 @@ class DelegationTest {
""".trimIndent()) """.trimIndent())
} }
@Test
fun testThisInLazy() = runTest {
eval("""
class A {
val numbers = [1,2,3]
val tags by lazy { this.numbers }
}
class B {
val a by lazy { A() }
val test by lazy { a.tags + [4] }
}
assertEquals( [1,2,3], A().tags)
assertEquals( [1,2,3,4], B().test)
""")
}
@Test
fun testScopeInLazy() = runTest {
val s1 = Script.newScope()
s1.eval("""
class A {
val tags by lazy { GLOBAL_NUMBERS }
}
""".trimIndent())
// on the same scope, it will see my GLOBAL_NUMBERS:
s1.eval("""
val GLOBAL_NUMBERS = [1,2,3]
class B {
val a by lazy { A() }
val test by lazy { a.tags + [4] }
}
assertEquals( [1,2,3], A().tags)
assertEquals( [1,2,3,4], B().test)
""")
}
} }

View File

@ -310,6 +310,16 @@ interface Delegate {
fun bind(name, access, thisRef) = this fun bind(name, access, thisRef) = this
} }
/*
Executes the block with `this` set to self and
returns what the block returns.
*/
fun with(self, block) {
var result = Unset
self.apply { result = block() }
result
}
/* /*
Standard implementation of a lazy-initialized property delegate. Standard implementation of a lazy-initialized property delegate.
The provided creator lambda is called once on the first access to compute the value. The provided creator lambda is called once on the first access to compute the value.
@ -325,7 +335,7 @@ class lazy(creatorParam) : Delegate {
} }
override fun getValue(thisRef, name) { override fun getValue(thisRef, name) {
if (value == Unset) value = creator() if (value == Unset) value = with(thisRef,creator)
value value
} }
} }