# What's New in Lyng This document highlights the current Lyng release, **1.5.4**, and the broader additions from the 1.5 cycle. It is intentionally user-facing: new language features, new modules, new tools, and the practical things you can build with them. For a programmer-focused migration summary across 1.5.x, see `docs/whats_new_1_5.md`. ## Release 1.5.4 Highlights - `1.5.4` is the stabilization release for the 1.5 feature set. - The 1.5 line now brings together richer ranges and loops, interpolation, math modules, immutable and observable collections, richer `lyngio`, and much better CLI/IDE support. - `1.5.4` specifically fixes user-visible issues around decimal arithmetic, mixed numeric flows, list behavior, and observable list hooks. - `1.5.4` also fixes extension-member registration for named singleton `object` declarations, so `fun X.foo()` and `val X.bar` now work as expected. - `1.5.4` also lets named singleton `object` declarations use scoped indexer extensions with bracket syntax, so patterns like `Storage["name"]` can be implemented with `override fun Storage.getAt(...)` / `putAt(...)`. - The docs, homepage samples, and release metadata now point at the current stable version. ## User Highlights Across 1.5.x - Descending ranges and loops with `downTo` / `downUntil` - String interpolation with `$name` and `${expr}` - Decimal arithmetic, matrices/vectors, and complex numbers - Calendar `Date` support in `lyng.time` - Immutable collections and opt-in `ObservableList` - Rich `lyngio` modules for SQLite databases, console, HTTP, WebSocket, TCP, and UDP - CLI improvements including the built-in formatter `lyng fmt` - Better IDE support and stronger docs around the released feature set ## Language Features ### Descending Ranges and Loops Lyng ranges are no longer just ascending. You can now write explicit descending ranges with inclusive or exclusive lower bounds. ```lyng assertEquals([5,4,3,2,1], (5 downTo 1).toList()) assertEquals([5,4,3,2], (5 downUntil 1).toList()) for (i in 10 downTo 1 step 3) { println(i) } ``` This also works for characters: ```lyng assertEquals(['e','c','a'], ('e' downTo 'a' step 2).toList()) ``` See [Range](Range.md). ### String Interpolation Lyng 1.5.1 added built-in string interpolation: - `$name` - `${expr}` Literal dollar forms are explicit too: - `\$` -> `$` - `$$` -> `$` ```lyng val name = "Lyng" assertEquals("hello, Lyng!", "hello, $name!") assertEquals("sum=3", "sum=${1+2}") assertEquals("\$name", "\$name") assertEquals("\$name", "$$name") ``` If you need legacy literal-dollar behavior in a file, add: ```lyng // feature: interpolation: off ``` See [Tutorial](tutorial.md). ### Matrix and Vector Module (`lyng.matrix`) Lyng now ships a dense linear algebra module with immutable double-precision `Matrix` and `Vector` types. It provides: - `matrix([[...]])` and `vector([...])` - matrix multiplication - matrix inversion - determinant, trace, rank - solving `A * x = b` - vector operations such as `dot`, `normalize`, `cross`, and `outer` ```lyng import lyng.matrix val a: Matrix = matrix([[4, 7], [2, 6]]) val inv: Matrix = a.inverse() assert(abs(inv.get(0, 0) - 0.6) < 1e-9) ``` Matrices also support Lyng-style slicing: ```lyng import lyng.matrix val m: Matrix = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) assertEquals(6.0, m[1, 2]) val column: Matrix = m[0..2, 2] val tail: Matrix = m[1.., 1..] assertEquals([[3.0], [6.0], [9.0]], column.toList()) assertEquals([[5.0, 6.0], [8.0, 9.0]], tail.toList()) ``` See [Matrix](Matrix.md). ### Multiple Selectors in Bracket Indexing Bracket indexing now accepts more than one selector: ```lyng value[i] value[i, j] value[i, j, k] ``` For custom indexers, multiple selectors are packed into one list-like index object and dispatched through `getAt` / `putAt`. This is the rule used by `lyng.matrix` and by embedding APIs for Kotlin-backed indexers. ### Decimal Arithmetic Module (`lyng.decimal`) Lyng now ships a first-class decimal module built as a regular extension library rather than a deep core special case. It provides: - `Decimal` - convenient `.d` conversions from `Int`, `Real`, and `String` - mixed arithmetic with `Int` and `Real` - local division precision and rounding control via `withDecimalContext(...)` ```lyng import lyng.decimal assertEquals("3", (1 + 2.d).toStringExpanded()) assertEquals("0.30000000000000004", (0.1 + 0.2).d.toStringExpanded()) assertEquals("0.3", "0.3".d.toStringExpanded()) assertEquals( "0.3333333333", withDecimalContext(10) { (1.d / 3.d).toStringExpanded() } ) ``` The distinction between `Real -> Decimal` and exact decimal parsing is explicit by design: - `2.2.d` converts the current `Real` value - `"2.2".d` parses exact decimal text See [Decimal](Decimal.md). ### Complex Numbers (`lyng.complex`) Lyng also ships a complex-number module for ordinary arithmetic in the complex plane. ```lyng import lyng.complex assertEquals(Complex(1.0, 2.0), 1 + 2.i) assertEquals(Complex(2.0, 2.0), 2.i + 2) val z = 1 + π.i println(z.exp()) ``` See [Complex](Complex.md). ### Legacy Digest Module (`lyng.legacy_digest`) For situations where an external protocol or file format requires a SHA-1 value, Lyng now ships a `lyng.legacy_digest` module backed by a pure Kotlin/KMP implementation with no extra dependencies. > ⚠️ SHA-1 is cryptographically broken. Use only for legacy-compatibility work. ```lyng import lyng.legacy_digest val hex = LegacyDigest.sha1("abc") // → "a9993e364706816aba3e25717850c26c9cd0d89d" // Also accepts raw bytes: import lyng.buffer val buf = Buffer.decodeHex("616263") assertEquals(hex, LegacyDigest.sha1(buf)) ``` The name `LegacyDigest` is intentional: it signals that these algorithms belong to a compatibility layer, not to a current security toolkit. See [LegacyDigest](LegacyDigest.md). ### Binary Operator Interop Registry Lyng now provides a general mechanism for mixed binary operators through `lyng.operators`. This solves cases like: - `Int + MyType` - `Real < MyType` - `Int == MyType` without requiring changes to built-in classes. ```lyng import lyng.operators class DecimalBox(val value: Int) { fun plus(other: DecimalBox) = DecimalBox(value + other.value) fun compareTo(other: DecimalBox) = value <=> other.value } OperatorInterop.register( Int, DecimalBox, DecimalBox, [BinaryOperator.Plus, BinaryOperator.Compare, BinaryOperator.Equals], { x: Int -> DecimalBox(x) }, { x: DecimalBox -> x } ) assertEquals(DecimalBox(3), 1 + DecimalBox(2)) assert(1 < DecimalBox(2)) assert(2 == DecimalBox(2)) ``` `lyng.decimal` uses this same mechanism internally to interoperate with `Int` and `Real`. See [Operator Interop Registry](OperatorInterop.md). ### 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. ### Refined Protected Visibility Ancestor classes can now access `protected` members of their descendants if it is an override of a member known to the ancestor. This enables base classes to call protected methods that are implemented or overridden in 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.5.4" fun show() = println("Config version: " + version) } Config.show() ``` Named singleton objects can also be used as extension receivers: ```lyng object X { fun base() = "base" } fun X.decorate(value): String { this.base() + ":" + value.toString() } val X.tag get() = this.base() + ":tag" assertEquals("base:42", X.decorate(42)) assertEquals("base:tag", X.tag) ``` ### Nested Declarations and Lifted Enums You can now declare classes, objects, enums, and type aliases inside another class. These nested declarations live in the class namespace (no outer instance capture) and are accessed with a qualifier. ```lyng class A { class B(x?) object Inner { val foo = "bar" } enum E* { One, Two } } val ab = A.B() assertEquals(ab.x, null) assertEquals(A.Inner.foo, "bar") assertEquals(A.One, A.E.One) ``` The `*` on `enum E*` lifts entries into the enclosing class namespace (compile-time error on ambiguity). ### 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). ### Transient Attribute (`@Transient`) The `@Transient` attribute can now be applied to class fields, constructor parameters, and static fields to exclude them from serialization. ```lyng class MyData(@Transient val tempSecret, val publicData) { @Transient var cachedValue = 0 var persistentValue = 42 } ``` Key features: - **Serialization**: Transient members are omitted from both Lynon binary streams and JSON output. - **Structural Equality**: Transient fields are automatically ignored during `==` equality checks. - **Deserialization**: Transient constructor parameters with default values are correctly restored to those defaults upon restoration. ### Value Clamping (`clamp`) A new `clamp()` function has been added to the standard library to limit a value within a specified range. It is available as both a global function and an extension method on all objects. ```lyng // Global function clamp(15, 0..10) // returns 10 clamp(-5, 0..10) // returns 0 // Extension method val x = 15 x.clamp(0..10) // returns 10 // Exclusive and open-ended ranges 15.clamp(0..<10) // returns 9 15.clamp(..10) // returns 10 -5.clamp(0..) // returns 0 ``` `clamp()` correctly handles inclusive (`..`) and exclusive (`..<`) ranges. For discrete types like `Int` and `Char`, clamping to an exclusive upper bound returns the previous value. ### Immutable Collections Lyng 1.5 adds immutable collection types for APIs that should not expose mutable state through aliases: - `ImmutableList` - `ImmutableSet` - `ImmutableMap` ```lyng val a = ImmutableList(1,2,3) val b = a + 4 assertEquals(ImmutableList(1,2,3), a) assertEquals(ImmutableList(1,2,3,4), b) ``` See [ImmutableList](ImmutableList.md), [ImmutableSet](ImmutableSet.md), and [ImmutableMap](ImmutableMap.md). ### Observable Mutable Lists For reactive-style code, `lyng.observable` provides `ObservableList` with hooks and change streams. ```lyng import lyng.observable val xs = [1,2].observable() xs.onChange { println("changed") } xs += 3 ``` You can validate or reject mutations in `beforeChange`, listen in `onChange`, and consume structured change events from `changes()`. See [ObservableList](ObservableList.md). ### Random API The standard library now includes a built-in random API plus deterministic seeded generators. ```lyng val rng = Random.seeded(1234) assert(rng.next(1..10) in 1..10) assert(rng.next('a'..<'f') in 'a'..<'f') ``` Use: - `Random.nextInt()` - `Random.nextFloat()` - `Random.next(range)` - `Random.seeded(seed)` ## Tooling and Infrastructure ### Rich Console Apps with `lyng.io.console` `lyngio` now includes a real console module for terminal applications: - TTY detection - screen clearing and cursor movement - alternate screen buffer - raw input mode - typed key and resize events ```lyng import lyng.io.console Console.enterAltScreen() Console.clear() Console.moveTo(1, 1) Console.write("Hello from Lyng console app") Console.flush() Console.leaveAltScreen() ``` The repository includes a full interactive Tetris sample built on this API. See [lyng.io.console](lyng.io.console.md). ### HTTP, WebSocket, TCP, and UDP in `lyngio` `lyngio` grew from filesystem/process support into a broader application-facing I/O library. In 1.5.x it includes: - `lyng.io.http` for HTTP/HTTPS client calls - `lyng.io.ws` for WebSocket clients - `lyng.io.net` for raw TCP/UDP transport HTTP example: ```lyng import lyng.io.http val r = Http.get("https://example.com") println(r.status) println(r.text()) ``` TCP example: ```lyng import lyng.io.net val socket = Net.tcpConnect("127.0.0.1", 4040) socket.writeUtf8("ping") socket.flush() println(socket.readLine()) socket.close() ``` WebSocket example: ```lyng import lyng.io.ws val ws = Ws.connect("wss://example.com/socket") ws.sendText("hello") println(ws.receive()) ws.close() ``` These modules are capability-gated and host-installed, keeping Lyng safe by default while making networked scripts practical when enabled. See [lyngio overview](lyngio.md), [lyng.io.db](lyng.io.db.md), [lyng.io.http](lyng.io.http.md), [lyng.io.ws](lyng.io.ws.md), and [lyng.io.net](lyng.io.net.md). ### 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 ``` ### CLI: Better Terminal Workflows The CLI is no longer just a script launcher. In the 1.5 line it also gained: - built-in formatter support - integrated `lyng.io.console` support for terminal programs - downloadable packaged distributions for easier local use This makes CLI-first scripting and console applications much more practical than in earlier releases. ### 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. ### Kotlin API: Bridge Reflection and Class Binding (Preferred Extensions) Lyng now provides a public Kotlin reflection bridge and a Lyng‑first class binding workflow. This is the **preferred** way to write Kotlin extensions and library integrations: - **Bridge resolver**: explicit handles for values, vars, and callables with predictable lookup rules. - **Class bridge binding**: declare extern surfaces in Lyng (`extern` members, or members inside `extern class/object`) and bind the implementations in Kotlin before the first instance is created. - **Extern declaration rule**: `extern class` / `extern object` are declaration-only; all members in their bodies are implicitly extern. See **Embedding Lyng** for full samples and usage details.