Control Flow Expressions

Introduction to Control Flow Expressions in Flowata

In the realm of programming and scripting, control flow is the orchestrator of logic. It determines the sequence in which various parts of your code are executed, allowing you to introduce decision-making, repetition, and more structured pathways into your scripts.

Flowata's Control Flow Expressions are designed to bring this orchestration to your formulas. They empower you to dictate how your formulas respond to different conditions, how they loop through data, and how they manage the overall flow of execution. These expressions are the backbone of dynamic and responsive formulas, enabling you to create more complex and interactive applications.

Whether you're looking to implement simple conditional checks or craft intricate loops, Flowata's Control Flow Expressions provide the tools you need. They are crafted to be intuitive, mirroring the natural flow of logic we use in everyday decision-making. As you navigate this section, you'll gain insights into the various expressions available in Flowata and how to harness them effectively.

From conditional expressions that evaluate "if this, then that" scenarios, to loops that iterate over data sets, Flowata's Control Flow Expressions are designed to make your formulas both powerful and efficient.

Scope Behavior in Control Flow Expressions

Flowata's Control Flow Expressions exhibit a unique behavior when it comes to variable scope. Unlike many typical functions, these expressions inherently access and operate within the local scope of where they are invoked. This means that any variable defined or modified within a Control Flow Expression directly affects its parent scope.

To illustrate this, consider the for loop expression:

for(["apple", "banana", "cherry"], seq(
  setLocal(fruit, $value),
  print(fruit)
)); // Output: apple banana cherry

In this example, the fruit variable is defined and modified within the loop expression. However, its value is directly accessible and modified in the local scope where the loop is invoked. This behavior ensures that developers can naturally manage and manipulate data without the need for explicit data passing or return statements, streamlining the code and making it more intuitive.

This inherent scope behavior is intentional, designed to provide a seamless experience when orchestrating the flow of logic in Flowata. However, developers should be aware of this characteristic to avoid unintended side effects and ensure they understand the scope implications when using Control Flow Expressions.

Conditional Expressions

if(condition1, result1, condition2, result2, ..., elseResult)

Evaluates the conditions one by one and returns the result of the first matching condition. The elseResult is returned if none of the conditions are true.

setLocal(score, 85);
print(if(lessThan(score, 60), "Fail", lessThan(score, 80), "Pass", "Distinction")); // Output: Pass

Would be be equivalent to writing multiples like this.

setLocal(score, 85);
if(lessThan(score, 60), print("Fail"));
if(lessThan(score, 80), print("Pass"));
if(equals(score, 85), print("Distinction"));

switch(value, case1, result1, case2, result2, ..., defaultResult)

Evaluates the value and compares it with the case values in order. If a match is found, the corresponding result is returned. If no match is found, the defaultResult is returned. If no defaultResult is provided, it will default to null.

setLocal(day, "Tuesday");
print(switch(day, "Monday", "Start of the week", "Tuesday", "Middle of the week", "Friday", "End of the week", "Other day")); // Output: Middle of the week

Loop Expression

for(dataStructure, formula)

Executes the formula for each element in the provided data structure. Depending on the type of data structure, different variables are set:

The appropriate variables are then passed to the formula.

Examples:

  1. Using an array:
for(["apple", "banana", "cherry"], seq(
  setLocal(fruit, $value),
  print(fruit)
)); // Output: apple banana cherry
  1. Using a range:
for(range(6), seq(
  setLocal(number, $value),
  print(number)
)); // Output: 0 1 2 3 4 5
  1. Using a range with start, stop, and step:
for(range(2, 10, 2), seq(
  setLocal(evenNumber, $value),
  print(evenNumber)
)); // Output: 2 4 6 8
  1. Using an object:
for({ name: "John", age: 30, city: "New York" }, seq(
  setLocal(key, $key),
  setLocal(value, $value),
  print(concat(key, ": ", value))
)); // Output: name: John, age: 30, city: New York

break()

Can only be used within a loop and halts the current loop iteration.

for(range(6), seq(
  if(equals($, 3), break()),
  print($)
)); // Output: 0 1 2

continue()

Can only be used within a loop and skips the current iteration, proceeding to the next iteration.

for(range(6), seq(
  if(equals($, 3), continue()),
  print($)
)); // Output: 0 1 2 4 5

Sequential Execution

seq(formula1, formula2, ..., formulaN) or seq([formula1, formula2, ..., formulaN]) or seq({name1: formula1, name2: formula2, ...})

Runs multiple formulas as a group, executing them sequentially. Each formula is evaluated, and their results are returned in an array. The last result can be accessed using the index [-1].

Error Handling: If any formula within the sequence errors out, further execution will be stopped and an error object will be thrown.

Error Object Details:

Example:

setLocal(results, seq(
    setLocal(a, 10),
    setLocal(b, 20),
    print(add(a, b))
));

print(results[-1]); // Output: 30

If a named object is passed, the return type will also be an object with named results.

Example:

setLocal(results, seq({
    first: setLocal(a, 10),
    second: setLocal(b, 20),
    output: print(add(a, b))
}));


print(results.output); // Output: 30

Concurrent Execution

Concurrent execution allows multiple formulas to be run simultaneously, rather than one after the other as in sequential execution. This can be particularly useful when performing operations that might take some time, such as fetching data from multiple sources. By running these operations concurrently, you can potentially save time and make your formulas more efficient.

concurrent(formula1, formula2, ..., formulaN) or concurrent([formula1, formula2, ..., formulaN]) or concurrent({name1: formula1, name2: formula2, ...})

Runs multiple formulas concurrently. Each formula is evaluated, and the results are returned in an array once all formulas have completed their execution.

Error Handling: If any formula within the concurrent group errors out, further execution will be stopped and an error object will be thrown.

Error Object Details:

Example:

Imagine you have two functions, fetchUserData and fetchProductData, which retrieve user and product data respectively:

setLocal(data, concurrent(
    fetchUserData(),
    fetchProductData()
));

print(data[0]); // Output: User data
print(data[1]); // Output: Product data

If a named object is passed, the return type will also be an object with named results.

Example:

setLocal(data, concurrent({
    userData: fetchUserData(),
    productData: fetchProductData()
}));

print(data.userData); // Output: User data
print(data.productData); // Output: Product data

Timer API

setTimeout(formula, duration, scope="screen")

Schedules a formula to be executed once after a specified duration (in milliseconds). By default, the timer is scoped to the current screen, but you can specify a global scope if needed. Returns a timer ID which can be used to cancel the timer before it fires.

Example:

setLocal(timerId, setTimeout(print("5 seconds passed!"), 5000));
setLocal(globalTimerId, setTimeout(print("This is a global timer"), 5000, "global"));

clearTimeout(timerId, scope="screen")

Cancels a timer set with setTimeout, preventing the formula from being executed. By default, it clears timers scoped to the current screen, but you can specify a global scope if needed.

Example:

setLocal(timerId, setTimeout(print("This won't be printed"), 5000));
clearTimeout(timerId);

setInterval(formula, duration, scope="screen")

Schedules a formula to be executed repeatedly, with a fixed time delay between each call. By default, the interval is scoped to the current screen, but you can specify a global scope if needed. Returns a timer ID which can be used to cancel the interval.

Example:

setLocal(intervalId, setInterval(print("Another 3 seconds passed!"), 3000));

clearInterval(intervalId, scope="screen")

Cancels a repeated action which was set up using setInterval. By default, it clears intervals scoped to the current screen, but you can specify a global scope if needed.

Example:

setLocal(intervalId, setInterval(print("This will be printed only once"), 3000));
setTimeout(clearInterval(intervalId, "screen"), 3100);

getCurrentTimers(scope="screen")

Returns an array of active timer IDs for the specified scope. By default, it lists timers scoped to the current screen, but you can specify a global scope if needed. Useful for debugging or for bulk cancellation of timers.

Example:

print(getCurrentTimers()); // Output for screen-scoped timers: [1, 2, 3, ...]
print(getCurrentTimers("global")); // Output for global timers: [4, 5, 6, ...]

Error Handling

tryCatch(tryFormula, catchFormula)

Executes the tryFormula and catches any errors that occur. If an error occurs, the catchFormula is executed provided with the $error variable that contains the error information limited to the catchFormula scope.s

tryCatch(
  print(divide(10, 0)),
  print("Error occurred: " + $error.message);
); // Output: Error occurred: + whatever the message is

throw(errorObject)

Throws an error that halts the execution of the current formula unless caught by a tryCatch block or similar error-handling mechanism. The errorObject should be an object created using the error function, containing details about the error, such as a message, error type, error code, and any additional information that might be relevant.

Example:

throw(error({ message: "An error occurred", errorType: "RuntimeError", errorCode: 101 }));

This can be used in conjunction with tryCatch for error handling:

tryCatch(
  throw(error({ message: "An error occurred", errorType: "RuntimeError", errorCode: 101 })),
  print("Error caught: " + $error.message)
); // Output: Error caught: An error occurred

By using the error function to create standardized error objects, you ensure consistency in error handling and make it easier to debug and handle exceptional cases in your formulas.

Returning Values

In Flowata, the result of a formula is determined by its return value. This return value can be explicitly set using the setReturnValue function, or it can be implicitly determined by the value of the last executed statement in the formula.

setReturnValue(value)

Sets the return value from the current formula. This function explicitly sets the value to be returned when the formula completes execution.

setReturnValue("foo");

return(value)

Halts the execution of the current formula and returns the provided value. If no value is provided, the language will use the last set return value. If setReturnValue hasn't been called, the value of the last executed statement in the formula will be returned. If the formula doesn't contain any statements, a default value of null is returned.

Examples:

  1. Using return without a value after setting a return value:
setReturnValue("bar");
return(); // Returns "bar" and halts execution
  1. Using return without a value and without setting a return value:
add(5, 10);
return(); // Returns 15 (the result of the last executed statement) and halts execution
  1. Using return without any statements in the formula:
return(); // Returns null and halts execution
  1. Demonstrating the halting behavior of return:
print("This will be printed");
return("Exiting early");
print("This won't be printed"); // This line won't be executed due to the return statement above

By providing both explicit and implicit ways to determine the return value, Flowata offers flexibility in how developers structure and manage their formulas.