Skip to content


Scowl is a statically-typed language. The data type of every feature is known at compile-time. The compiler will catch type errors and prevent an invalid topology from being deployed.

Basic Types

Scowl supports the following basic types:

Type Example #1 Example #2
bool true false
int 5 -7
float 4.2 -3.0
string "hello" 'World🌎'
time Time("June 26, 2011") FromUnixSeconds(100000)


In Scowl, all types are treated as nullable. Hence, null is considered a valid value of each type. To create a null value of a specific type:

Type Null Constructor
bool Bool(null)
int Int(null)
float Float(null)
string String(null)
time Time(null)

Besides literal nulls, the primary way null values are introduced is by way of a missing or null JSON input. See Optional Inputs for details on how these values are handled.

Number Promotion

Many common transformations combine int and float values together. Scowl makes it easy to work with numbers without requiring explicit promotion.

When an int value needs to be promoted to a float for the operation to be valid, Scowl will do so automatically.

Expression Result
1 + 1.0 2.0
Maximum(3, 2.0) 3.0

Complex Types

Scowl supports array, struct, and map complex data types.


An array is a list type with elements accessed by index.

Expression Result Type
[1, 2, 3] [int]
[4.0, null, 6.0] [float]
["hello"] [string]

To access elements of an array by index, use the ArrayIndex and ArraySlice operators.

To determine the length of an array, use the Len function.


A struct is a record type with named fields.

Expression Result Type
{lat: 34.0901, lng: -118.4065} {lat: float, lng: float}
{foo: 5, bar: "six", baz: false} {foo: int, bar: string, baz: bool}
{now: EventTime()} {now: time}

To access fields of a struct by name, use the . operator:

Expression Result
geo.lng 34.0901 "six" 2022-03-17T12:20:25.645035-05:00

Attempting to access a non-existent field raises a compile-time error.


A map is a dictionary type with string-indexed elements.

Expression Result Type
map{"lat": 34.0901, "lng": -118.4065} map{string: float}
map{"now": EventTime(), "later": null} map{string: time}
map{"foo": 5, "bar": "six"} error: all elements must be the same type

Note that string is the only map key type currently supported. For future-compatibility, the key type is explicitly represented in the map type.

To access fields of a struct by name, use the [] operator:

Expression Result
geo['lng'] 34.0901
time_val["now"] 2022-03-17T12:20:25.645035-05:00

Attempting to access a missing element raises a run-time error.

Nesting Types

Complex types may be arbitrarily nested inside each other:

For example, the following expression:

  "science fiction": [
      author: "Weir, Andy",
      title: "Project Hail Mary"
      author: "Adams, Douglas",
      title: "Hitchhiker's Guide to the Galaxy"
  "historical fiction": [
      author: "Pressfield, Steven",
      title: "Gates of Fire"

has the Scowl type:

map{string: [{author: string, title: string}]

User-Defined Types

Basic and complex types may be assigned to a named identifier to be reused elsewhere in the code, e.g.:

type degrees float
type geopoint {lat: degrees, lng: degrees}
type customer {
    name: string,
    billing_geo: geopoint,
    shipping_geo: geopoint

JSON Input

The primary use case for user-defined types is code reuse in JSON Input features.

User-defined type specifications may include Optional designations and Default Values, e.g.:

type address {
    street: string,
    city: string,
    state: string?, -- state is optional
    zip: int?       -- zip is optional

billing_address := $.billing as address
shipping_address := $.shipping as address? -- shipping address is optional


Regardless of whether a type definition appears inside or outside an event scope, the type has global scope. It is available to be used across the entire Scowl program, in any event's feature definitions.

Type Inference

Scowl is able to infer the type of nearly every expression without the need to specify any explicit types.

For example:

foo := 1 + 1             -- infers foo is an int
bar := "hello" + "world" -- infers bar is a string
baz := Maximum(3, 2.0)   -- infers baz is a float

Besides null literals, the only time a user must declare a data type explicitly is in a JSON input variable. See JSON Input section.

Inspecting Type

To fetch a string-valued representation of a feature's type, you can use the Type function.