6.10.94-stable

GreyCat Control Flow

Conditional

Classic if control flow is offered such as the following:

var x = true;
if(x){
    // x is true
} else {
    // x is false
}

Basic Loop

For loops are centric in GreyCat. Built to allow its user to efficiently navigate through time and relationships between elements, the for loops present various facets depending on the type of element they are iterating on.

The basic form of the for loop uses a C-like syntax:

fn main() {
  // from 0 to 9, by step of 1
  for (var i = 0; i < 10; i++) {}
}

Nevertheless, this form is already powerful enough to allow iterating not only on numbers but also on time/duration.

fn main() {
  // from duration 0 to duration 10s step 1s
  for (var i = 0_s; i < 10_s; i = i + 1_s) {}

  // from time 0 to time 10 step 1us.
  for (var i = 0_time; i < 10_time; i = i + 1_us) {}
}

Advanced Loop

In GreyCat, some types can be used as iterators therefore allowing for..in loops to iterate over the elements of that iterators. In this case, the loop presents several particularities and options.

  • For loops always have two iteration variables on types. They can be named freely by the developer for easy contextual reference, or replaced by _ if not used (but they are expected).
  • Some types support the use of bounds to customize the range of the iteration. Bounds can specify a sub-range of elements to iterate on, and/or the direction of the iteration (forward or backward). Bounds are specified using square brackets [, ], as in [<from>..<to>] where from and to are beginning and end indexes for the loop.
    If from > to, navigation will be backward.
  • Some types allow for skipping some elements. This can be specified by using the keyword skip <n> with n the number of elements to skip after each iteration.
  • Most types support a limit <n> specifier to limit the total number of iterations to n. The loop stops when either the limit is reached, or there is no more element.

The most complex form of for..in statement is represented by:

fn main() {
  var countries = ["luxembourg", "USA", "Germany", "France", "Netherlands"];
  for (a, b in countries[0..3] skip 2 limit 5) {}
}

This would iterate over countries on the range [0..3], skipping 2 elements between each iteration (0, 3, 6, …), and limiting the number of iterations to 5 elements (in this case, only 2 iterations happen, so the limit is not reached).

For on Arrays

For loops can be used to iterate over arrays. In this configuration, the for loop will offer two iteration variables:

  1. The first will bear the index in the array of the current iteration
  2. The second the value.

These parameters can be named freely by the developer, or replaced by _ if not used.

fn main() {
  var countries = ["Luxembourg", "France", "Germany"];

  for (index, value in countries) {}

  for (_, value in countries) {}

  for (index, _ in countries) {}
}

Specifying range

The iteration can also be controlled using a range. To this end, from and to indexes can be specified in braces after the array, with the general form Array[<from>..<to>].
When iterating forward, the to bound can be omitted to indicate that the iteration shall continue until the last element.

fn main() {
  var countries = ["Luxembourg", "France", "Germany", "USA", "Canada"];

  // from first to last, all elements
  for (idx, value in countries) {}

  // identical to previous, but with 'from' and 'to' defined
  for (idx, value in countries[0..4]) {}

  // identical to previous, but with omitted 'to'
  for (idx, value in countries[0..]) {}

  // first to last, excluding first
  for (idx, value in countries]0..4]) {}

  // first to last, excluding last
  for (idx, value in countries[0..4[) {}

  // from index 1 to index 2 (inclusive)
  // also note that ranges are not limited to literals, and also accept expressions
  var to = 1 + 1;
  for (idx, value in countries[1..to]) {}
}

Arrays can also be iterated backwards by reversing the bounds.

fn main() {
  var countries = ["Luxembourg", "France", "Germany", "USA", "Canada"];

  // from last to first, all elements
  for (idx, value in countries[4..0]) {}

  // index 2 to index 1 (inclusive)
  for (idx, value in countries[2..1]) {}
}

Bounds from and to must both, and in all cases, be strictly between 0 and Array.size()-1 When navigating backwards, the to bound cannot be omitted. If not specified, it will iterate forward.

For on Maps

For loops can iterate natively on Maps. In this configuration, the for loop will offer two iteration variables:

  1. The first will bear the key of the current element
  2. The second its value.

These parameters can be named freely by the developer, or replaced by _ if not used.

fn main() {
  var capitals = Map::new();

  capitals.set("Luxembourg", "Luxembourg");
  capitals.set("France", "Paris");
  capitals.set("Germany", "Berlin");

  for (key, value in capitals) {}

  for (_, value in capitals) {}

  for (key, _ in capitals) {}
}

For loops on Maps do not support bounds, nor skip.

For on TimeSeries

For loops can iterate natively on TimeSeries. In this configuration, the for loop will offer two iteration variables:

  1. The first will bear the time of the current element
  2. The second its value.
fn main() {
  var sensorData = nodeTime<float>::new();

  sensorData.setAt(time::new(1, DurationUnit::seconds), 0.5);
  sensorData.setAt(time::new(2, DurationUnit::seconds), 0.7);
  sensorData.setAt(time::new(3, DurationUnit::seconds), 1.2);
  sensorData.setAt(time::new(4, DurationUnit::seconds), 1.5);
  sensorData.setAt(time::new(5, DurationUnit::seconds), 2.8);

  for (time, value in sensorData) {}

  var from = time::new(1, DurationUnit::seconds);
  var to = time::new(2, DurationUnit::seconds);
  for (time, value in sensorData[from..to]) {}

  for (time, value in sensorData limit 2) {}

  for (time, value in sensorData skip 1) {}

  // bounds don't need to be limited by TimeSeries data
  to = time::new(5, DurationUnit::seconds);
  for (time, value in sensorData[from..to] skip 1 limit 2) {}
}

With TimeSeries, the bounds must be specified in time.

For to sample TimeSeries

A special form of for loops makes it possible to sample data from a TimeSeries by specifying the requested frequency.
In this configuration the for loop has five iteration variables:

  1. The time of the iteration
  2. The time of the previous closest element in the series
  3. The value of the previous closest element in the series
  4. The time of the next closest element in the series
  5. The value of the next closest element in the series

This form supports only bounds.

fn main() {
    var sensorData = nodeTime<float>::new();

    sensorData.setAt(time::new(1, DurationUnit::seconds), 0.5);
    sensorData.setAt(time::new(2, DurationUnit::seconds), 0.7);
    sensorData.setAt(time::new(3, DurationUnit::seconds), 1.2);
    sensorData.setAt(time::new(4, DurationUnit::seconds), 1.5);
    sensorData.setAt(time::new(5, DurationUnit::seconds), 2.8);

    var from = time::new(0, DurationUnit::seconds);
    var to = time::new(10, DurationUnit::seconds);
    for (itTime: time,
      prevElemTime: time,
      prevElemVal: any,
      nextElemTime: time,
      nextElemVal: any in sensorData[from..to]
      sampling 1_s) {
      //...
    }
}

The sampling does not aggregate values in case of down-sampling. You might want to consider using TimeWindow in conjunction to aggregate.

While loop

GreyCat provides C-like while, and do-while to conditionally loop.

while loops are used to execute a block of code, while a condition remains true. Because it is a while, the condition is evaluated pre-iteration.

fn main() {
  var i = 0;
  while (i < 10) {
    i++;
  }
  Assert::equals(i, 10);
}

Do While Loop

do-while loops are used to execute a block of code, while a condition remains true. In opposition to while loops, here the condition is evaluated post-iteration.

fn main() {
  var i = 0;
  do {
    i++;
  } while (i < 10);
  Assert::equals(i, 10);
}

Break

The break instruction can be used in any for loop to exit the current execution flow (i.e.: exit the loop and continue the remainder of the program.)

At

The at keywords is used in GreyCat as a control-flow to specify a contextual time of execution for the associated block statement. It is allowed to nest at statements.

use util;

fn main() {
  at (time::new(1, DurationUnit::seconds)) {
    // the execution context is 1 second after `1970-01-01T00:00:00`
    Assert::equals("1970-01-01T00:00:01Z", "${time::current().toDateUTC().toString()}");
  }

  at (3_time) {
    // the execution context is 3 microseconds after `1970-01-01T00:00:00`
    Assert::equals("1970-01-01T00:00:00.000003+00:00", "${time::current().toDateUTC().toString()}");
  }

  at (time::parse("2021-02-02T13:46:23Z")) {
    // the execution context is set to be `2021-02-02T13:46:23Z`
    Assert::equals("2021-02-02T13:46:23Z", "${time::current().toDateUTC().toString()}");
  }
}

Is

GreyCat provides a binary operator named is to test the type of a value.

use util;

fn main() {
  Assert::isTrue(3 is int);
  Assert::isTrue(3.0 is float);
  Assert::isTrue(3_s is duration);
  Assert::isTrue(3_time is time);
  Assert::isTrue("3" is String);
  //...
}

This also allows you to check your own defined types

type Foo{}

fn main(){
  var foo = Foo{};

  Assert::isTrue(foo is Foo)
}

Try-Catch

Try-catch blocks allow for tentatively recover from an Error in the execution.
These blocks are provided to catch anticipated errors, and present the user with a reason why his execution has failed, while maintaining the execution of the main program.

This could be useful to prevent for instance divisions by 0 to have your main loop exiting, in case the denominator is dynamically loaded from the data.

use io;

fn willFail() {
  throw "not implemented yet";
}

fn main() {
  try {
    willFail();
  } catch (e) {
    error("Something went wrong: ${e}");
  }
}

Nullable types

From time to time, values can be null. This however often breaks the processing flow by requesting explicit null checking, default value management, etc. Every type in GreyCat is non-null by default, to get its nullable counterpart you just need to add the character ? after the type identifier.

To indicate that a type is nullable, add the ? character after the type identifier:

// `p` can be anything including `null`
fn anything(p: any?) {}

// `p` can either be a `String` or `null`
fn stringOrNull(p: String?) {}

// `p` can only be `String` and never be `null`
fn strictString(p: String) {}

fn main() {
  anything(42);
  anything(null);
  anything("GreyCat");

  stringOrNull("GreyCat");
  stringOrNull(null);

  strictString("DataThings");
  strictString(null); // this will fail since null is not accepted as parameter, COMMENT/REMOVE THIS LINE to successfully compile.
}

Nullable Optional chaining

Calls to access attributes or function on objects can be protected by placing a ? right after the variable that can be null. In the event the protected variable is effectively null, the remaining of the expression is not executed, and the expression value is null, as presented in the following example.

use util;

fn main() {
  var city = { sensors: null };

  Assert::isNull(city.sensors?.size());
}

Nullable Arrays access

Accesses to nullable arrays can also be protected by using ?.

use util;

fn main() {
  var cities: Array? = null;
  Assert::isNull(cities?[0]);
  cities = ["Luxembourg"];
  Assert::equals(cities[0], "Luxembourg");
}

Nullable Iterations

Finally, iteration loops (for) can also be protected against null values by adding a ?

use util;

fn main() {
  var cities: Array? = null;
  var count = 0;

  for (idx, value in cities?[0..]) {
    count++;
  }

  Assert::equals(count, 0);
}

Nullish coalescing operator

If you want to provide for a default value in place of null, you can do so by appending ?? to the nullable expression and provide the default value.

fn main() {
  var city = { sensors: null };
  var size = city.sensors?.size() ?? 0;
  Assert::equals(0, size);
}

The previous code snippet is essentially syntactic sugar for:

use util;

fn main() {
  var city = { sensors: null };
  var size;
  if (city.sensors == null) {
    size = 0;
  } else {
    size = city.sensors.size();
  }
  Assert::equals(0, size);
}

Non-null Assign

use util;

fn main() {
  var a: String? = null;
  var b = "initial value";

  a ?= "the value of a";
  b ?= "this is not gonna be assigned"; // because 'b' is already not null

  Assert::equals(a, "the value of a");
  Assert::equals(b, "initial value");
}

This construction is particularly useful when working with module variables:

var items: nodeList<String>?;

@expose
fn add_item(i: String) {
  items ?= nodeList<String>::new();
  items.add(i);
}

Here, the module variable items will only be initialized once on the first call to add_item(), any subsequent calls will only append the given i to the node list.

Non-null Operator

Similar to typescripts !, In Greycat it’s !!, only effects the lsp, and has no bearing on greycat code execution. In most cases it’s not required unless the lsp is lost and you are sure the variable is not null.

use util;

fn process(data: Array<int>?) {

  //...Code

  //Im sure data is not empty but the lsp is still warning me about it
  for (_, val in data!!) {} // !! will remove the warning from the lsp
}

Static Operator (Scope resolution operator)

Useful when accessing static methods:

use util;

abstract type CountryService {
    static fn resolveCountry(nCountry: node<String>): String {
        return *nCountry;
    }
}

fn main() {
  var nCountry: node<String> = node::new("Luxembourg");

  // access the static method 'resolveCountry' from the 'CountryService' and validate its content
  Assert::equals(CountryService::resolveCountry(nCountry), "Luxembourg");
}

Dot Operator

Used to access variables inside a Greycat object:

use util;

type Country {
    name: String;
}

fn main() {
  var country: Country = Country { name: "Luxembourg" };
  Assert::equals(country.name, "Luxembourg");
}

Arrow Operator

Used to resolve and access variables inside a Greycat node:

use util;

type Country {
    name: String;
}

fn main() {
  var nCountry: node<Country> = node::new(Country { name: "Luxembourg" });
  Assert::equals(nCountry->name, "Luxembourg");
}

It is the same as resolving the node and accessing it with the . operator

use util;

type Country {
    name: String;
}

fn main() {
  var nCountry: node<Country> = node::new(Country { name: "Luxembourg" });
  Assert::equals(nCountry->name, (*nCountry).name);
}