Core syntax Collection literals

Collections and Literals

Klyn collection literals are statically typed. The literal prefix chooses the concrete collection family: fixed-size arrays, immutable lists, dynamic lists, synchronized variants, sets, or maps. The element type is inferred from the literal content unless an explicit annotation gives the compiler a stronger contract.

Importing Collections

Examples on this page that name collection interfaces or concrete collection classes assume import klyn.collections at module level. Literal syntax itself remains available without writing the class names explicitly.

import klyn.collections
Literal Prefixes

Prefixes are short on purpose. They encode the storage contract directly in the syntax: f means fixed-size, i means immutable, and s means synchronized/thread-safe. fs combines fixed-size and synchronized.

data = f:[10, 20, 30]      # Array<Int>           fixed-size contiguous array

data = fs:[10, 20, 30]     # ArraySync<Int>       fixed-size thread-safe array

data = i:[10, 20, 30]      # ImmutableList<Int>   homogeneous immutable sequence

data = [10, 20, 30]       # ArrayList<Int>       dynamic-size list

data = s:[10, 20, 30]      # ArrayListSync<Int>   dynamic-size thread-safe list

data = {10, 20, 30}       # HashSet<Int>         unique unordered values

data = s:{10, 20, 30}      # HashSetSync<Int>     thread-safe unique unordered values

mapping = {"a": 1, "b": 2} # HashMap<String, Int>

Empty literals need context. Use an explicit annotation or constructor when the element type cannot be inferred from values.

names as ArrayList<String> = []
ids as HashSet<UInt> = HashSet<UInt>()
index as HashMap<String, Int> = {}
Fixed Arrays

Array<T> is a fixed-size contiguous array. Its size is decided at construction time and never changes. Use it when the number of slots is known and indexed access is the dominant operation.

values = f:[30, 10, 20]
values.sort()
assert values[0] == 10
assert values.size == 3

ArraySync<T> has the same fixed-size contract, but runtime operations are synchronized for shared mutable access.

shared = fs:[1, 2, 3]
shared[0] = 10
assert shared is ArraySync<Int>
Dynamic Lists

ArrayList<T> is the default list literal. It grows dynamically and supports indexed reads, indexed writes, appends, sorting, reversing, and membership checks.

data = [10, 20, 30]
assert data is ArrayList<Int>
assert data[0] == 10
assert data[-1] == 30

data[1] = 2000
data.add(40)

ArrayListSync<T> exposes the same typed list API with synchronized storage. Prefer the non-synchronized version for single-threaded hot paths.

shared = s:[10, 20, 30]
shared.add(40)
assert shared is ArrayListSync<Int>

Copying from another collection now goes through constructors, not static factories.

immutable as IList<Int> = i:[1, 2, 3]
mutable = ArrayList<Int>(immutable)
syncMutable = ArrayListSync<Int>(mutable)
Immutable Lists

i:[...] creates an ImmutableList<T>: a homogeneous immutable sequence exposed through IList<T>. It is the right choice when callers should read values without mutating the collection.

values as IList<Int> = i:[10, 20, 30]
assert values[1] == 20
assert values.size == 3
Tuples

Parenthesized comma expressions build positional tuples. Tuples are fixed positional values and may be heterogeneous. They are not List<T>.

single = (1)
pair = ("age", 42)
singletonTuple = (1,)

assert single is Int
assert singletonTuple is Tuple<Int>

A trailing comma is what makes a one-element tuple. Use i:[...] for a homogeneous immutable sequence.

Sets

HashSet<T> stores unique values with average O(1) add, lookup, and remove. Duplicate literal entries are ignored by the set contract.

colors = {"red", "blue", "red"}
assert colors is HashSet<String>
assert colors.size == 2
assert "red" in colors

Use s:{...} for the synchronized set variant.

sharedColors = s:{"red", "blue"}
assert sharedColors is HashSetSync<String>

An empty brace literal {} is a map, not a set. Empty sets must therefore be constructed explicitly.

empty = HashSet<String>()
copy = HashSet<String>(["red", "blue"])
Maps

Map literals use key: value pairs and infer both key and value types. The default concrete literal type is HashMap<K, V>.

config = {
    "fullscreen": true,
    "theme": "dark"
}

config["theme"] = "light"
assert config is HashMap<String, Object>

Use constructors to copy maps or build a map from parallel key/value lists.

keys = ["a", "b", "c"]
values = [1, 2, 3]
index = HashMap<String, Int>(keys, values)
copy = HashMap<String, Int>(index)
shared = HashMapSync<String, Int>(copy)
Main Interfaces
Contract Meaning Typical concrete types
Collection<T> Iterable homogeneous collection. ArrayList, HashSet, synchronized variants.
IList<T> Read-only indexed homogeneous sequence. Array, ArraySync, ImmutableList.
List<T> Mutable indexed homogeneous sequence. ArrayList, ArrayListSync, LinkedList.
Set<T> Unique values. HashSet, HashSetSync.
MapCollection<K, V> Key/value lookup contract. Map, HashMap, synchronized variants.
Literal Type Inference
ints = [10, 20, 30]                  # ArrayList<Int>
doubles = [10, 20, 30.0]             # ArrayList<Double>
fixed = f:[10, 20, 30]                # Array<Int>
syncFixed = fs:[10, 20, 30]           # ArraySync<Int>
immutable = i:[10, 20, 30]            # ImmutableList<Int>
setValues = {10, 20, 30}             # HashSet<Int>
mixed = [10, true, "hello"]          # ArrayList<Object>
mapping = {"a": 1, "b": 2}          # HashMap<String, Int>
Unpacking
a, b, c = [10, 100, 1000]
x, y, z = (1, 2, 3)

Unpacking works with list-like values and tuples when the number of values matches the number of targets.

Comprehensions

Comprehensions build a new collection by iterating over an existing collection and evaluating an expression for each element. The result is still statically typed: the compiler infers the output element type from the generated expression, not from a dynamic runtime scan.

source = [10, 20, 30]

doubled = [x * 2 for x in source]       # ArrayList<Int>
assert doubled == [20, 40, 60]

Add an if clause when only some elements should be kept. The filter is evaluated before appending the transformed value to the result collection.

large = [x * 2 for x in source if x > 15]
assert large == [40, 60]

The prefix before the comprehension controls the concrete result type, exactly like it does for plain literals.

dynamic = [x + 1 for x in source]       # ArrayList<Int>
shared = s:[x + 1 for x in source]       # ArrayListSync<Int>
immutable = i:[x + 1 for x in source]    # ImmutableList<Int>
fixed = f:[x + 1 for x in source]        # Array<Int>
syncFixed = fs:[x + 1 for x in source]   # ArraySync<Int>

Use fixed-array comprehensions when the result is read mostly by index and should not grow after construction. Use synchronized comprehensions only when the resulting collection is shared across threads; the unsynchronized variants remain the better default for hot paths.

names = ["Ada", "Grace", "Linus"]
upperNames = f:[name.upper() for name in names]
assert upperNames is Array<String>
assert upperNames[0] == "ADA"

Comprehensions can also change the element type. In the next example, the source is a list of strings while the result is a list of integers.

words = ["Klyn", "is", "fast"]
sizes = [word.size for word in words]   # ArrayList<ULong>
assert sizes[0] == 4u

Map comprehensions use keyExpr: valueExpr. When the source is a map, iteration may bind both the key and the value with for key, value in map.

scores = {"alice": 12, "bob": 7, "chloe": 18}

bonus = {name.upper(): score + 1 for name, score in scores}
assert bonus["ALICE"] == 13

passing = {name: score for name, score in scores if score >= 10}
assert "bob" not in passing

There is no special set-comprehension syntax. Build the intermediate list with a comprehension, then pass it to a set constructor when uniqueness is the target contract.

raw = [1, 2, 2, 3, 3, 3]
uniqueDoubled = HashSet<Int>([x * 2 for x in raw])
assert uniqueDoubled.size == 3
Iteration Helpers
values = [10, 20, 30]
for value in values:
    print(value)

for key in config.keys():
    print(key)

for value in config.values():
    print(value)

for entry in config.items():
    print(entry)

Direct map iteration yields keys. Use helper views when you want values or key/value tuples.