Skip to content

Values and Formal Types

In this chapter we will explore MindScript's formal type system. Formal types are those that can be programmatically declared and manipulated, and they have strict semantics, just as you would expect from any programming language. This is contrast to informal types, which have a vague, context-dependent meaning. Informal types are explained here.

MindScript's built-in types have been deliberately chosen to mirror JSON types. In fact, the type system implements a simple subset of the JSON schema standard, rendering MindScript a scripting language that is specially suited for processing JSON objects and Web applications.

Expressions and Everything-as-a-Value

Before diving into types, remember one core principle: every MindScript construct is an expression. That means whether you write a standalone literal or invoke a function, you always get back a value. For example:

> 42
42

> (40 + 2)
42

> print(42)
42

> let x = 42
42

Because all constructs, including assignments, loops, and conditionals yield values, you can chain and nest them. If you know programming languages like LISP or any of its dialects (e.g. Scheme) this should be familiar.

Dynamic Typing: Variables vs. Values

MindScript is dynamically typed, just like JavaScript or Python:

let greeting = "Hello, world!"
Here, greeting is a variable bound to the value "Hello, world!", which is of type Str (string). Variables themselves don't have a type; only values do. You can check the type of any value using the typeOf(...) function:

> typeOf(greeting)
type Str

> typeOf(42)
type Int

Unlike Python and JavaScript however, in MindScript types are runtime checked. Calling a function with incompatible argument types will lead to a runtime error.

Primitive Types

MindScript ships with the following primitive types:

Type Example Literals Description
Null null The type of the null value. It is idiomatic to use null values to mark the absence of a value or to indicate an error.
Bool true
false
The type of the two logical truth values.
Int 42
-7
Integer numbers without fractional parts.
Num 3.14
-1e3
Floating-point numbers for real values.
Str 'Hi'
"🚀Launch!"
Sequences of characters, delimited by double- or single-quotes.
Type type Str
type [Int]
The type of types. Note that type literals always feature the type constructor keyword type.

Remember, only the values/literals have a type, never the variables.

Again, you can check the types at runtime using the typeOf(...) function.

> typeOf(null)
type Null

> typeOf(true)
type Bool

> typeOf(42)
type Int

> typeOf(42.0)
type Num

> typeOf("foo")
type Str

Operators

MindScript provides familiar operators:

Level Operators
Arithmetic 1 - (unary, e.g. -7)
Arithmetic 2 * / %
Arithmetic 3 + -
Comparison == != < > <= >=
Logical 1 not
Logical 2 and
Logical 3 or
Assignment =

The operators higher in this list have precendence over those below (e.g. * before not).

No automatic casting

In addition, there is no automatic type casting. Invoking a function with wrong types or applying an operator on incompatible values yields a runtime error (e.g., "text" + 5). The mathematical operators are the exception: applying a mathematical operator on an Int and a Num promotes the Int operand to a Num.

> true and false
false

> "Hello, " + 'world!'
"Hello, world!"

> 42 * 3
126

> 42 * 3.
126.

Container Types

MindScript has two container types: arrays and objects. Unlike other programming languages, there are no tuples (i.e. arrays of fixed size).

Arrays

Arrays hold a sequence of values all of the same type. They are instantiated using square brackets:

> let path = ["start", "middle", "end"]  ## of type [Str]
["start", "middle", "end"]

> path[1]
"middle"

> path[-1]
"end"

> path[1] = "begin"
"begin"

As shown above, individual elements are accessed using the index notation. Negative indices count from the end. Note the double-hash ##: these are comments that are truly ignored by the interpreter.

You can mutate arrays in place by using functions such as push, pop, shift, and unshift. The slice function allows extracting slices.

> push(path, "bonus")
["begin", "middle", "end", "bonus"]

> pop(path) 
"bonus"

> slice(path, 0, 2)
["start", "middle"]

Objects

Objects are key–value maps. They are instantiated by enclosing a list of key-value pairs within curly brackets:

> let user1 = {"name": "Alice", "age": 30}
{"name": "Alice", "age": 30}

> let user2 = {name: Sarah, age: 28}
{"name": "Sarah", "age": 28}

> let point = {"x-coordinate": -1, "y-coordinate": 12}
{"x-coordinate": -1, "y-coordinate": 12}

Note that you can omit the quotes delimiting key names (e.g. name instead of "name") if they follow the same naming convention as variable names. If they don't, you must use quotes.

You can access the properties using the dot . notation as shown below:

> user1.age
30

> user1.age = 31
31

> point."x-coordinate"
-1

> point."z-coordinate" = 3
3

As shown before, it is valid to assign a value to a new property. However, attempting to access a non-existent property will yield a runtime error.

Alternatively, you can also use the get and set functions to retrieve or set the value of a property:

> let property = "email"
"email"

> set(user, "email", "alice@example.com")
"alice@example.com"

> get(user, property)
"alice@example.com"

This is especially useful when the property name is only known at runtime.

To check whether a property exists, use the exists function:

> exists(user, "hobbies")
false

> exists(user, "age")
true

Function Types

Functions have types too. These are indicated with an arrow (->). For instance, a function that takes and integer and produces a string from has type Int -> Str. Function types will be discussed later in the chapter about functions.