189 lines
5.9 KiB
Markdown
189 lines
5.9 KiB
Markdown
# What's New in Lyng
|
|
|
|
This document highlights the latest additions and improvements to the Lyng language and its ecosystem.
|
|
|
|
## Language Features
|
|
|
|
### Class Properties with Accessors
|
|
Classes now support properties with custom `get()` and `set()` accessors. Properties in Lyng do **not** have automatic backing fields; they are pure accessors.
|
|
|
|
```lyng
|
|
class Person(private var _age: Int) {
|
|
// Read-only property
|
|
val ageCategory get() = if (_age < 18) "Minor" else "Adult"
|
|
|
|
// Read-write property
|
|
var age: Int
|
|
get() = _age
|
|
set(v) {
|
|
if (v >= 0) _age = v
|
|
}
|
|
}
|
|
```
|
|
|
|
### Private and Protected Setters
|
|
You can now restrict the visibility of a property's or field's setter using `private set` or `protected set`. This allows members to be publicly readable but only writable from within the declaring class or its subclasses.
|
|
|
|
```lyng
|
|
class Counter {
|
|
var count = 0
|
|
private set // Field with private setter
|
|
|
|
fun increment() { count++ }
|
|
}
|
|
|
|
class AdvancedCounter : Counter {
|
|
var totalOperations = 0
|
|
protected set // Settable here and in further subclasses
|
|
}
|
|
|
|
let c = Counter()
|
|
c.increment() // OK
|
|
// c.count = 10 // Error: setter is private
|
|
```
|
|
|
|
### Late-initialized `val` Fields
|
|
`val` fields in classes can be declared without an immediate initializer, provided they are assigned exactly once. If accessed before initialization, they hold the special `Unset` singleton.
|
|
|
|
```lyng
|
|
class Service {
|
|
val logger
|
|
|
|
fun check() {
|
|
if (logger == Unset) println("Not initialized yet")
|
|
}
|
|
|
|
init {
|
|
logger = Logger("Service")
|
|
}
|
|
}
|
|
```
|
|
|
|
### Named Arguments and Named Splats
|
|
Function calls now support named arguments using the `name: value` syntax. If the variable name matches the parameter name, you can use the `name:` shorthand.
|
|
|
|
```lyng
|
|
fun greet(name, greeting = "Hello") {
|
|
println("$greeting, $name!")
|
|
}
|
|
|
|
val name = "Alice"
|
|
greet(name:) // Shorthand for greet(name: name)
|
|
greet(greeting: "Hi", name: "Bob")
|
|
|
|
let params = { name: "Charlie", greeting: "Hey")
|
|
greet(...params) // Named splat expansion
|
|
```
|
|
|
|
### Multiple Inheritance (MI)
|
|
Lyng now supports multiple inheritance using the C3 Method Resolution Order (MRO). Use `this@Type` or casts for disambiguation.
|
|
|
|
```lyng
|
|
class A { fun foo() = "A" }
|
|
class B { fun foo() = "B" }
|
|
|
|
class Derived : A, B {
|
|
fun test() {
|
|
println(foo()) // Resolves to A.foo (leftmost)
|
|
println(this@B.foo()) // Qualified dispatch to B.foo
|
|
}
|
|
}
|
|
|
|
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()
|
|
```
|
|
|
|
### Object Expressions
|
|
You can now create anonymous objects that inherit from classes or interfaces using the `object : Base { ... }` syntax. These expressions capture their lexical scope and support multiple inheritance.
|
|
|
|
```lyng
|
|
val worker = object : Runnable {
|
|
override fun run() = println("Working...")
|
|
}
|
|
|
|
val x = object : Base(arg1), Interface1 {
|
|
val property = 42
|
|
override fun method() = this@object.property * 2
|
|
}
|
|
```
|
|
|
|
Use `this@object` to refer to the innermost anonymous object instance when `this` is rebound.
|
|
|
|
### 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.
|
|
|
|
### User-Defined Exception Classes
|
|
You can now create custom exception types by inheriting from the built-in `Exception` class. Custom exceptions are real classes that can have their own fields and methods, and they work seamlessly with `throw` and `try-catch` blocks.
|
|
|
|
```lyng
|
|
class ValidationException(val field, m) : Exception(m)
|
|
|
|
try {
|
|
throw ValidationException("email", "Invalid format")
|
|
}
|
|
catch(e: ValidationException) {
|
|
println("Error in " + e.field + ": " + e.message)
|
|
}
|
|
```
|
|
|
|
### Assign-if-null Operator (`?=`)
|
|
The new `?=` operator provides a concise way to assign a value only if the target is `null`. It is especially useful for setting default values or lazy initialization.
|
|
|
|
```lyng
|
|
var x = null
|
|
x ?= 42 // x is now 42
|
|
x ?= 100 // x remains 42 (not null)
|
|
|
|
// Works with properties and index access
|
|
config.port ?= 8080
|
|
settings["theme"] ?= "dark"
|
|
```
|
|
|
|
The operator returns the final value of the receiver (the original value if it was not `null`, or the new value if the assignment occurred).
|
|
|
|
## Tooling and Infrastructure
|
|
|
|
### CLI: Formatting Command
|
|
A new `fmt` subcommand has been added to the Lyng CLI.
|
|
|
|
```bash
|
|
lyng fmt MyFile.lyng # Print formatted code to stdout
|
|
lyng fmt --in-place MyFile.lyng # Format file in-place
|
|
lyng fmt --check MyFile.lyng # Check if file needs formatting
|
|
```
|
|
|
|
### IDEA Plugin: Autocompletion
|
|
Experimental lightweight autocompletion is now available in the IntelliJ plugin. It features type-aware member suggestions and inheritance-aware completion.
|
|
|
|
You can enable it in **Settings | Lyng Formatter | Enable Lyng autocompletion**.
|
|
|
|
### Kotlin API: Exception Handling
|
|
The `Obj.getLyngExceptionMessageWithStackTrace()` extension method has been added to simplify retrieving detailed error information from Lyng exception objects in Kotlin. Additionally, `getLyngExceptionMessage()` and `raiseAsExecutionError()` now accept an optional `Scope`, making it easier to use them when a scope is not immediately available.
|