Coalesce
Often absence has an obvious stand-in. With no frost reading there is no frost, so report zero; with no nickname, use the real name. A whole match for that is a lot of code for a small idea. The coalescing operator ?? says it in one line.
optional ?? fallback
PrintLine("monday: {}", FirstFrost(monday) ?? 0);
PrintLine("tuesday: {}", FirstFrost(tuesday) ?? 0);
When the optional is present you get what is inside it. When it is none you get the fallback. Either way the result is a plain int — not an int? — so the rest of the program never has to think about absence again. Monday has a frost and prints -4; Tuesday has none and prints 0.
It is a shorter way to write a match you already know:
With ?? | Means the same as |
|---|---|
FirstFrost(day) ?? 0 | match FirstFrost(day) { t? => t, none => 0 } |
The fallback must have the type of the value inside: an int? takes an int fallback.
The fallback is lazy
The fallback is evaluated only when it is needed. Forecast prints a line whenever it runs, so you can see when that is:
PrintLine(" {}", FirstFrost(monday) ?? Forecast());
PrintLine(" {}", FirstFrost(tuesday) ?? Forecast());
flowchart LR
o["FirstFrost(day)"] --> q{"Present?"}
q -- "yes" --> v["its value<br/>(Forecast never runs)"]
q -- "no" --> f["run Forecast()<br/>and use its result"]Monday has a frost, so the forecast is never asked. Tuesday has none, so it is — and (asking the forecast) appears in the output only once. That matters when the fallback is slow, or does something you can see, such as printing or reading a file.
Chaining
?? chains. The optionals are tried from left to right, the first present one wins, and the last fallback covers the case where every one is absent:
let either = FirstFrost(tuesday) ?? FirstFrost(monday) ?? 0;
Tuesday is none, Monday is -4, so either is -4, and the final 0 is never needed.
Mind the precedence
== binds more tightly than ??, so a comparison next to a fallback needs parentheses:
let mild = (FirstFrost(tuesday) ?? 0) == 0;
Without them, FirstFrost(tuesday) ?? 0 == 0 would mean FirstFrost(tuesday) ?? (0 == 0) — an int optional with a bool fallback.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// Often absence has an obvious stand-in. With no reading there is no frost,
// so report zero. A whole `match` for that is a lot of code, and `??` says it
// in one line:
//
// optional ?? fallback
//
// When the optional is present you get what is inside it. When it is `none`
// you get the fallback. Either way the result is a plain `int`, so the rest of
// the program never has to think about absence again.
import Io::PrintLine;
// The first temperature below zero, or `none` on a mild day.
func FirstFrost(readings: int[..]) -> int? {
for reading in readings {
if reading < 0 {
return reading;
}
}
return none;
}
// A slow fallback, which prints so that you can see when it runs.
func Forecast() -> int {
PrintLine(" (asking the forecast)");
return -2;
}
func Main() -> int {
let monday = [ 3, 1, -4, -1 ];
let tuesday = [ 5, 6, 4, 2 ];
PrintLine("monday: {}", FirstFrost(monday) ?? 0);
PrintLine("tuesday: {}", FirstFrost(tuesday) ?? 0);
// The fallback is lazy: it is evaluated only when it is needed. Monday has
// a frost, so the forecast is never asked. Tuesday has none, so it is.
PrintLine("monday or forecast:");
PrintLine(" {}", FirstFrost(monday) ?? Forecast());
PrintLine("tuesday or forecast:");
PrintLine(" {}", FirstFrost(tuesday) ?? Forecast());
// `??` chains. The optionals are tried from left to right, the first
// present one wins, and the last fallback covers the case where every one
// is absent.
let either = FirstFrost(tuesday) ?? FirstFrost(monday) ?? 0;
PrintLine("first frost this week: {}", either);
// Watch out: `==` binds more tightly than `??`. Without the parentheses,
// `FirstFrost(tuesday) ?? 0 == 0` would mean `FirstFrost(tuesday) ?? (0 == 0)`,
// which mixes an `int` with a `bool` and does not compile.
let mild = (FirstFrost(tuesday) ?? 0) == 0;
PrintLine("tuesday was mild: {}", mild);
return 0;
}
Run it
cd Examples/Optionals/Coalesce
rux run
monday: -4
tuesday: 0
monday or forecast:
-4
tuesday or forecast:
(asking the forecast)
-2
first frost this week: -4
tuesday was mild: true
Common mistakes
let mild = FirstFrost(tuesday) ?? 0 == 0; reads as FirstFrost(tuesday) ?? (0 == 0) and stops with error: coalescing fallback has type 'bool8', but the optional payload is 'int'. Wrap the coalescing part: (FirstFrost(tuesday) ?? 0) == 0.?? on a value that is not optional.A plain
int is never absent, so there is nothing to fall back from: count ?? 0 fails with error: operator '??' requires an optional left operand, but found 'int'. Remove the ??.The fallback runs only for
none. If it has an effect you rely on, such as a line of output, that effect happens only on the absent path, as the forecast shows.Try it yourself
- Print the first frost of a week of three days,
FirstFrost(a) ?? FirstFrost(b) ?? FirstFrost(c) ?? 0, with only the last day frosty. - Make
Forecastreturn-9and check that Monday's line still prints-4. - Declare
let nickname: char8[..]? = none;and printnickname ?? "anonymous".
Learn more
- Presence — the
matchthat??abbreviates - Coalesce exit — a fallback that leaves the function or loop instead of producing a value
- Precedence — how Rux decides which operator binds first