diff --git a/docs/tutorial.md b/docs/tutorial.md index d65560b..f96965b 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -8,7 +8,7 @@ __Other documents to read__ maybe after this one: - [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md) - [OOP notes](OOP.md), [exception handling](exceptions_handling.md) -- [math in Lyng](math.md) +- [math in Lyng](math.md), [the `when` statement](when.md) - [time](time.md) and [parallelism](parallelism.md) - [parallelism] - multithreaded code, coroutines, etc. - Some class @@ -712,6 +712,8 @@ Or, more neat: ## When +See also: [Comprehensive guide to `when`](when.md) + It is very much like the kotlin's: fun type(x) { diff --git a/docs/when.md b/docs/when.md new file mode 100644 index 0000000..5dc65a3 --- /dev/null +++ b/docs/when.md @@ -0,0 +1,239 @@ +# The `when` statement (expression) + +Lyng provides a concise multi-branch selection with `when`, heavily inspired by Kotlin. In Lyng, `when` is an expression: it evaluates to a value. If the selected branch contains no value (e.g., it ends with `void` or calls a void function like `println`), the whole `when` expression evaluates to `void`. + +Currently, Lyng implements the "subject" form: `when(value) { ... }`. The subject-less form `when { condition -> ... }` is not implemented yet. + +## Quick examples + + val r1 = when("a") { + "a" -> "ok" + else -> "nope" + } + assertEquals("ok", r1) + + val r2 = when(5) { + 3 -> "no" + 4 -> "no" + else -> "five" + } + assertEquals("five", r2) + + val r3 = when(5) { + 3 -> "no" + 4 -> "no" + } + // no matching case and no else → `void` + assert(r3 == void) + >>> void + +## Syntax + + when(subject) { + condition1 [, condition2, ...] -> resultExpressionOrBlock + conditionN -> result + else -> fallback + } + +- Commas group multiple conditions for one branch. +- First matching branch wins; there is no fall‑through. +- `else` is optional. If omitted and nothing matches, the result is `void`. + +## Matching rules (conditions) + +Within `when(subject)`, each condition is evaluated against the already evaluated `subject`. Lyng supports: + +1) Equality match (default) +- Any expression value can be used as a condition. It matches if it is equal to `subject`. +- Equality relies on Lyng’s comparison (`compareTo(...) == 0`). For user types, implement comparison accordingly. + + when(x) { + 0 -> "zero" + "EUR" -> "currency" + } + >>> void + +2) Type checks: `is` and `!is` +- Check whether the subject is an instance of a class. +- Works with built‑in classes and user classes. + + fun typeOf(x) { + when(x) { + is Real, is Int -> "number" + is String -> "string" + else -> "other" + } + } + assertEquals("number", typeOf(5)) + assertEquals("string", typeOf("hi")) + >>> void + +3) Containment checks: `in` and `!in` +- `in container` matches if `container.contains(subject)` is true. +- `!in container` matches if `contains(subject)` is false. +- Any object that provides `contains(item)` can act as a container. + +Common containers: +- Ranges (e.g., `'a'..'z'`, `1..10`, `1..<10`, `..5`, `5..`) +- Lists, Sets, Arrays, Buffers +- Strings (character or substring containment) + +Examples: + + when('e') { + in 'a'..'c' -> "small" + in 'a'..'z' -> "letter" + else -> "other" + } + >>> "letter" + + when(5) { + in [1,2,3,4,6] -> "no" + in [7,0,9] -> "no" + else -> "ok" + } + >>> "ok" + + when(5) { + in [1,2,3,4,6] -> "no" + in [7,0,9] -> "no" + in [-1,5,11] -> "yes" + else -> "no" + } + >>> "yes" + + when(5) { + !in [1,2,3,4,6,5] -> "no" + !in [7,0,9,5] -> "no" + !in [-1,15,11] -> "ok" + else -> "no" + } + >>> "ok" + + // String containment + "foo" in "foobar" // true (substring) + 'o' in "foobar" // true (character) + >>> true + +Notes on mixed String/Char ranges: +- Prefer character ranges for characters: `'a'..'z'`. +- `"a".."z"` is a String range and may not behave as you expect with `Char` subjects. + + assert( "more" in "a".."z") + assert( 'x' !in "a".."z") // Char vs String range: often not what you want + assert( 'x' in 'a'..'z') // OK + assert( "x" !in 'a'..'z') // String in Char range: likely not intended + >>> void + +## Grouping multiple conditions with commas + +You can group values and/or `is`/`in` checks for a single result: + + fun classify(x) { + when(x) { + "42", 42 -> "answer" + is Real, is Int -> "number" + in ['@', '#', '^'] -> "punct1" + in "*&.," -> "punct2" + else -> "unknown" + } + } + assertEquals("number", classify(π/2)) + assertEquals("answer", classify(42)) + assertEquals("answer", classify("42")) + >>> void + +## Return value and blocks + +- `when` returns the value of the matched branch result expression/block. +- Branch bodies can be single expressions or blocks `{ ... }`. +- If a matched branch produces `void` (e.g., only prints), the `when` result is `void`. + + val res = when(2) { + 1 -> 10 + 2 -> { println("two"); 20 } + else -> 0 + } + assertEquals(20, res) + >>> void + +## Else branch + +- Optional but recommended when non‑exhaustive. +- If omitted and nothing matches, `when` result is `void` (see r3 in the Quick examples). +- Only one `else` is allowed. + +## Subject‑less `when` + +The Kotlin‑style subject‑less form `when { condition -> ... }` is not implemented yet in Lyng. Use `if/else` chains or structure your checks around a subject with `when(subject) { ... }`. + +## Extending `when` for your own types + +### Equality matches +- Equality checks in `when(subject)` use Lyng comparison (`compareTo` semantics under the hood). For your own Lyng classes, implement comparison appropriately so that `subject == value` works as intended. + +### `in` / `!in` containment +- Provide a `contains(item)` method on your class to participate in `in` conditions. +- Example: a custom `Box` that contains one specific item: + + class Box(val item) + fun Box.contains(x) { x == item } + + val b = Box(10) + when(10) { in b -> "hit" } + >>> "hit" + +Any built‑in collection (`List`, `Set`, `Array`), `Range`, `Buffer`, and other containers already implement `contains`. + +### Type checks (`is` / `!is`) +- Every value has a `::class` that yields its Lyng class object, e.g. `[1,2,3]::class` → `List`. +- `is ClassName` in `when` uses Lyng’s class hierarchy. Ensure your class is declared and can be referenced by name. + + []::class == List + >>> true + + fun f(x) { when(x) { is List -> "list" else -> "other" } } + assertEquals("list", f([1])) + >>> void + +## Kotlin‑backed classes (embedding) + +When embedding Lyng in Kotlin, you may expose Kotlin‑backed objects and classes. Interactions inside `when` work as follows: +- `is` checks use the Lyng class object you expose for your Kotlin type. Ensure your exposed class participates in the Lyng class hierarchy (see Embedding docs). +- `in` checks call `contains(subject)`; if your Kotlin‑backed object wants to support `in`, expose a `contains(item)` method (mapped to Lyng) or implement the corresponding Lyng container wrapper. +- Equality follows Lyng comparison rules. Ensure your Kotlin‑backed object’s Lyng adapter implements equality/compare correctly. + +For details on exposing classes/methods from Kotlin, see: [Embedding Lyng in your Kotlin project](embedding.md). + +## Gotchas and tips + +- First match wins; there is no fall‑through. Order branches carefully. +- Group related conditions with commas for readability and performance (a single branch evaluation). +- Prefer character ranges for character tests; avoid mixing `String` and `Char` ranges. +- If you rely on `in`, check that your container implements `contains(item)`. +- Remember: `when` is an expression — you can assign its result to a variable or return it from a function. + +## Additional examples + + fun label(ch) { + when(ch) { + in '0'..'9' -> "digit" + in 'a'..'z', in 'A'..'Z' -> "letter" + '$' -> "dollar" + else -> "other" + } + } + assertEquals("digit", label('3')) + assertEquals("dollar", label('$')) + >>> void + + fun normalize(x) { + when(x) { + is Int -> x + is Real -> x.round() + else -> 0 + } + } + assertEquals(12, normalize(12)) + assertEquals(3, normalize(2.6)) + >>> void diff --git a/lynglib/src/jvmTest/kotlin/BookAllocationProfileTest.kt b/lynglib/src/jvmTest/kotlin/BookAllocationProfileTest.kt index f59c05e..1de33f2 100644 --- a/lynglib/src/jvmTest/kotlin/BookAllocationProfileTest.kt +++ b/lynglib/src/jvmTest/kotlin/BookAllocationProfileTest.kt @@ -86,6 +86,7 @@ class BookAllocationProfileTest { runDocTests("../docs/Set.md") runDocTests("../docs/Map.md") runDocTests("../docs/Buffer.md") + runDocTests("../docs/when.md") // Samples folder, bookMode=true for (bt in Files.list(Paths.get("../docs/samples")).toList()) { if (bt.extension == "md") runDocTests(bt.toString(), bookMode = true)