I may redesign types significantly in the future. This is just an early idea.
Cyen's type system is fully object-oriented.
Properties of a type
Cyen is strictly object-oriented, meaning that even primitive types take part of the type tree. Every type has a supertype (e.g.
any. In fact,
any is a supertype of all types.
Cyen allows the ability to limit which types may inherit a certain type:
- An open type may be inherited by any type.
- A restricted type may be inherited only by types defined the same namespace.
- A terminal type is prohibited from inheritance entirely.
Cyen’s types consist of two components: a definition and an implementation. Both are inherited from a supertype. The definition defines what the implementation should look like, and the definition of inherited types can be extended. The implementation defines the actual behaviour behind the definition.
Even though the object orientation pattern prohibits one type from inheriting multiple others, Cyen actually allows the inheritance of multiple types. This causes one problem, where definitions and implementations clash. If definitions clash, Cyen will prohibit you from inheriting both together.
Builtin type names
Builtin types are basic types default to Cyen. Their names are reserved type names and may not be redefined. However, they are only reserved type names: you may in fact just name your variable or function after one (e.g. it’s perfectly valid to make a variable of type
float and name it
float as well).
Cyen’s builtin types provide the basics for certain language operations. Not every
The following type names are builtin:
any: The supertype of all types
num: The supertype of all numbers
u8: An unsigned 8-bit integer
u16: An unsigned 16-bit integer
u32: An unsigned 32-bit integer
u64: An unsigned 64-bit integer
s8: A signed 8-bit integer
s16: A signed 16-bit integer
s32: A signed 32-bit integer
s64: A signed 64-bit integer
f32: A 32-bit floating point number
f64: A 64-bit floating point number
int: Alias for
uint: Alias for
float: Alias for
long: Alias for
ulong: Alias for
double: Alias for
bool: A boolean type, either
str: A character string, similar to
type: A meta type
However, the type tree involving these types is a bit more complicated, and involves some more advanced types. These advanced types are named by special syntax, to not pollute the type namespace with too much builtin names:
<type>. It is illegal to define a type with such naming, but they are valid type names for Cyen’s advanced builtin types.
Cyen’s advanced types are:
<incr>: An open type that allows
incrto be applied.
<decr>: An open type that allows
decrto be applied.
<comparable>: An open type that allows the use of comparison operators such as
<sequential>: An open type that inherits
<comparable>, allowing to be used as a range.
<signed>: An open type to allow the use of unary
<integral>: A restricted subtype of
numfor all integral types. This allows the use of bit shifting operators.
<bitlogic>: An open type that allows the use of bitwise logic (
<bitshift>: An open type that allows the use of bitwise shifts (
<decimal>: A restricted subtype of
numfor all floating point types.
<iterable>: An open type that allows iteration syntax using a
<generator>: An open type that generates a finite or infinite stream of values.
<sizable>: An open type that has a length that can be read through the length operator:
<sequence>: An open type inheriting
<iterable>that allows reading values through
<iterable>is implicitly implemented by this type.
<array>: An open type inheriting
<sequence>that allows writing values through
indexing[syntax] = value.
Composite types are types that are made up of other types. Composite types may be defined by the language syntax, or by the programmer. Cyen’s builtin composite types are arrays, sequences, tuples and optionals.
Arrays and sequences
An array is a mutable collection of zero or more elements of one specific type. For example,
[int] is an array whose elements are of type
int. The length of an array cannot change, but its elements can.
A sequence works exactly the same as an array, except one may not set its values. For example,
[!int] is a sequence whose elements are
Arrays and sequences may have their length defined in their type name:
[!int*5]. Fixed-length array types inherit from any-length array types.
One can cast an array to a sequence, but not vice versa. Casting an any-length array to a fixed-length array is possible, but will fail if the actual array length is not the required array length. A fixed-length array is per definition assignable to it’s matching any-length array type.
Arrays also inherit from the array types of its elements’ supertypes. E.g.
[int] inherits from
A tuple is an immutable collection of two or more elements whose types may be entirely independent. Tuples are always fixed-length. Their order is fixed. For example,
(int, str) is a tuple of an
int and a
It is impossible to set the values of a tuple. Getting the values of a tuple is only possible by assigning the entire tuple to a bunch of variables.
(a, b, c) = (6, 2, 1) println a ~~ 6 println b ~~ 2 println c ~~ 1 ~~ If you need one specific value only, assign others to 'void' (void, a, void) = (6, 2, 1) println a ~~ 2
An optional type is a type that may be
null. For example,
int? is either an integer value, or
null. They can be acted upon just like its internal type, but it will implicitly dereference and cause exceptions if the value is null. I.e. one may add an
int?, but if the latter is
null it will fail. Casting an optional to a non-optional type will fail if the optional is null. Non-optional types and optional types may be assigned interchangably, but assigning an optional type to a non-optional type will fail if it’s
An optional of an optional is illegal: it is not allowed to have a type like
Note that since any optional type also inherits
any, it is perfectly legal to assign
The following diagram displays inheritance between Cyen’s builtin types. The green types are open types, yellow types are restricted types that may only be inherited by other builtin types, red types are terminal types, blue types are aliases and purple types are composite types.
An important feature of a language is the definition of types. Cyen allows definition of types in several different ways.
The easiest way to define a type is to directly refer to another type. This is what is done with some numeric types for easier naming. E.g.
float is an alias for
f32. Aliases may not be written for composite types.
To define an alias, write:
alias byte: u8
A struct is a simple user-defined type that inherits
any. It has a bunch of assignable fields.
To define a struct, write:
struct StructName: field1: int field2: int field3: int = 3 ::
To instantiate a struct, simply call the struct as a function and supply all field values in order, or supply no values for the default struct values. You may then read the fields however you’d like.
loc myStruct: StructName = StructName(6, 2, 1) println myStruct.field1 ~~ 6 println myStruct.field2 ~~ 2 println myStruct.field3 ~~ 1 loc defStruct: StructName = StructName() ~~ Default struct value println myStruct.field1 ~~ 0 (default for unassigned 'int' fields) println myStruct.field2 ~~ 0 println myStruct.field3 ~~ 3
You may define a custom struct constructor:
struct StructName: field1: int field2: int field3: int = 3 :: @ int, int constructor StructName(a, b): field1 = a field2 = b field3 = a + b :: ~~~ Or alternatively write @ int, int constructor StructName(a, b) -> constructor(a, b, a + b) ~~~ loc myStruct: StructName = StructName(6, 9) println myStruct.field1 ~~ 6 println myStruct.field2 ~~ 9 println myStruct.field3 ~~ 15
A struct may, by definition, not be inherited.
A record is similar to a struct, but its values may only be assigned once. It allows its constructors to write its values (and they may even write them multiple times), but the values may not be written elsewhere. They may only be read. Just like with structs, records have a default constructor that takes all fields as arguments. However, they do not have a nullary default constructor.
Notable is that if a constructor does not assign a record field, the field’s value is forever the default value.
record RecordName: field1: int field2: int field3: int = 3 ~~ You may in fact pre-assign fields for custom constructors :: @ int, int constructor RecordName(a, b): field1 = a field2 = b field3 = a + b :: @ int constructor RecordName(a): field1 = a ~~ In this case, field2 is always 0 and field3 is always 3 :: loc myRecord: RecordName = RecordName(6, 9) println myRecord.field1 ~~ 6 println myRecord.field2 ~~ 9 println myRecord.field3 ~~ 15
Like a struct, a record may not be inherited.
A union type allows a bunch of differen types to be assigned, but only stands for one value. An union looks like a struct with fields, but internally only one field is assigned. Reading a union’s field will dereference the field value, but if that field value was not assigned it will fail.
While records and structs behave almost nearly like classes, unions behave a bit differently. A union is defined like a struct and a record:
union UnionName: field1: int field2: float field3: str ::
Unlike with structs and records, fields may not be assigned within the union. Neither may the union have constructors or methods. To instantiate a union, we instead call one of its fields as a static member of the union:
loc myUnion: UnionName = UnionName.field2(3.1415) println myUnion.field2 ~~ 3.1415 println myUnion.field1 ~~ Fails
Classes are the most flexible way to define types, as they allow inheritance. However, I wanna dedicate this to another article.
class Vec2 inherit <signed> fixed x: float fixed y: float @ float, float constructor(x, y): me.x = x me.y = y :: @ -> float func magnitude: return math.pythagoreanSolve(x, y) :: @ -> Vec2 inherit func neg: return Vec2(-x, -y) ::
Hi! I'm Samū, a furry, artist and game developer from the Netherlands. This is my blog, where I write about my projects and ideas.