# The `when` statement (expression)

[//]: # (topMenu)

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
