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. float
inherits num
), except 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 typesnum
: The supertype of all numbersu8
: An unsigned 8-bit integeru16
: An unsigned 16-bit integeru32
: An unsigned 32-bit integeru64
: An unsigned 64-bit integers8
: A signed 8-bit integers16
: A signed 16-bit integers32
: A signed 32-bit integers64
: A signed 64-bit integerf32
: A 32-bit floating point numberf64
: A 64-bit floating point numberint
: Alias fors32
uint
: Alias foru32
float
: Alias forf32
long
: Alias fors64
ulong
: Alias foru64
double
: Alias forf64
bool
: A boolean type, eithertrue
orfalse
str
: A character string, similar to[!u16]
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 allowsincr
to be applied.<decr>
: An open type that allowsdecr
to be applied.<comparable>
: An open type that allows the use of comparison operators such as<
and>
.<sequential>
: An open type that inherits<incr>
,<decr>
and<comparable>
, allowing to be used as a range.<signed>
: An open type to allow the use of unary+
and-
operators.<integral>
: A restricted subtype ofnum
for all integral types. This allows the use of bit shifting operators.<bitlogic>
: An open type that allows the use of bitwise logic (&
,|
and^
).<bitshift>
: An open type that allows the use of bitwise shifts (&
,|
and^
).<decimal>
: A restricted subtype ofnum
for all floating point types.<iterable>
: An open type that allows iteration syntax using afor
loop.<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:#val
.<sequence>
: An open type inheriting<sizable>
and<iterable>
that allows reading values throughindexing[syntax]
. The<iterable>
is implicitly implemented by this type.<array>
: An open type inheriting<sequence>
that allows writing values throughindexing[syntax] = value
.
Composite types
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 int
s.
Arrays and sequences may have their length defined in their type name: [int*5]
or [!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 [num]
.
Tuples
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 str
.
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
Optionals
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
and 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 null
.
An optional of an optional is illegal: it is not allowed to have a type like int??
.
Note that since any optional type also inherits any
, it is perfectly legal to assign null
to any
.
Diagram
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.
Type definition
An important feature of a language is the definition of types. Cyen allows definition of types in several different ways.
Alias
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
Struct
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.
Record
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.
Union
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
Class
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.