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 ValueException("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.

Read a field or property without parentheses: point.x, text.isEmpty, or values.size. Writing values.size() is a TypeError, not an alternative getter syntax. Parentheses invoke a value only when its declared type is callable; for example, a property of type () -> Int may be invoked as owner.callback().

Readonly Properties

import klyn.collections

class Project:
    public readonly property name as String
    public readonly property files as ArrayList<String>

    public Project(name as String):
        this.name = name
        this.files = []

project = Project("Klyn")
# project.name = "renamed"      # Compile-time error: no writable setter.
project.files.add("main.kn")    # Allowed: readonly is shallow.

Use readonly when callers may observe a property but must not replace its value. This is a shallow restriction: if the property returns a mutable object, that object's own API remains usable. Unlike a class-level const, a readonly property is not an implicitly static constant; its getter may return instance state or compute a value on every access. Use an immutable collection or value type when deep immutability is required.

A custom readonly property may declare a get block only. Declaring a set block would contradict the public contract and is rejected during compilation.

Write-Only Properties

class SecretSink:
    private _receivedSize as ULong = 0uL

    public writeonly property secret as String:
        set:
            this._receivedSize = value.size

    public readonly property receivedSize as ULong:
        get:
            return this._receivedSize

sink = SecretSink()
sink.secret = "temporary value"
assert sink.receivedSize == 15uL
# print(sink.secret)             # Compile-time error: no readable getter.

Use writeonly when callers may submit a value but must not read it back. The modifier removes the getter from both auto-implemented and custom properties; a custom write-only property must declare a set block. A property cannot be both readonly and writeonly, and signals and events always remain readable.

class TokenReceiver:
    public writeonly property token as String

receiver = TokenReceiver()
metadata = type(receiver).getProperty("token")

assert metadata.isWriteonly
metadata.setValue(receiver, "one-use token")
# metadata.getValue(receiver)   # TypeError: the property is write-only.

Reflection preserves the same contract: Property.setValue() may write the value, while Property.getValue() raises TypeError. A write-only declaration is valid only for a property, never for a field, method, constructor, signal, event, or entity relationship. An implementation or override of a write-only property must preserve its setter.

Named Indexed Properties

A named indexed property takes one or more explicitly typed index parameters. Its declared type is the type of each returned or assigned value, not a collection type. Access calls its getter or setter directly, without constructing a proxy object.

class Grid:
    private _values as Long[] = f:[10l, 20l, 30l, 40l]

    public property cells[row as Long, column as Long] as Long:
        get:
            return this._values[row * 2l + column]
        set:
            this._values[row * 2l + column] = value

grid = Grid()
grid.cells[1, 0] = 42l
assert grid.cells[1, 0] == 42l

The setter receives the implicit parameter value. A getter-only property rejects assignments with TypeError; a setter-only property rejects reads. readonly forbids a setter and writeonly forbids a getter. Index parameters cannot be optional, variadic, ref or out, and cannot be named value. Overloads may use different index signatures. Visibility and overriding follow the usual class rules, including mandatory override when replacing a class implementation.

The index arguments are required: grid.cells alone is not a value and cannot be assigned to a variable or iterated. For example, NDArray<T>.flat[index] provides scalar access without materializing a flattened array. Use flatten() when an independent one-dimensional array is needed.

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
Accept a value without exposing it writeonly property
Observable state for binding signal property

An ordinary property is readable and writable, a readonly property is readable only, and a writeonly property is writable only. These capabilities are part of interface and inheritance contracts, not merely implementation hints.