Update Summary: v0.20.0 and v0.21.0
It’s time for the next update on Dyvil, this time concluding the updates v0.20.0 and v0.21.0. Both updates introduce some fairly radical syntactic changes. This is mainly due to the fact that I wanted to steer it away from Scala and towards Swift. This transition is clearly visible in this months change list:
- New Generic and Wildcard Syntax -
List[_ <: String]
becomesList<+String>
- New Function Type syntax -
int => int
becomesint -> int
- New Closure Syntax
- New Operator Parsing and custom Ternary Operators
- Enhanced Method Call Style and Varargs
- Field Properties
- Improved Reference Syntax and Property References
New Generic and Wildcard Syntax
This is a rather large set of changes to the overall look and feel of the programming language. After a really, really long phase of debate, it has finally been implemented in v0.21.0. The thing that made this complicated was not the implementation, but the design and resolution of ambiguities, but they are worthy of an entire blog post. In short, the syntax for generic types and generic method calls changed as shown below:
// Declarations
class List[T <: Comparable[T]] becomes class List<T <: Comparable<T>>
type MyList[T] = List[T] becomes type MyList<T> = List<T>
func myFunc[T](List[T] list) becomes func myFunc<T>(List<T> list)
// Types
List[String] becomes List<String>
List[Map[int, String]] becomes List<Map<int, String>>
List[_ <: Object] becomes List<+Object>
List[_ >: Object] becomes List<-Object>
List[_] becomes List<_>
// Method Calls
List.[int] becomes List<int> // apply
List.[int](1, 2, 3) becomes List<int>(1, 2, 3) // apply args
List.[int][1, 2, 3] becomes List<int>[1, 2, 3] // subscript
List.[int]fromArray(array) becomes List.fromArray<int>(array)
New Function Type Syntax
This was minor syntactic change in v0.21.0 that originated in the design decision to separate types and return values for Lambda Expressions. What they were lacking was a way to denote the return type explicitly:
var f = (int i) => i + 1 // inferred as int -> int, but want int -> long
var g = (int i) -> long => i + 1 // inferred as int -> long
This change was also reflected for Lambda Types, which now use a Single Arrow instead of a Double one:
=> int becomes -> int
int => int becomes int -> int
(int, int) => int becomes (int, int) -> int
New Closure Syntax
Dyvil v0.20.0 introduced a new lightweight way to declare Lambda Expressions with multiple statements in their return body. Statement Lists may now define a ‘Lambda Header’ consisting of a parameter list, an optional lambda return type and the Double Arrow operator, followed by the body:
=> { foo(); bar() } becomes { => foo(); bar() }
i => { foo(i); bar() } becomes { i => foo(i); bar() }
(int i, int j) => { foo(i); bar(j) } becomes { int i, int j => foo(i); bar(j) }
New Operator Parsing and custom Ternary Operators
The v0.20.0 update also introduced new operator semantics that partially rely on whitespace to disambiguate certain expressions:
int i = 0
println(i++ + 1) // parsed as (i++) + 1
println(i ++ + 1) // parsed as i ++ (+ 1)
While this may seem counter-intuitive at first, it was fine-tuned to be as reliable and unsurpring as possible. The main advantage of this is the fact that Dyvil files can now be parsed without having to look up custom operators. This is a relief for both the compiler and the human who has to read your code.
Custom Ternary Operators can be defined like ordinary custom Operators in Headers, but consist of two Identifier parts. They may have a precedence value, but can’t define an associativity.
infix operator ? : { precedence 120 }
The standard library currently only defines the above ternary operator, also called the Ternary Conditional Operator. With the addition of Ternary Operators to the operator resolution system, the v0.20.0 update also removes all ambiguities and weird errors related to these.
Enhanced Method Call Style and Varargs
Since v0.20.0, Method Calls in Dyvil can now mix named with unnamed Arguments. This is especially interesting with the addition of multiple Varargs Parameters in the same update. In this example, we define a method that has two varargs parameters, and call it using name d arguments.
func foo(String... strings, int... ints) = println(string.toString + " " + ints.toString)
foo(strings: "a", "b", "c", ints: 1, 2, 3) // prints [ a, b, c ] [ 1, 2, 3 ]
Without named arguments, we would need to resort to explicit Array Literals. However, Array Literals and other expressions that return Arrays can no longer be used directly in place of Varargs parameters. The v0.20.0 update introduced the Varargs Expansion Operator, which allows you to do just that with a little bit of extra syntax:
foo([ "a", "b", "c" ]..., [ 1, 2, 3 ]...)
A third option is to omit the strings
tag. In this case, all arguments up to the next label are used for the strings
parameter:
foo("a", "b", "c", ints: 1, 2, 3)
It’s up to you to decide which option is clearer.
Field Properties
Another feature introduced in v0.20.0 are Field Properties. They provide a very terse way to create field getters and setters without Property boilerplate and name mangling. Property Definitions can occur after any Field or Class Parameter:
class Person(String name {
get //: return this.name
set //: this.name = newValue
})
{
int age = _
{
get // : return this.age
set {
println "set \(newValue)"
this.age = newValue
}
}
}
var person = new Person("Peter")
println person.name // calls the `name` property method
person.age = 20 // prints 'set 20', then sets the field
Improved Reference Syntax and Property References
Dyvil v0.20.0 changed the way References work syntactically. To create a reference to a variable or field, you can now
use the &
prefix operator. De-referencing and assignment happens with the *
operator:
int i = 0
int* ref = &i
*ref = 2 // assign 2 to referenced field
println i // prints 2
println *ref // de-reference ref and get field value; prints 2 again
// (notice operator syntax)
To make working with References easier, a new Type Syntax for Implicitly Wrapped References was introduced:
int^ ref = i // no & required
println(ref + 1) // no * required for de-referencing
Since most fields of external classes will probably hidden behind property accessors, it is now also possible to
reference these using the &
operator:
class Person(String name {
get { println "get"; this.name }
set { println "set \(newValue)"; this.name = newValue }
})
var person = new Person("John")
String* ref = &person.name
println *ref // prints 'get', 'John'
*ref = "Jack" // prints 'set Jack'