5.5 KiB
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.
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.
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.
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.
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.
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.
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.
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.
// 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 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.
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.
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.
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.