Typesystem
Typesystem
Zirric is dynamically, but strongly typed. In the future it may add type
inference, but today values can flow through any variable, field, or parameter
as long as explicit conversions and runtime checks are satisfied.
The type system stays intentionally small. Zirric supports the following classes
of types:
datatypes are the most common types. They are used to store data and can be easily created by calling the type name as a function.enumtypes are used to express that their values can be one of a group of types. In other languages they are also called union types.annotationtypes are used to annotate declarations with metadata. They can only be constructed at compile time.externtypes are built-in types that are implemented in the runtime likeFunc,StringorInt.
Data types
Data types are the most common types in Zirric and store data. In most other languages they are called classes or structs. They are defined by the data keyword followed by the type name and a list of fields.
data Person {
name
age
}
To create a new instance of a data type, simply call the type name as a function with the field values as arguments.
let person = Person("John", 42)
Fields
Fields are the building blocks of data types. They are defined by their name and optionally annotations.
To increase the expressiveness, function fields can be defined by adding a function signature after the field name.
data Greetable {
greeting(ofValue) // function field
}
Field annotations
Fields can be annotated with metadata. These annotations will be processed at compile time and can be accessed at runtime.
data Person {
@String
@json.HasKey("name")
name
@Int
@json.HasKey("age")
age
}
Enum types
Enum types are used to express that their values can be one of a group of types. In other languages they are also called union types. They are defined by the enum keyword followed by the type name and a list of types.
As convenience, you can even declare types within the enum declaration. These will still be available outside of the enum.
enum JuristicPerson {
Person
data Company {
name
corporateForm
}
}
In this example every Person and every Company is a JuristicPerson.
To discriminate union values, use switch with annotation cases:
func nameOf(juristic) {
switch juristic {
case @Person:
juristic.name
case @Company:
juristic.name + " " + juristic.corporateForm
case _:
"unknown"
}
}
Annotation types
Annotation types are used to annotate declarations with metadata. They can only be constructed at compile time.
They are defined by the annotation keyword followed by the type name and a list of fields.
module json
annotation HasKey {
name
}
To annotate a declaration, start with the @ symbol followed by the annotation type name and a list of field values.
import json
data Person {
@Type(String)
@json.HasKey("name")
name
@Int // shorthand for @Type(Int)
@json.HasKey("age")
age
}
Accessing annotations
Annotations can be accessed at runtime by using the reflect module.
import json
import reflect
let person = Person("John", 42)
let personType = reflect.typeOf(person)
let fields = reflect.fieldsOf(personType)
let nameAnnotation = reflect.annotation(fields[0], json.HasKey)
Protocol-like annotations
The prelude defines annotations such as @Countable and @Iterable to describe
capabilities of types. The compiler and tooling use these annotations to drive
loop behavior and helper utilities.
@Countable({ v -> v.length })
@Iterable(_arrayIterate)
extern type Array {
@Int length
}
Common prelude patterns
The prelude includes small data structures such as Result and Optional that
encode success/failure and nullable values without relying on implicit nulls.
Extern types
Extern types are built-in types that are implemented in the runtime like Func, String or Int. They are defined by the extern keyword followed by the type name. Optionally you can add a list of fields.
extern Int
extern String {
length
}
Each extern type behaves slightly different in terms of how it is created and accessed.
Many types like String, Int, Float and Dict will be created by literals, types like Func and Module by declarations. Any on the other hand is more like an enum containing all types.
Note: The
externkeyword is also used to declare functions provided by the compiler likeextern print(str).