Core syntax Static typing

Typing and Constants

Klyn is statically typed from top to bottom. Type inference keeps declarations compact, but the inferred type is still a real compile-time type. This page explains the practical rules that matter when you declare variables, annotate APIs, and rely on constants.

Klyn Is Always Typed
count = 10          # inferred as Int
ratio = 0.5         # inferred as Double
name = "Klyn"       # inferred as String

count = 12          # valid
count = "twelve"    # invalid

Inference is convenient syntax, not dynamic typing. The compiler remembers the type and checks every later assignment against it.

Explicit Type Annotations
count as Int = 10
price as Double = 19.99
names as ArrayList<String> = ["Ada", "Grace"]

Use explicit annotations when the type is part of the design, when a public API must stay self-documenting, or when you want a specific numeric type instead of the default literal type.

Numeric Types
Family Types Notes
Signed integers Byte, SByte, Short, Int, Long Use Int by default unless you have a clear range or ABI reason not to.
Unsigned integers UShort, UInt, ULong Literal suffixes such as 10u make unsigned intent explicit.
Floating-point Float, Double Floating literals default to Double; use 0.5f for Float.
No silent narrowing

Klyn rejects assignments that would lose precision or violate the target type range.

Constants with const
const PI = 3.141592653589793
const APP_NAME as String = "Klyn"

def demo():
    const LIMIT as Int = 3
    print(LIMIT)

Constants may be declared at module level or locally. They cannot be reassigned, incremented, decremented, or modified through compound assignment.

Null References
message as String = null

if message is null:
    print("no message")
else:
    print(message)

Reference values may be null. Use is null and is not null for clear null checks.

Generic Types in Declarations
names as ArrayList<String> = ["Ada", "Grace"]
config as Map<String, Int> = {"port": 8080}
values as IList<Int> = ImmutableList([1, 2, 3])
pair as Tuple<String, Int> = ("age", 42)

Generic arguments are part of the declared type. Literals often infer them automatically, but explicit generic annotations are usually clearer in public APIs. In Klyn, IList<T> is the immutable homogeneous collection contract, while Tuple<...Ts> denotes a fixed positional tuple.

Inspecting a Type Quickly
value = [10, 20, 30]
metadata = type(value)
print(metadata.fullName)

The full reflection API is documented later, but type(...) is already useful when you want to verify what the compiler or runtime considers the real type of a value.

Common Mistakes
  • Expecting inference to allow later assignments of unrelated types.
  • Forgetting that const also forbids +=, ++, and similar updates.
  • Relying on implicit numeric narrowing instead of converting explicitly.
  • Leaving externally visible properties untyped because “the compiler can infer it anyway”.