Announcing Jactl 2.0.0
Jactl 2.0.0 is a major release that fixes a few bugs and adds some new language features. The biggest new feature is powerful pattern matching with destructuring via a new switch expression.
Enhancements
Pattern Matching with Destructuring (#46)
Jactl now offers a switch
expression similar to Java but with more powerful pattern matching with destructuring.
Unlike Java, there is no case
keyword used so a simple switch
expression would look like this:
switch (x) {
1,3,5,7,9 -> println 'odd'
2,4,6,8,10 -> println 'even'
default -> println 'unknown'
}
Since switch
is actually an expression and returns a value, you could also write it like this:
println switch (x) {
1,3,5,7,9 -> 'odd'
2,4,6,8,10 -> 'even'
default -> 'unknown'
}
As well as matching simple literal values such as numbers and Strings you can match on a type pattern:
switch (x) {
String -> 'string'
int,long -> 'whole number'
double,Decimal -> 'decimal number'
}
Any type of pattern can be followed by an if
clause to specify additional conditions that must be met for the
match.
The it
variable is bound to the value being switched on and can be referred to within the if
and within the
result:
switch (str.substring(3)) {
/content=/r if it.size() < 20 -> 'short content'
/name="(.*)".*type="(.*)"/r -> "name is $1,type is $2" // Regex match with capture variables
}
List and Map patterns can also be matched against and _
can be used to match any value and *
can be used to
match any number of values:
switch (x) {
[1,2,3] -> 'matched'
[int,String,long] -> 'matched' // List with an int, a String, and a long
[_,_] -> 'matched' // List of size 2
[1,*,4] -> 'matched' // List where 1st element is 1 and last is 4 (size is at least 2)
[name:_,*] -> 'matched' // Map that has an entry of 'name' of any value
}
Destructuring is supported where a binding variable can be supplied that will bind to that part of the structure if the overall pattern matches:
switch (x) {
[a,b,c] -> a+b+c // Match 3 element list and return sum of the elements
}
Binding variables can occur multiple times and for the pattern to match the values of the binding variable must be the same at all locations. They can also be specified with a type to match on a specific type at that location:
switch (x) {
[a,_,a] -> 'matched' // First and last elements must be the same
[_,[int a,a]] -> 'matched' // Second element is a pair of ints of same value
}
See blog post on Pattern Matching and Destructuring for more details and examples.
Support for Named Constants (#51)
A new keyword const
allows you to create named constants:
const Decimal PI = 3.1415926536
def area = PI * radius.sqr()
The type is optional and will be inferred from the type of constant expression of the initialiser:
const PI = 3.1415926536
const RADIUS = 100
const AREA = PI * RADIUS * RADIUS
Import Static
import
statements can now selectively import static methods and constants from other classes into the current
script/class:
import static utils.Math.PI
import static utils.Math.circleArea
def area = circleArea(100)
def circumference = 2 * PI * 100
as
can be used to give another name to the item being imported:
import static utils.Math.circleArea as area
def x = area(100)
The use of *
to import all static functions and constants from a class is also supported:
import static utils.Math.*
do/until
Loops (#54)
Jactl does not support do/while
loops (see FAQ) but now has
support for do/until
loops that loop until a condition is met:
int countTokens = 0;
do {
countTokens++
} until (nextToken().isEof())
New groupBy()
Method for Lists (#55)
Jactl has added a groupBy()
method for lists that, given a closure that returns the key
for a given list element, will group elements with the same key and return a Map of lists
where the lists contain all elements with the same key.
For example, to group words of the same length together:
['list', 'of', 'words', 'with', 'different', 'sizes'].groupBy{ it.size().toString() }
Result will be:
['4':['list', 'with'], '2':['of'], '5':['words', 'sizes'], '9':['different']]
New transpose()
Method for Lists (#56)
transpose()
works as a matrix transpose where rows are transposed into columns (and columns
into rows).
It operates on a list of lists:
[ ['a',1], ['b',2], ['c',3] ].transpose()
Result is:
[['a', 'b', 'c'], [1, 2, 3]]
If the input lists are not all the same size, null
will be used to fill any missing values.
Breaking Changes
New Keywords
Jactl has introduced these new keywords in version 2.0.0:
const
until
switch
default
do
Blocks Return Value of Last Expression/Statement (#53)
To make the language more consistent, do
blocks now work like functions/closures and return the
value of the last expression/statement rather than always returning true
.
Byte Values Are Always Positive
Previously there were inconsitencies with how byte values were treated. Sometimes negative byte values were allowed, and sometimes they were automatically converted to the equivalent postive value.
To make the language more consistent, byte values are always treated as positive values between
0
and 255
.
If a negative value is supplied it will be automatically converted into the appropriate postive
value.
For example, (byte)-1
will be converted to 255
.
Use of ++
and --
on Constant Values is Now Invalid
Previously, expressions such as ++1
was allowed (and would return 2
in this case).
Now, this will cause a compile-time error.
This change was needed as part of the support for named constants to make sure that code does not try to modify a constant.