Construction
Every type has exactly one constructor, generated from its fields in declaration order, and it never contains logic - validation and parsing are static factory functions instead.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
A type cannot declare a constructor of its own. The one it gets is built from its fields, in the order they are
declared, and answers a value or nothing else.
Example#
type Email with TryFrom<String, String> {
private value: String
static fn tryFrom(text: String): Result<Email, String> {
if !text.contains("@") {
return Fail "'{text}' is not an email address"
}
Ok Self(text)
}
fn address(): String {
value
}
}
const email = Email.tryFrom "info@example.test"
match email {
Ok(address) => print "Parsed: {address.address()}"
Fail(reason) => print "Rejected: {reason}"
}Syntax#
<Type>(<field>, ...) // Positional, in declaration order
<Type>(<name>: <value>, ...) // Labelled, any order
Self(<field>, ...) // The constructor, from inside the typeRules#
The constructor takes every field, in declaration order, and nothing else. It cannot be written by hand, and it never contains logic: there is no body to put an
ifor aprintinto.type Account { owner: String var nickname: String = "" } const first = Account "Ada" const second = Account owner: "Alan", nickname: "al" print "{first} {second}"A field with a default value can be left out of the call, positionally or by label.
Account "Ada"leavesnicknameat"". A field without a default is required, whatever its type: an optional field isNoneonly when it is written= None, because "may be absent" and "may be left out of the call" are two different promises and only the declaration can make the second one.Kompiliert nichttype Person { name: String nickname: String? } const wrong = Person("Ada") // error: `Person` has no value for the field `nickname`The message says both ways out: write the absence at the call (
Person("Ada", nickname: None)), or give the field a default and leave it out everywhere (nickname: String? = None).type Person { name: String nickname: String? = None } print Person("Ada") print Person("Alan", nickname: "al")Positional arguments fill the fields from the left; a field with a default in the middle is passed over by naming the one behind it.
Kompiliert nichttype Server { host: String port: Int = 80 retries: Int } const wrong = Server("a", 3) // error: `Server` has no value for the field `retries`type Server { host: String port: Int = 80 retries: Int } print Server("a", retries: 3)A field default is a constant. It is a literal,
None, a collection literal of constants, a constructor or a case applied to constants, a named constant - a top-levelconst, astaticvalue of a type - or an operator on those: what the initializer of a module's top-levelconstmay be. A closure that captures nothing is one too, because the constructor only stores it. A call is not, astatic fnincluded:List.filled 16, None,Set.of()andlimit.max(1)run code, and the constructor has no body to run it in. A value that is computed comes from astatic fnfactory, and a collection starts from its empty literal -[]for a list, aSetor a queue,[:]for a map. A parameter default has no such rule and may be any expression, a call included (Default values): it runs at the call, which has a caller to run it in.Kompiliert nichttype Buffer { var slots: List<Int?> = List.filled 16, None } // error: A field default is a constant: `List.filled` is a call - compute it in a `static fn`, or start from `[]`type Buffer { var slots: List<Int?> = [] var seen: Set<String> = [] var counts: Map<String, Int> = [:] static fn withSlots(count: Int): Buffer { Buffer slots: List.filled(count, None) } } print Buffer.withSlots(3)A default never makes an object: a
shared typehas an identity, and every value needs one of its own, so that is a factory too. A default is read in a scope withoutselfand without the other fields, so the order the fields are declared in is not observable from one. And a construction cannot fail, so a?in a default is refused - a value that has to be checked first comes from a factory that answers aResult.Kompiliert nichtfn parsed(text: String): Int? { None } type Probe { count: Int = parsed("1")? } // error: `?` cannot stand in a field default: building a value never failsThe constructor is usable from outside the type if and only if every
privatefield has a default. Aprivatefield with a default can be left out, and leaving it out is the only way outside code ever reaches this constructor.type Session { token: String private hits: Int = 0 } const session = Session token: "abc" print session.tokenNaming a
privatefield is refused wherever it is named, including by position in the constructor. Reaching the constructor and naming one of itsprivatefields are two different permissions - the first rule is about which fields have to be left out, this one is about what happens when they are not.privatereaches as far as the file that declares the type (Fields, rule 5), so the refusal is what another file gets -ArrayQueueofstd/collectionskeeps its slots to itself:Kompiliert nichtuse ArrayQueue from "std/collections" const wrong = ArrayQueue<Int>(head: 3) // error: `head` is private to `ArrayQueue<Int64>`, so it cannot be passed from hereInside the type,
Self(...)is the constructor and every field can be passed,privateones included - that is howEmail.tryFrombuilds the value nothing outside can.Everything that is not the constructor is a static factory function: a member of the type declared
static.Email.tryFromanswers aResultbecause a constructor cannot fail; a factory can, and a factory is where a value that is computed - a default the field cannot hold - comes from.
What this is not#
A constructor is not a place for validation, parsing or conversion. Those need to fail, and a constructor in a
language without exceptions cannot. They are static factory functions that return a Result, next to the
constructor rather than inside it.
type Percent {
private value: Int
static fn tryFrom(value: Int): Result<Percent, String> {
if value < 0 || value > 100 {
return Fail "{value} is not between 0 and 100"
}
Self value
}
}From another file the constructor of such a type is not reachable at all, and the factory is the only way in:
use Path from "std/path"
const wrong = Path(None, [])
// error: `Path` cannot be constructed here: `rootValue` is private and has no defaultRelated#
- Fields -
var,privateandprotected var, the modifiers a constructor's fields carry. - Maps and sets -
[]as the emptySet, the constant a field of one starts from. - Declaring a type - fields, methods and what else is generated.
- Copy and equality -
copy, which has the same shape as the constructor with every field optional. - Result - what a factory function answers when construction can fail.