Update Summary: v0.17.0 to v0.19.0
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 trait
s, 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