Dyvil v0.19.0 is out, and with it come a whole range of features and changes to the programming language and standard library. The last update was on v0.16.0, so this one will cover the following changes from v0.17.0, v0.18.0 and v0.19.0.

  • Ternary Operators
  • Java Primitive Interop
  • The applyStatement method
  • New Declaration Style and Nested Methods
  • New For Each Statement Syntax and Optional Parentheses
  • Repeat Statements
  • Brace Access Expressions
  • Implicitly Unwrapped References and Options
  • Trait Initialization

Ternary Operators

Dyvil v0.19.0 added language support for Ternary Operators, making them as efficient as if statements. However, it was possible to emulate their syntax since v0.17.0, using a new language element called the Colon Operator. The colon operator has two operands (left and right) and can be used in place of @ColonConvertible classes or tuples.

(int, String) tuple = 1 : "a"
[int:String] map = Map(tuple, 2 : "b", 3 : "c")

By defining an infix ? operator and an implementation method that takes a tuple of nullary functions, one can craft a ternary operator:

public infix T operator ?[T](boolean condition, (=> T, => T) branches)
                       = if (condition) branches._1() else branches._2()

This can be called with the colon operator like this:

int result = true ? 1 : 2
// equivalent of
int result = ?(true, (=> 1, => 2))

The main disadvantage of this is that it needs two Lambda expressions (in the desugared form). Since v0.19.0, the compiler avoids this by converting the ternary operator to an if statement, giving it the same performance as the Java ternary operator.

Java Primitive Interop

The v0.17.0 update also changed the way primitives (or, more precesily, wrappers thereof) are compiled and stored in memory. The various dyvil.lang.Int, dyvil.lang.Boolean etc. classes have been removed; the compiler now uses the java.lang.Integer, java.lang.Boolean etc. classes to wrap primitives. All primitive operators and wrapper methods are now implemented in the dyvil.lang.Primitives class (which is now also written in Dyvil). The main advantage of this change is that Dyvil wrapped primitives are now fully compatible and comparable with Java wrapper primitives.

The applyStatement method

The applyStatement is a very minor semantic addition introduced in v0.18.0. It effectively removes the not usable as a statement error in statements lists for ordinary values as long as an applyStatement method is in scope. ‘In Scope’ can mean either in the current class, or through the Implicit Closure Value in Extension Closures. A key example is the dyvil.util.AutoPrinter class, which is defined like this:

public class AutoPrinter
{
	public static void apply(AutoPrinter.() => void closure) = closure(new AutoPrinter)

	public void applyStatement(any value) = println value
}

Because of the apply method, it can be used like this:

dyvil.util.AutoPrinter {
    "Hello"          // prints 'Hello'
    "World"          // prints 'World'
    (1 + 1).toString // prints '2'
}

All items in the statement list that are not statements (i.e. not if, while, for, variables or nested statement lists) are redirected to the applyStatement method, which is available through the Implicit Closure Value ($1). The above call can be transformed to this:

dyvil.util.AutoPrinter(ap => {
    ap.applyStatement("Hello")
    ap.applyStatement("World")
    ap.applyStatement((1 + 1).toString)
})

This is of cource significantly more verbose. The applyStatement method could also be useful in other contexts like Data Structure or HTML builders.

New Declaration Style and Nested Methods

Dyvil v0.19.0 introduced, among a whole range of other syntactical changes, a new (optional) declaration style, that can be used in Class Bodies and Statement Lists. Functions and Methods can use the new func keyword; Variables, Fields and Properties are declared with the var keyword. The let keyword can be used to indicate final fields and variables. Types can now be denoted after the member name, which is known as Type Ascription.

class MyClass
{
    var i = 0 // int i = 0
    var j: String = null // String s = null
    
    var I  // int i -- property type inferred
    {
        get: return 0
        set: println newValue
        // newValue is the new implicit property setter parameter name
    }
    
    func setJ(newJ: String): void // void setJ(String newJ)
         = this.j = newJ

    public override func toString: String
    // String toString() -- no () required with func keyword
    {
        let temp = this.i * 2 // final int temp
        // let = final var
        return this.j == null ? temp : temp + j
    }
}

The new declaration style is completely optional; the C declaration style can still be used. However, it is worth noting one important difference: To declare an operator method using C-style, one has to use the operator keyword before the identifier symbol. In class contexts, the compiler will mandate a warning if this is missing, but in Statement Lists, it will parse Nested Methods without the keyword as expressions. This may cause confusion and multiple syntax errors.

class Foo
{
    infix int **(int i, int j) = Math.pow(i, j) as int
           // ^ warning, missing operator keyword
    infix long operator **(int i, int j) = ...
            // ^ valid
    
    void test()
    {
        int +++(int i, int j) = ...
        // parsed as
        int. +++(int(i), int(j)) = ...
        // (update method call)
        
        int operator +++(int i, int j) = ...
        // correct
    }
}

New For Each Statement Syntax and Optional Parentheses

In both v0.18.0 and v0.19.0, Dyvil changed the way for statements are denoted. In particular, due to the addition of Type Ascriptions, For Each statements can no longer use the colon :. Instead, a new symbol has been introduced, the Left Arrow Operator <-. For Each Statements may now look like this:

for (var i <- 1 .. 10) { }      // var keyword - i type inferred to int
for (i <- 1 .. 10) { }          // no keyword - i type inferred to int
for (var i: int <- 1 .. 10) { } // var keyword, i type explicitly int
for (int i <- 1 .. 10) { }      // C-style variable declaration, i type explicitly int

C-Style For Statements with Conditions and Updates retained their syntax, but may also use the var keyword and Type Ascriptions.

for (int i = 0; i < 10; i++) { }
for (var i = 0; i < 10; i++) { }
for (var i: int = 0; i < 10; i++) { }

Note that C-Style For Statements are deprecated, as they can almost always be replaced with For Each Statements over Ranges or While Statements.

Since v0.19.0, Dyvil also allows you to omit the parenthesis in If, For, While and Synchronized Statements as well as Catch Blocks.

if 1 == 2 { ... } else { ... }
while 0 < n { ... } else { ... }
for var i <- 1 .. n { ... }
synchronized this { ... }
try { ... } catch Exception ex { ... }

Catch Blocks may also use var, let and Type Ascriptions:

catch Exception ex { ... }
catch var ex: Exception { ... }
catch (let ex: Exception) { ... }

Repeat Statements

Repeat Statements replaced Do Statements in v0.19.0. While the do { ... } while ... construct is still available, it was deprecated in favour of the new repeat keyword, which serves the same purpose.

Brace Access Expressions

Brace Access Expressions are a small but powerful syntactic addition introduced in v0.19.0. They are created by adding a period followed by a statement list after any expression:

"abc".{
    println toUpperCase // prints 'ABC'
    println $0 // prints 'abc'
}

Within the statement list, the expression is available either via the implicit $0 variable, or as the Implicit Closure Value. Unlike Extension Closures, this feature is compiled inline, avoiding any Lambda overhead. The return value (and, through type inference, return type) is that of the last expression in the statement list.

Implicitly Unwrapped References and Options

Dyvil v0.19.0 introduces two new type kinds, implicit Option and Reference Types, and improves Option Types in general. The following expressions are now legal:

var optionalInt: int? = 10 // implicitly wrapped in a Some(10)
var implicitlyUnwrapped: int! = Some(10) // new type syntax - int!
var concreteInt = implicitlyUnwrapped + 1 // no explicit unwrapping required

var i = 0 // dummy variable
var intRef: int* = &i // ordinary reference, now using the & operator
var implicitlyUnwrapped: int^ = &i // new type syntax - int^
var implicitlyUnwrapped: int^ = i // implicitly referenced to &i
var concreteInt = implicitlyUnwrapped + 1 // no explicit dereferencing required

Trait Initialization

Trait Initialization was a rather complex feature that was added in v0.19.0. It allows you to write custom initialization code (Class and Property Initializers) in traits, which will be executed when instantiating a concrete subtype of the trait. This is best illustrated with an example:

trait MyTrait
{
    init { println "Trait init" }
}

class MyClass implements MyTrait

new MyClass // prints 'Trait Init'

For deeper class hierarchies involving many traits, the compiler requires a concept called Trait Initialization Order. It will ensure that first the super traits of a class and their super traits, and then the super traits of all super classes will be initialized, where no trait should be initialized more than once. Again, this is best demonstrated with an example:

trait TraitA { init { ... } }
trait TraitB { init { ... } }
trait TraitC { init { ... } }
trait TraitD extends TraitC { init { ... } }

class ClassA implements TraitA, TraitB
class ClassB extends ClassA implements TraitA, TraitD

The Trait Initialization Order for ClassB will be as follows:

TraitA // direct super-trait
TraitD // direct super-trait
TraitC // super-trait of TraitD
TraitB // super-trait of ClassA