Types and OOP Properties

# Attributes and Properties

Klyn distinguishes between stored implementation state and the public surface that exposes it. In practice, properties are the primary way to publish state cleanly, while attributes or backing fields remain an implementation detail.

Backing Fields

    public class Rational:
        private _numerator as Int
        private _denominator as Int

Backing fields usually carry a leading underscore and stay private to the class.

Custom Properties

    public property denominator as Int:
        get:
            return this._denominator
        set:
            if value == 0:
                throw Exception("denominator cannot be 0")
            this._denominator = value

Properties can enforce validation or compute values before they expose data.

Auto-Implemented Properties

    public class Point:
        public property x = 0
        public property y = 0

Auto-properties are ideal when you want a normal property surface but do not need custom getter or setter logic.

Readonly Properties

    public readonly property name as String
    public readonly property offset as Int

Use `readonly` when callers may observe a value but should not mutate it directly.

Observable Signal Properties

Add `signal` when a property represents observable state. Assignment keeps the same property syntax, but the compiler compares the old and new values and notifies the shared binding engine automatically when the value actually changes. Application code must not call `Binding.notify()` itself.

    import klyn.binding

    public class Counter:
        public signal property value as Int = 0

    counter = Counter()
    mirror = Counter()
    Binding.oneWay(counter::value, mirror::value)

    counter.value = 1
    assert mirror.value == 1

Use signal properties for durable state such as text, selection, progress, or a current value. Use an `event` instead for a one-shot action such as a click. The complete distinction is covered in Signals vs Events.

Property Access Syntax

    r = Rational(1, 3)
    print(r.numerator)
    r.denominator = 5

Properties use ordinary member access syntax. Callers do not need to know whether the property is auto-implemented or backed by custom getter and setter blocks.

When to Use Each Form

Need | Preferred form
--- | ---
Internal implementation state only | Private backing field
Simple public state | Auto-implemented property
Validation or derived logic | Custom property with `get` and/or `set`
Read-only public surface | `readonly property`
Observable state for binding | `signal property`