Property Access
Variables
A name reads a variable from the context. In an open environment (no declared variables), an unknown name reads as null; in a strict environment (the default once variables are declared) it is a check error. Variables and functions are separate namespaces: sum as a variable and sum(...) as a call do not conflict.
Members: a.b
a.b reads the own property b of a map. Reading a missing property gives null, and reading any property of null gives null, so chains of reads never fail on missing data:
user.name // => "Ada"
user.address.city // => "London"
user.profile.avatar // => null
user.profile.avatar ?? "default.png" // => "default.png"Strings and lists have exactly one property, length. Reading any other property of a string, number, boolean, list, timestamp, duration, or opaque value is a type error. Functions are called, not read: write user.name.toUpperCase(), not user.name.toUpperCase.
user.name.length // => 3
user.tags.length // => 2
user.name.first // error: TYPE_ERRORIndexing: a[k]
| Receiver | Key | Result |
|---|---|---|
| map | string | the own property, or null |
| map | number | the property named by its decimal text |
| list or string | integer | the item or character, or null when the position does not exist |
null | anything | null |
| anything | null | null |
Negative and fractional positions do not exist, so they give null. Use at(-1) or last() to count from the end.
user["name"] // => "Ada"
user[key] // => "Ada"
counts[id] // => 3
user.tags[0] // => "admin"
user.tags[5] // => null
user.tags[-1] // => null
user.tags.at(-1) // => "beta"
"hello"[1] // => "e"Optional calls: ?.
?. is accepted wherever . is and means the same for reads (which already give null on null). It matters for calls: a function call with a null receiver is an error, while ?. makes that one call return null instead.
user.nickname.toUpperCase() // error: NULL_RECEIVER
user.nickname?.toUpperCase() // => null
user.nickname?.toUpperCase() ?? user.name // => "Ada"Unlike JavaScript, ?. short-circuits only its own call, not the rest of the chain. Write ?. at every call that may receive null:
user.nickname?.trim()?.toUpperCase() // => nullWith declared types, the checker reports a missing ?. before evaluation (NULLABLE_RECEIVER). See Static Checking.
Presence: has
has(a.b) is true when a is a map with an own property b, even if its value is null (a host undefined counts as absent). It distinguishes "present but null" from "missing", which == null does not. See let, try, and has.
has(user.nickname) // => true
has(user.middleName) // => false
user.nickname == null && user.middleName == null // => trueBlocked names
__proto__, constructor, and prototype are never data. As a literal property name they are a syntax error. A computed read of one is null, as in, has(), and keys() also treat them as absent, so a lookup table keyed by user text can fall back with ??. Writing one as a computed key ({[k]: 1}) is a BLOCKED_PROPERTY error.
user.__proto__ // error: SYNTAX
user["constructor"] // => null
user["constructor"] ?? "none" // => "none"Only own properties are visible. Inherited members such as toString or hasOwnProperty are not properties of a map:
user.hasOwnProperty // => null