Matches in Expressions
We need a compact alternative for match statements that can be used within expression.
Other languages
Rust
Rust uses the postfix ? operator to obtain a value from
a Result or an Option and aborts the current function
in case this value does not exists: The ? operator for easier error handling.
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("username.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
C/C++/Java/C#
The C-like languages know the ternary operator b ? a : b to
distinguish the two values of a boolean expression.
Java
With JEP 305, Java introduced "pattern matching for instanceof" in OpenJDK 14 with a syntax as follows:
if (obj instanceof String s && s.length() > 5) {.. s.contains(..) ..}
While a Java enum can be checked using simple ==
and != comparisons.
Python
Python has a few alternatives to the ternary operator:
min = a if a < b else b # equivalent to ternary op a < b ? a : b
print( (b, a) [a < b] ) # selecting item from tuple
print({True: a, False: b} [a < b]) # using dictionary
Go
Go has type-switches that declare a new variable that can be used for different types:
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
Possible syntax using Operators ?, | and !
Assume we have a choice type that can be V or W or some error and we want to work with the value if it is V or W or otherwise return the error:
match x v V => print_v v w W => print_w w x * => return x // pseudo-code, Fuzion does not have return
Using Operators ?, | and !
A first step could be to
- allow
x ?as a short form ofmatch x - introduce a delimiter like
|for the cases - introduce an operator like
!to return an error immediately
So we get:
x ? v V => print_v v | w W => print_w w !
Omitting Variables
We could use the same variable name in all cases, then there it would be sufficient to have a single declaration:
x ? v V => print_v v | W => print_w v !
Using a pre-declared variable it in groovy-style.
x ? V => print_v it | W => print_w it !
Omitting Types
Next, we can make the types optional and automatically use the types in the order given in the choice type declaration:
x ? v => print_v v | w => print_w w !
Omitting Variables and Types
Combining both, we would get:
x ? v => print_v v
| => print_w v
!
or even
x ? print_v it | print_w it !
Performing calls on case elements
A common use case would be to perform a call on the elements of a choice (in particular, if there is only one element of interest, and the remaining being errors):
match x v V => v.print_v w W => w.print_w x * => return x // pseudo-code, Fuzion does not have return
This case could be simplified by allowing a dot directly following
the ? or | as follows:
x ?.print_v |.print_w !
A Single Case of Interest
If there is only one case of interest, as in an option or a choice between a value and error cases:
match x v V => v.print x Error => return x // pseudo-code, Fuzion does not have return
The expression form
x ?.print_v !
Could allow omitting the ! since it is clear that we do not handle any of the other cases.
x ?.print_v
Even simpler, if all we are interested in is the value of the first element in a choice type
res := match x
v V => v
x * => return x // pseudo-code, Fuzion does not have return
we could simplify the expression to
res := x?
Ternary Operation on bool
With these simplifications, a switch over the values of a bool
match a = b t true_ => "same" f false_ => "different"
turns into an expression very similar to the ternary ? : operator known from C or Java:
a = b ? "same" | "different"
Using | instead of : avoids conflicts
with : used as boolean implication operator (e.g., in pre and
postconditions like pre debug: a.isValid) or with :
used for inheritance or anonymous inner features.
Solution for Fuzion
Here is what I consider reasonable
- we should not omit variable names, well chosen names help make the code readable and it would become confusing when matching over value vs. errors, strings vs. integer, etc.
- using
itquickly turns into a readability disaster if you have nested expressions with conflictingits, so we should not use them. - omitting the type, however, seems reasonable for small choice types with a
clear order such as
optionorbool - error handling with
!that could even be optional for a single case seems essential for readable i/o code without having an exception mechanism
So we end up allowing these expressions:
x ? v V => v.print_v | w W => w.print_w ! x ? v => v.print_v | w => w.print_w ! x?.print_v |.print_w ! x?.print_v res := x? str := a = b ? "same" | "different"