In Argentum optional type can wrap any type, including another Optional. This theoretically allows having types like
???int, and so on.
Why is this needed? For instance, when accessing a container of
optional elements, it's necessary to distinguish between the absence of an element (going out of array bounds) and an element with a value of
nothing. Or when using nested
? operators, the result will depend on which condition didn't work.
Let's consider an example:
token = name ? findUserByName(_) ? getUserToken(_); // Operator associativity is right-to-left. So, this example can be written as: token = name ? (findUserByName(_) ? getUserToken(_));
Let's go right-to-left:
getUserTokenreturns a value of type
?String, which could be either a token or
nothingif the user doesn't have a token.
- The innermost
?operator will return
??String. It will be
nothingif there's no user,
some(nothing)if there's no token, or
some(some(string)), if there's a token.
- The left
?operator will have a type
???String, which could be
nothingif there's no name, and various forms of
some(...)indicating problems with finding the user or their token.
The resulting package of optional values can be analyzed with three ":" operators:
log(token : "No name" : "No user" : "No token)
However, this level of nesting isn't always necessary (and definitely not good for function results). That's why, in Argentum, there's a
&&-operator, which works like
?, but it requires both left and right operands to return optional values (not necessarily of the same type).
It functions quite similarly to Haskell's
maybe >>=. Its left operand returns
?T, and its right operand transforms
?X. The result of this operator is of type
It starts by executing the left operand:
- If it's
nothing, the result becomes
- Otherwise, it binds the inner value from
?Tto the variable "_" and executes the right operand, which becomes the result of the entire
Compared to the
? operator, the
&& operator has only one difference – it doesn't wrap the result of the right operand in an optional; instead, it requires it to be optional already. If you substitute
bool (optional void) for
?X in the
&&-operator, it becomes identical to the
&&-operator in most C-like languages.
&& also has the form
&&=name, to provide a name instead of "_".
Let's rewrite the previous example:
token = name && findUserByName(_) && getUserToken(_);
Now, the token has a type
?String. It has lost all information about why the token retrieval failed. It's now either "no token" or the actual token value.
Sometimes, nesting of optional wrappers is useful, and sometimes it isn't. That's why both the
&& operators find their uses.
The last of the unmentioned operators is "
||". It's similar to "
:". The only difference is that it requires the left and right sides to be of the same optional type. If the "
:" operator returns its left operand unwrapped from an optional, "
||" doesn't do that. In this aspect, it's also analogous to the
|| operator in most C-like languages.
Examples of usage:
x = a < 0 || a > 100 ? -1 : 1; myConfig = getFromFile() || getFromServer() || getDefaultConfig() : terminate();
The last line of code attempts to acquire config from different sources, all "
||"-operators in the chain return
?Config which is examined and unwrapped by the last