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.