Flow control
Move along in a continuous stream of decisions that should be made.
Fallback
Default or nullish coalescing operations, are defined with double question marks ?? and work the following way:
<maybeNullOrError> ?? <fallbackValue>
In case the left-hand side is not null nor Error, then it's used; otherwise, fallbackValue is returned.
similarly you can use the nullish coalescing assign operator
??=
If
If statements are defined with a question mark ?, like so:
<condition> ? <response>
as there is no alternative
nullis returned if condition is not met
If-Else
If-Else statements are defined with a question mark ? followed by a colon :, like so:
<condition> ? <response> : <alternativeResponse>
To use multiline If-Else statements, wrap the response in curly brackets {...} like so:
<condition> ? {
<response>
} : {
<alternativeResponse>
}
Cases
Cases are defined with the thick arrow => and are automatically chained, creating an intuitive and streamlined syntax similar to a switch statement without the possibility of fall-through. This allows for unrelated conditions to be mixed together, ultimately resulting in a more concise "if-else-if-else" structure:
<condition1> => <responseFor1>
<condition2> => <responseFor2>
<condition3> => <responseFor3>
...
Example:
choose = (x) -> {
x == 1 => 'a'
x == 2 => 'b'
x == 3 => 'c'
}
choose(2) # 'b'
choose(8) # null
To provide a default value for your method, you can add a catch-all case using an underscore _ at the end of the sequence:
choose = (x) -> {
x == 1 => 'a'
x == 2 => 'b'
x == 3 => 'c'
_ => 'd'
}
choose(2) # 'b'
choose(8) # 'd'
For more complex scenarios, you can use blocks as outcomes for each case:
...
condition => {
# do something
'foo'
}
_ => {
# do something else
'bar'
}
...
Cases must end in a catch-all case _ or end of block. The most effective use of Cases is within methods at the bottom of the method body.
While it's possible to add nested Cases, it's best to avoid overly complex constructions. This makes code harder to follow and likely misses the point of using this feature.
It may be more appropriate to extract that logic into a separate method. FatScript encourages developers to split logic into distinct methods, helping to prevent spaghetti code.
Switch
The Switch operator is denoted by the double right arrow >> symbol, which guides the flow of control based on the value's match against a series of cases:
Syntax:
<value> >> {
<caseValue1> => <responseFor1>
<caseValue2> => <responseFor2>
...
_ => <defaultResponse>
}
Each case in the Switch block is evaluated in order until a match is found and the result of the matching case is returned:
choose = -> _ >> {
1 => 'one'
2 => 'two'
3 => 'three'
_ => 'other'
}
choose(2) # 'two'
choose(4) # 'other'
Switch cases can also involve expressions, allowing for dynamic matching:
evaluate = (x, y) -> x >> {
y + 1 => 'just above y'
y - 1 => 'just below y'
_ => 'not directly around y'
}
evaluate(5, 4) # 'just above y'
evaluate(3, 4) # 'just below y'
evaluate(7, 4) # 'not directly around y'
Tap
The Tap operator is denoted by the double left arrow << symbol, which facilitates the execution of side effects without altering the main result of an expression. It is designed to process values through specified methods (taps) that can perform side effects, while still returning the original value of the expression.
Syntax:
<result> << <tapMethod>
the right-hand side of a
tapmust always be a method
In this structure, <result> is an expression whose value is passed to <tapMethod>, which executes using <result> as its input but does not affect the final value of the expression. Instead, <tapMethod> is used purely for its side effects.
See how the tap operator can be used:
console <- fat.console
increment = x -> x + 1
result = increment(4) << console.log
In this example, increment(4) computes to 5, which is then passed to console.log and although console.log returns null, the final result assigned to result is 5.
Multiple side effects can be chained sequentially, each receiving the same initial result:
val = pure(in) << fx1 << fx2 << fx3