In this page
- Conditional
- Basic Loop
- Advanced Loop
- For on Arrays
- For on Maps
- For on TimeSeries
- For to sample TimeSeries
- While loop
- Do While Loop
- Break
- At
- Is
- Try-Catch
- Nullable types
- Nullable Optional chaining
- Nullable Arrays access
- Nullable Iterations
- Nullish coalescing operator
- Non-null Assign
- Non-null Operator
- Static Operator (Scope resolution operator)
- Dot Operator
- Arrow Operator
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.
Iffrom > to
, navigation will be backward. - Some types allow for skipping some elements. This can be specified by using the keyword
skip <n>
withn
the number of elements to skip after each iteration. - Most types support a
limit <n>
specifier to limit the total number of iterations ton
. 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:
- The first will bear the index in the array of the current iteration
- 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
andto
must both, and in all cases, be strictly between 0 and Array.size()-1 When navigating backwards, theto
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:
- The first will bear the key of the current element
- 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:
- The first will bear the time of the current element
- 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:
- The time of the iteration
- The time of the previous closest element in the series
- The value of the previous closest element in the series
- The time of the next closest element in the series
- 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);
}