The Basics


Flax provides a variety of different types, including Int for integers, Bool for boolean values, Float for floating-point numbers, and String for textual data. In addition to these familiar types, Flax also employs the use of tuple types, which are essentially anonymous structs without named members, facilitating an easy way to pass pieces of related data around.

Flax is a type-safe language, which means the language helps you to be clear about the types of values your code can work with. If part of your code expects a String, type safety prevents you from passing it an Int by mistake. Type safety helps you catch and fix errors as early as possible in the development process.

Constants and Variables

Constants and variables are a way to associate a value, for example 10 or "John Doe" with a name, for instance age or name. Constants cannot be changed once they are assigned, while variables can be given a new value later on in the program.

Declaring Constants and Variables

As with any imperative language, constants and variables need to be declared before they are used. In Flax, variables are declared with the var keyword, while constants can be declared either with val or let.


let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

This code can be read as:

"Declare a new constant called maximumNumberOfLoginAttempts, and give it a value of 10. Then, declare a new variable called currentLoginAttempt, and give it an initial value of 0".

In this example, the maximum number of allowed login attempts is declared as a constant, because the maximum value never changes. The current login attempt counter is declared as a variable, because this value must be incremented after each failed login attempt.

Type Annotations

You can provide a type annotation when you declare a constant or variable, to be clear about the kind of values the constant or variable can store. Write a type annotation by placing a colon after the constant or variable name, followed by a space, followed by the name of the type to use.

This example provides a type annotation for a variable called welcomeMessage, to indicate that the variable can store String values:


var welcomeMessage: String

The colon in the declaration means "... of type ...", so the code above can be read as:

"Declare a variable called welcomeMessage that is of type String".

The phrase "of type String" means "can store any String value". Think of it as meaning "the type of thing" (or "the kind of thing") that can be stored.

The welcomeMessage variable can now be set to any string value without error:


welcomeMessage = "Hello"

Naming Variables and Constants

Constant and variable names can contain almost any character, including Unicode characters:


let π = 3.14159
let 你好 = "你好世界"

Constant and variable names cannot contain whitespace characters, mathematical symbols, arrows, private-use (or invalid) Unicode code points, or line- and box-drawing characters. Nor can they begin with a number, although numbers may be included elsewhere within the name; emojis also cannot be used.

Once you’ve declared a constant or variable of a certain type, you can’t redeclare it again with the same name, or change it to store values of a different type. Nor can you change a constant into a variable or a variable into a constant.

You can change the value of an existing variable to another value of a compatible type. In this example, the value of friendlyWelcome is changed from "Hello!" to "Bonjour!":


var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome is now "Bonjour!"

Unlike a variable, the value of a constant cannot be changed once it is set. Attempting to do so is reported as an error when your code is compiled:


let name = "John"
name = "Joe"
// compile-time error, name cannot be changed

Comments

Use comments to include non-executable text in your code, as a note or reminder to yourself. Comments are ignored by the compiler when your code is compiled.

Comments in Flax are very similar to comments in C. Single-line comments begin with two forward-slashes (//):


// this is a comment

Multiline comments start with a forward-slash followed by an asterisk (/*) and end with an asterisk followed by a forward-slash (*/):


/* this is also a comment,
but written over multiple lines */

Unlike multiline comments in C, multiline comments in Flax can be nested inside other multiline comments. You write nested comments by starting a multiline comment block and then starting a second multiline comment within the first block. The second block is then closed, followed by the first block:


/* this is the start of the first multiline comment
/* this is the second, nested multiline comment */
this is the end of the first multiline comment */

Nested multiline comments enable you to comment out large blocks of code quickly and easily, even if the code already contains multiline comments.

Semicolons

Flax does not require you to write a semicolon (;) after each statement in your code, although you can do so if you wish. However, semicolons are required if you want to write multiple separate statements on a single line:


let cat = "🐱"; printf("%s", cat)
// prints "🐱"

Integers

Integers are whole numbers with no fractional component, such as 42 and -23. Integers are either signed (positive, zero, or negative) or unsigned (positive or zero).

Flax provides signed and unsigned integers in 8, 16, 32, and 64 bit forms. These integers follow a naming convention similar to C, in that an 8-bit unsigned integer is of type Uint8, and a 32-bit signed integer is of type Int32.

Integer Bounds

You can access the minimum and maximum values of each integer type with its min and max properties:


let minValue = Uint8.min  // minValue is equal to 0, and is of type Uint8
let maxValue = Uint8.max  // maxValue is equal to 255, and is of type Uint8

Naturally, the values of these properties are of the appropriate-sized number type (such as Uint8 in the example above) and can therefore be used in expressions alongside other values of the same type.

Finally, the Int and Uint are convenience types that are available. These should be used instead of integer types with explicit sizes, when possible. Exceptions could include explicitly conveying the size of the type for some external interface, or when reading data from a foreign source.

  • On 32-bit platforms, Int and Uint are the same size as Int32 and Uint32 respectively.
  • On 64-bit platforms, Int and Uint are the same size as Int64 and Uint64 respectively.

A summary of the minimum and maximum values for each signed integer type is presented below:


let i8Min = Int8.min		// -128
let i8Max = Int8.max		// +127

let i16Min = Int16.min		// -32,768
let i16Max = Int16.max		// +32,767

let i32Min = Int32.min		// −2,147,483,648
let i32Max = Int32.max		// +2,147,483,647

let i64Min = Int64.min		// −9,223,372,036,854,775,808
let i64Max = Int64.max		// +9,223,372,036,854,775,807

Similarly, the bounds of the unsigned integer types are below:


let u8Min = Uint8.min		// 0
let u8Max = Uint8.max		// 255

let u16Min = Uint16.min		// 0
let u16Max = Uint16.max		// 65,535

let u32Min = Uint32.min		// 0
let u32Max = Uint32.max		// 4,294,967,295

let u64Min = Uint64.min		// 0
let u64Max = Uint64.max		// 18,446,744,073,709,551,615

Floating-Point Numbers

Floating-point numbers are numbers with a fractional component, such as 0.1, -273.15, and 3.1415926535897932384626.

Floating-point types can represent a much wider range of values than integer types, and can store numbers that are much larger or smaller than can be stored in an Int. Two floating-point number types are available:

  • Float64, or Double, represents a 64-bit floating-point number.
  • Float32, or Float, represents a 32-bit floating-point number.

Float32 and Float64 are interchangable with Float and Double respectively.

Note that floating-point numbers are always signed, and do not have an unsigned version.

Types and Type Inference

Types are the basis of every sane programming language. Everything has a type -- expressions, variables, and functions, to name a few. Since types play such a fundamental role in the language, the usage of these types must be made as easy as possible, and the possibility of making mistakes must be reduced as far as possible.

Type Safety

Flax is a statically-typed language; that is to say, the types of all expressions and variables are known at compile-time. As such, the compiler is able to provide type-safety, in contrast to other languages such as Python (ew) that might throw runtime errors when mismatched types are found. This ensures that good, helpful error messages are given to the programmer as early as possible, to reduce the occurrence of mistakes.

Type Inference

However, often times type safety comes at a cost of having to specify or annotate variables and expressions with explicit types. In Flax, the compiler is capable of inferring, to a basic degree, the appropriate type based on what is going on. Note that this does not in any way diminish the type-safety of Flax -- the types are all present, except they are automatically deduced by the compiler instead of needing to be manually specified.

One of the places where type inference is most useful is when assigning initial values to variables or constants. In such cases, the type of the variable is quite evident based on the right-hand-side of the assignment, and explicit types do not have to be specified:


let meaningOfLife = 42
// meaningOfLife is inferred to be of type Int

As you might have guessed, integer literals are always inferred to have type Int. Naturally, when explicit types are provided on the variable, the literal is automatically coerced into the correct type:


let someByte: Uint8 = 61
// 61 is converted to Uint8 first, then assigned to someByte

Similarly, floating point literals are always inferred to have type Double:


let someFloat = 3.1415
// someFloat is inferred to be of type Double

Numeric Literals

There are currently three main forms of numeric literals supported in Flax: integer literals, floating-point literals, and hexadecimal literals. The first two have already been introduced, but hexadecimal literals are, at their heart, just another representation for integer literals:


let someInt = 240
let someHex = 0xF0
// someInt === someHex

As can be seen, hexadecimal literals can be expressed by prefixing the number with 0x, and the proceeding digits can range from 0 to F, as opposed to 0 to simply 9 with decimal numbers.

Implicit Type Coercion

To reduce the friction experienced by the programmer, the Flax compiler allows several implicit type coercions, ie. allowing types to be converted to-and-from each other without explicit casts. These implicit coercions are similar in nature to those in C or C++.

These coercions fall under three main categories: Int to Int, Int to Float, and polymorphic coercions. The latter will be discussed in a later section.

Int to Int coercions mainly involve up-casting, or increasing the bitwidth, of an integer. For example, a function accepting an Int64 argument can be passed a variable of type Int8, because it can be safely coerced into an Int64 without loss of data.


let someSmallInt: Int8 = 50
func eatLargeInt(someInt: Int64)
{
	// ...
}

eatLargeInt(someSmallInt) // okay

Similarly, Int types can also be implicitly coerced into floating-point types without losing data. As such, the following code will compile and run without errors:


func eatDouble(db: Double)
{
	// ...
}

eatDouble(10) // okay

In this case, 10 becomes 10.0.

Booleans

Boolean values in Flax are a logical type -- they can either have a value of true, or a value of false. Naturally, true and false are constants provided by the compiler, with a type of Bool.


let pigsCanFly = false
let skyIsBlue = true

In the example above, both the constants pigsCanFly and skyIsBlue were inferred to have a type of Bool, since they were initialised with boolean literals.

Boolean types are often used to control the flow of the program, through constructs such as if-statements or while-loops. Both will be explored later.


if pigsCanFly
{
	printf("What just happened??\n")
}
else
{
	printf("All is well with the world.\n")
}

Type Aliases

Type aliases define an alternative name for an existing type. Type aliases are defined with the typealias keyword.

Type aliases are useful when you want to refer to an existing type by a name that is contextually more appropriate, such as when working with data of a specific size from an external source:


typealias AudioSample = Uint16

Once you define a type alias, you can use the alias anywhere you might use the original name:


var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0

Here, AudioSample is defined as an alias for Uint16. Because it is an alias, referencing AudioSample.min actually references Uint16.min, which provides an initial value of 0 for the maxAmplitudeFound variable.

Strong vs. Weak Type Aliases

In Flax, there are two kinds of type aliases -- weak and strong type aliases. By default, typealiases are defined as weak aliases. This means that it the aliased types are not treated as distinct types, and you can assign between them:


typealias Kilometres = Int
var orbitalPerigeeOfISS: Kilometres = 409	// assignment is transparent
let someRandomInt = 40

orbitalPerigeeOfISS = someRandomInt			// allowed

However, this might not be the most appropriate behaviour. If distiction is required, mark the typealias with the @strong attribute. When an alias is strongly typed, any attempt to assign a value that is not of the new type will result in a compile-time error. Reusing the example above:


@strong typealias Kilometres = Int
var orbitalPerigeeOfISS: Kilometres = 409	// intial assigment is transparently handled
let someRandomInt = 40

orbitalPerigeeOfISS = someRandomInt			// error: assigning type Int to var of type Kilometres

Tuples

Tuples are a convenient way to group multiple values into a single construct that is easy to manipulate and pass around to other pieces of code. The components of a tuple can have distinct or similar types, and tuples can have any number of member types.

In this example, (404, "Not Found") is a tuple that describes an HTTP status code. An HTTP status code is a special value returned by a web server whenever you request a web page. A status code of 404 Not Found is returned if you request a webpage that doesn’t exist.


let http404Error = (404, "Not Found")
// http404Error has type (Int, String)

The http404Error tuple provides a convenient way to group two related values together: the numerical error code, 404, as well as a human-readable error message, "Not Found".

Tuples are the preferred way to return two related values from a function, as opposed to traditional C-like methods such as passing pointers to functions. Currently, the constituent values in tuples can be accessed by using the dot operator and a zero-based index:


let numError = http404Error.0
let strError = http404Error.1

printf("Encountered HTTP Error %d, %s\n", numError, strError)
// prints "Encountered HTTP Error 404, Not Found".

Naturally, if you try someTuple.3 in a tuple with only 3 elements, the compiler will give an error, since there is no fourth element in the tuple.

Pointers

The final piece in the puzzle of Flax's basic types is pointers. Through pointers, Flax offers unparalleled low-level power, allowing for the manipulation of raw memory, with facilities such as pointer arithmetic, to aid in this process.

The behaviour of pointers in Flax is similar to that in C and C++. You can take the address of variables on the stack, pass pointers to functions, etc. Of course, pointers allow one to subvert the type system through type-casting, and thus should be used with care.

Pointer types are declared by adding *s after the type that is pointed to. Naturally, multiple indirections are supported. Taking the address is done with &, and dereferencing is done with #.


var someVariable = 40

var somePtr: Int* = &someVariable;
someVariable += 1

printf("%d\n", #somePtr)
// prints "41"