-
Notifications
You must be signed in to change notification settings - Fork 0
Tea 4.1
In previous versions of Tea, the only values or expressions that could be used in an if statement was ones that evaluated to boolean expressions. Several modern day languages now adopt the notion of truthful expressions that almost any data type can be coerced into a Boolean by virtue of meaning. Tea also has this capability now. Truthful expressions can be used anywhere a boolean expression is expected including conditional expressions (if, else-if, etc). The following rules apply in determining truth.
Boolean
Boolean expressions are either true or false by nature
Numbers (ints, doubles, etc)
If the value is 0 or 0.0, then the truth is false. Otherwise, any positive or negative number is true
String
If the string is null or empty, then the truth is false. Otherwise, a non-empty string is true
Collections (maps, lists, etc)
If the collection is null or empty, then the truth is false. Otherwise, a non-empty collection is true
Arrays
If the array is null or empty, then the truth is false. Otherwise, a non-empty empty is true
Truthful Objects
If the object implements the Truthful interface, then the Truthful.isTrue method is invoked to determine truth. If the object is null, then the truth is false.
Objects
Any other object is false if the object is null or true in all other cases.
Per the Truthful Objects, if you want to handle custom truth in your own custom objects, just implement the Truthful interface. The Truthful interface has a single method, isTrue, that will be invoked whenever the object needs to be checked for truth.
string = “”
if (string) { ‘true’ } else { ‘false’ } // print ‘true’
array = #(‘a’, ‘b’, ‘c’)
if (array) { ‘valid’ } // print ‘valid’
status = 0
if (!status) { ‘missing’ } // print ‘missing’
Ternary operators are basically shorthand forms of if/else statements found in most programming languages. The basic form of a ternary expression is:
[expression] ? [result if true] : [result if false]
The expression should be a truthful expression that evaluates to a boolean value. For more on truthful expressions, see the Truth section. If the expression is true, then the first result is the outcome of the statement. Otherwise, the outcome is the second result. This statement could be re-written as an if/else:
if ([expression]) {
[result if true]
} else {
[result if false]
}
The results themselves are also expressions allowing complex if/else-if/else scenarios:
[expression]
? ( [expression] ? [result if true] : [result if false] )
: [result if false]
The first result is returned if both expressions are true, the second result is returned if the first expression is true and the second false, and the third result is returned if the first expression is false. In other words:
if ([expression]) {
if ([expression]) {
[result if true]
} else {
[result if false]
}
} else {
[result if false]
}
The following example is a simple use of a ternary expression to assign a value to one of two results.
value = (param == null ? ‘default’ : param)
The following example uses truthful expressions to simplify the expression.
age = (person ? person.age : 0)
The following example is a simple case for generating a striped-row effect:
foreach (row in 0..10) {
‘<li class=”’ (row % 2 == 1 ? ‘highlight’ : ‘’) ‘”>’
row
‘</li>’
}
The following example is a complex use of a ternary. If the state is valid, then either Active or Inactive is returned based on their active flag. Otherwise, if the state is invalid, then either Retired or Unavailable is returned depending on the retired flag.
Note that this usage is strongly discouraged due to its complexity and difficult in easily reading the code to understand its effect. Complex ternary expressions are usually better suited at explicit if/else statements.
value = (state == 1
? (active ? ‘Active : ‘Inactive’)
: (retired ? ‘Retired’ : ‘Unavailable’)
The elvis operator is a shorthand version of the ternary operator that uses truthful expressions. Basically, the elvis operator takes just two expressions instead of three. If the first expression is true, then it is returned; otherwise, the second expression is returned.
[expression] ?: [result]
If you are curious about the name, just flip the expression on its side No, I did not make it up. It was defined in several other modern day dynamic languages such as Ruby, Groovy, etc.
The expression must be a truthful expression. Elvis operators are very useful for setting default values, by comparing it and if false, the default is set. The elvis operator is basically shorthand for this:
[expression] ? [expression] : [result]
It helps to avoid typing the same expression twice. This is especially useful with complex statements since you only need to include the expression once such as:
object1.property1.property2.property3 ?: ‘default’
The following example controls default values.
interval = interval ?: 10
name = name ?: ‘(unknown)’
The following example can be used within other expressions as well:
‘<div style=”width: ‘ (width ?: 10) ‘px;”></div>’
The null-safe operator allows basically null-safe navigation down an object graph. This reduces the need to manually write if/else checks to avoid potential null pointer exceptions. When a null-safe operator is used and that portion of the graph returns null, then the entire expression returns null. The null-safe operator can be used on dot-notation properties and bracket-lookup properties.
variable?.property
array?[index]
Essentially, those two calls result in the following if/else equivalent.
if (variable != null) {
variable.property
}
if (array != null) {
array[index]
}
The null-safe operator simplifies the operation and is much more useful in long composition such as:
object?.collection?[0]?.information?.name
That single line accesses the name property in the deep composition of object. If any part of that graph fails, then null is returned. That single line saves at least 4 if/else checks. The null-safe operator will also attempt to preserve coercion, including primitive types. However, note that primitive coercion can be misleading and should be avoided. When the null-safe operator coerces into a primitive, it uses the default value of the JVM types (0, false, etc). Note that over-using the null-safe operator will impact performance as it requires a if/else conditional check. The null-safe operator should only be used where you are unsure whether a value will be null or not. If the value is guaranteed to be non-null, then the operator should not be used. Tea will actually optimize the code if it encounters a non-null value such as a constant to avoid the if/else check.
The null-safe operator can be mixed with the elvis operator for more flexibility of default values.
status = user?.status?.id ?: 1
The spaceship operator is used to perform comparisons between two objects. If the first object is less than the first object, then a negative non-zero value is returned. If the first object is greater than the first object, then a positive non-zero value is returned. Otherwise, if equal, then 0 is returned. Basically, it follows the contract of the Comparable.compareTo method in Java.
[expression] <=> [expression]
The determination of whether a comparison is less than or greater than is determined by the associated data types of the arguments. If the data types are not compatible or of the same hierarchy, then the expressions are converted to strings via Object.toString and the resulting strings are compared. If the values are both numeric values, then they are compared numerically. If the values are both Strings, then they are compared lexically/alphabetically via String.compareTo. If the values both share the same class hierarchy and the hierarchy implements Comparable, then they are compared via Comparable.compareTo. Otherwise, the types are converted to strings and compared. Note that nulls are checked in any scenario to avoid NPEs.
Note that currently the spaceship operator is not too useful in practice. However, future releases of Tea may introduce closures with dynamic sorting capabilities to which the spaceship operator will simplify comparisons.
The following example compares two strings to each other to determine sorting.
min = players[0].firstName
foreach (player in players) {
compare = player.firstName <=> min
if (compare < 0) {
min = player
}
}
In previous versions of Tea, the only functions or methods you could invoke were those defined in application contexts. This meant that any method you wanted to expose had to be on a context implementation. This led to reduced functionality or complex contexts. Direct function invocation now allows invoking a method that exists on any given expression or variable.
variable.doSomething();
Note that only public accessible methods may be invoked. Similar to invoke context methods, the system will proper coerce types to match the most suited signature.
The following example invokes a method to retrieve a value using a passed in parameter, while also using null-safe access to avoid NPE. This example is typical when a normal dot operation would not be possible as it relies on a single argument.
‘AVG: ‘ stat?.getComparisonAverage(opponent?.id)
One of the more recent additions to the JVM was the support for variable arguments. Rather than having to pass in an explicit array of parameters, you could just pass the parameters one by one. Tea now supports this notion on both context methods and direct method invocation.
For example, consider the following method signature:
int max(int… values)
To invoke that, you’d normally need to invoke:
max = max(#(1, 2, 3, 4, 5))
Using variable argument support, you can now simply invoke:
max = max(1, 2, 3, 4, 5)
Tea will attempt to coerce the types as necessary to match the most suitable signature.
Although a very simple and minor change, map declarations now have an additional declaration mechanism. Previous versions of Tea declared a map in one of two forms:
map = ##(key, value, key, value)
map = ##(key -> value, key -> value)
This version now allows a more standard JSON-like notation using colons:
map = ##(key : value, key : value)
This helps to improve readability as well as be more JSON compliant.