Sequences

A sequence is a composite data type representing an ordered list of values. If you are familiar with other programming languages, you may recognize sequences as "lists", "arrays", or (in some cases) "tables".

Creating sequences

Sequences may be created using sequence expressions, comprising of an opening square bracket, a comma-separated list of elements, and a closing square bracket.

// Example: Create a sequence of numbers
let xs = [1, 2, 3];

// Exemple: Create a sequence of records
let animals = [
    { name: "Eurasian lynx", latinName: "Lynx lynx" },
    { name: "Wolverine", latinName: "Gulo gulo" }
];

A sequence of records is known, by convention, as a table.

Sequences are typically used in conjunction with queries, sequence-related functions and the concat operator.

Updating sequences of records

You can also update (or rather, create an updated copy) of a sequence of records using the with keyword.

// Example: create a sequence of cities and then create an updated copy.

let cities = [
    { name: "Stockholm", country: "Sweden" },
    { name: "Esch-sur-Alzette", country: "Luxembourg" },
];

return cities with { countryCode: upper(left(country, 3)) };

A with-expression may employ a guard expression to selectively update records:

let measurements = [
    { temperature: 7, unit: "Celsius" },
    { temperature: 53, unit: "Fahrenheit" },
]

// Example: convert all Fahrenheit measurements to Celsius
return measurements with {
    temperature: (temperature - 32) * 5/9,
    unit: "Celsius"
} when unit = "Fahrenheit";

Several update clauses may be specified in a comma-separated list. The first clause whose guard expression is true for a record will be used to update that record. The otherwise guard expression always evaluates to true and must come last.

let distances = [
    { value: 10, unit: "feet" },
    { value: 67, unit: "centimeters" }
]

// Example: convert all measurements to meters
return distances
    with { value: 0.3048 * value, unit: "meters" } when unit == "feet",
         { value: value / 100, unit: "meters" } otherwise;

Accessing columns

For sequences of records, it is possible to access each "column" by dot notation. This will return a sequence containing the value of the member on each element.

let users = [
    {id: 0, name: "Harinder"},
    {id: 1, name: "Natasha"}
];

return users.name; // returns ["Harinder", "Natasha"]

Sequence types

The name of a sequence type – as shown when you let the mouse cursor hover over a variable name in the Flow Designer – is created by appending an asterisk (*) to the type of the sequence's elements. In the example above, the variable xs contains numbers and therefore has the type number* (pronounced "sequence of numbers"). The variable animals has the type {name: text, latinName: text}* which is slightly more difficult to pronounce.

Advanced: The type of an empty sequence

The type of a sequence is inferred from its elements. What, then, is the type of an empty sequence? Since there are no elements from which to infer an element type, FlowScript will choose the type nothing* for such a sequence. In conjunction with type evolution and subtyping rules, this ensures that the type of the empty sequence behaves well when combining with other, non-empty values.

let xs = []; // Here, xs has type nothing*
set xs = xs & [1, 2]; // Now, xs has type number*
Advanced: Mixed-type sequences

What if we create a sequence where the elements have different types? In that case, the element type of the sequence will be the lowest common supertype of all the elements.

// Example: create two sequences where the elements have different types.
let mix1 = [
    { name: "Red fox", latinName: "Vulpes vulpes" },
    { name: "Yeti" } // Note: second item has no 'latinName' member
]

let mix2 = [1, 2, {name: "Panther"}];

In the example above, mix1 has the type {name: text}*, which is the most useful common type for all the elements, and mix2 has the type unknown*, signalling that the elements have nothing at all in common.

Although the type of mix1 does not indicate the presence of a member labelled latinName, the member has not been erased. You can use type narrowing to get it back:

// Example: use type narrowing to list the latin names in "mix1"
// as defined in the previous example.

let allLatinNames = [];
for animal in mix1 {
    if animal is {latinName: text} {
        set allLatinNames = allLatinNames & animal.latinName;
    }
}

// allLatinNames == ["Vulpes vulpes"]

Last updated