Franca programming language specification

Published Jul 14, 2018. 22 minutes to read.

Franca is a programming language, designed as part of a research project in programming language and compiler design.

I might create a compiler for it some day, but there is no immediate goal to do so. For now, the language is purely theoretical.

This specification assumes readers have general working knowledge in computer science.

Here be dragons.

What is Franca?

Franca is general purpose, object oriented, statically typed, imperative, compiled programming language. It is similar to Python in terms of syntax, but inherits a lot of concepts from C family of languages, namely Java and C#.

The language is designed with emphasis on program correctness and is therefore very opinionated about certain aspects of the code structure and syntax.

Major changes

  • 01.04.2019 - Syntax update (guards, conditionals)
  • 24.03.2019 - Syntax update
  • 03.10.2018 - Transport types.
  • 12.09.2018 - Events and visibility updates.
  • 14.07.2018 - Initial version.

TODO

  • Annotation collectors - alias for a group of annotations.
  • Clean up visibility slightly. Slightly overcooked that.
  • Generators (Python-like).
  • Rename values to labels.
  • Streams and stream processing support in language level.
  • Cast syntax specification (x as Type).
  • Annotations management in runtime (annotation reflections).
  • Documentation comments and apidoc generation.
  • Compile time dependency injection + configuration and properties. Basically a compile time DI.
  • Compile time reflections (query types in runtime - all instances of X type, all annotated with, etc).
  • Serialization and deserialization, object versions.
  • Explicit override for methods overriding imported type.
  • Easier way to define complex data structures. Sort of typed inline JSON or Hash/Dict. (extended transport types).
  • Built in support for async operations/co-routines. Sort of “enqueue X, and continue here when X is done (async/await)” with some syntax sugar related to error handling.
  • noop to indicate empty method blocks etc. Kind of like Python pass

Hello World

package com.github.addvilz.franca

import static std.io.StdIO.println

main:
    println('Hello world')

Kitchen sink

Small sample of the look and feel of the language.

package com.github.addvilz.franca

@Singleton
public type Application:
    extends BaseApplication
    extends ApplicationService
    implements Executable
    implements ServerApplication
    
    cast ContainerApplication:
        new ContainerApplicationWrapper(this)
        
    overload ApplicationCollection append(ServerApplication other):
        ApplicationCollection.of(this, other)
        
    event ApplicationStartEvent startEvent
    
    UInt16 port
    InetHost host
    
    constructor(String host, UInt16 port) !IllegalArgumentException:
        throw new IllegalArgumentException('Illegal port') if port < 1025
        throw new IllegalArgumentException('Illegal host') if host.isEmpty()
    
        this.host = host as InetHost
        this.port = port
    
    public start():
        super.start()
        emit new ApplicationStartEvent() to startEvent

General language properties

Operators

Arithmetic: +, -, /, *, %, ++, --

Comparison: ==, ===, >, <, >=, <=

Bitwise: &, |, ^, ~, <<, >>, >>>

Logical: &&, ||, !

Assignment: =, +=, -=, /=, *=, %=, ++=, --=, <<=, >>=, &=, ^=, |=

Ternary: ? and :

TODO: precedence def

Type modifiers

abstract - type that can only be extended, but not instantiated. Implicitly open. open - type that can be optionally subclassed. Franca types can not be subclassed by default.

Visibility modifiers

TODO: slightly overdone. Some of these serve no practical purpose.

In Franca, all visibility is structured in two main structures - local and foreign assembly. Local assembly is defined as current target of compilation for this source tree - for example, binary, library etc. Foreign assembly is defined as all assemblies that does not belong to current source tree - think, vendor dependencies, etc.

Franca also offers much finer visibility control inside the local assembly than other languages, namely, if used correctly, it will prevent accidental “spiderweb” source tree from occurring altogether.

external public

Globally visible in all packages, current and other assemblies.

When developing a library, this would be the visibility to apply to all interfaces, types and such, subject to public API.

Applies to: all visibility targets.

external protected

Globally visible in all packages, current and other assemblies by current type and any sub-type of the current type.

Applies to: fields, methods.

public

Accessible from anywhere within current assembly.

Applies to: all visibility targets.

private

Visible in current type only.

Applies to: fields, methods.

protected

Visible in local assembly by current type any sub-type.

Applies to: types, methods, fields, partials.

local public

Same as public, but limited to current package.

local protected

Same as protected, but limited to current package.

descendant public

Same as public, but limited to current and any descendant packages.

descendant protected

Same as protected, but limited to current and any descendant packages

umbrella public

Same as public, but limited to all packages EXCEPT the descendant packages.

umbrella protected

Same as protected, but limited to all packages EXCEPT the descendant packages.

Default visibility rules

Types: public by default. Types are by default visible to all other types in the current assembly.

Methods: private by default.

Fields. private by default.

Constructors: inherits visibility from defining type.

Destructors: no visibility possible. Effectively external protected.

Operator overloads: inherits visibility from defining type.

Cast overloads: inherits visibility from defining type.

Primitive types

TypeDescription
NullNull value, nothing.
nullAlias of null, used as literal value.
VoidAlias of null, used exclusively for type hinting and generic types.
StringTextual value
CharSingle logical unit of String, usually a character or symbol
BooleanUsed to declare variables to store the Boolean values, true and false
SByte8-bit signed integer. Value range from -128 to 127 (inclusive)
Byte8-bit unsigned integer. Value range from 0 to 255 (inclusive)
Int1616-bit signed integer. Value range from -32,768 to 32,767 (inclusive)
UInt1616-bit unsigned integer. Value range from 0 to 65,535 (inclusive)
Int3232-bit signed integer. Value range from -2,147,483,648 to 2,147,483,647 (inclusive)
UInt3232-bit unsigned integer. Value range from 0 to 4,294,967,295 (inclusive)
Int6464-bit signed integer. Value range from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
UInt6464-bit unsigned integer. Value range from 0 to 18,446,744,073,709,551,615 (inclusive)
Int128128-bit signed integer. Value range from −(2^127) to 2^127 - 1
UInt128128-bit unsigned integer. Value range from 0 to 2^128 − 1
Decimal32
Decimal64
Decimal128
Float32
Float64
Float128

Values and fields

Values and fields in Franca language are effectively labels that point to the object instance that was assigned to given label in current scope, similar to other object oriented programming languages.

In other programming languages this construct is often referred to as “variables”.

The main difference between Franca values and values in most other similar languages is that in France, value labels are immutable by default.

Once a value is defined it will always and forever refer to the same object.

If you require a mutable value - or a variable, you must prefix the definition of the value with keyword mut.

The same rules apply to fields (properties) of Franca types. Fields in types are also immutable by default, unless explicitly defined mutable.

For example:

// ...
String foo = 'bar' // Immutable label
mut String baz = 'biz' // Mutable label
// ...

No global functions or values

There are no global functions and no global values. All functions are members of objects - methods, all values are either fields of objects or local values.

Values are always objects

Everything, except language constructs is an object. Primitives are objects, as are type definitions.

One source file, one root construct

One source file can only hold one instance of whatever construct that source file describes - type, interface, partial and other.

Simply put, you can not have multiple types defined in the same source file, even if they have different visibility, as you can do, for example, in Java.

No nested structures

Types and other root structures can only be defined at the root level. You can not nest type definitions, interfaces and such.

(Almost) no inline implementations

Inline implementations of interfaces are not permitted, except if they are implementations of functional interface, effectively - lambdas.

Nulls and null safety

As a general rule, no value can be assigned null value unless explicitly marked nullable by suffixing the type in value or field definition with a question mark, or by wrapping the value in Nullable type by hand using Nullable.of(...) static method.

The question mark suffix is just a syntax sugar with some magic. Eventually, it still translates to Nullable<T> and both can be used interchangeably, although using question mark suffix is recommended.

Nullable type exposes methods <T> get, <Boolean> absent, <Boolean> present, and others (TBD).

Nullables are the only constructs capable of having null value. Attempt to assign null to non-nullable label in code will result in compile error. Attempt to assign null to non-nullable label in runtime will result in hard exception.

For example:

String notNull = null // Compile error

// ...
String? nullalbe = null // Compile ok

// ...
String? nullable
nullable = null // Compile ok

//...
Nullable<String> explicit = null // Compile ok

//...
Nullable<String> explicit = 'Foo Bar' // Compile ok (value auto-wrapped)

//...
Nullable<String> explicit = Nullable.of('Foo Bar') // Compile ok (value wrapped explicitly)

String notNull = null will result in compile time exception.

String? notNull = null will compile.

Value and field assignment and definition

Values and fields can be defined and assigned values, however, they MUST always be assigned some value when accessed.

For fields, value must be assigned in static {} initializer of the class for static fields, or in the type constructor for instance local fields, but always before this field is used. Usage of un-initialized field will result in compile time error.

For values, value must be assigned before the value is accessed. All execution branches must result in value assignment. Usage of un-initialized value will result in compile time error.

This rule also applies to Nullable values.

String value encoding

String value encoding is, by default, platform dependant. Custom default encoding can be set in compile time, or for each String object specifically using String type constructor. Strings in Franca are actually thin wrappers around byte arrays, and therefore can theoretically hold Strings encoded using any encoding supported by the system.

All String objects carry extra pointer to value of Encoding enum that indicates how the contents of the string are encoding during runtime.

Deterministic binary serialization of strings

Franca has special API to String and Char types to deal with binary serialization - instance methods String::getPortableBytes() and Point::getPortableBytes(), and static methods String.fromPortableBytes(...), and Point.fromPortableBytes(...).

In contrast to String::getBytes() and Point::getBytes(), and similar, these methods emit and accept Strings expressed as byte arrays, where byte arrays are prefixed with 2-byte numerical reference to encoding that was used to encode them.

This API can be used to safely store strings in external storage in binary form and later retrieve them from such storage without worrying about encoding correctness.

Design note: this approach is designed to solve very specific edge case, where working with different encodings in a program is commonplace. If possible, you should avoid having this issue in the first place.

Char type and strings

Individual logical parts of a String are referred to as chars in Franca and expressed as Char type. Franca Char differs slightly from implementations often found in other languages - Char type in Franca is in fact a kind of String with fixed maximum size of 1 logical character.

Char is defined to represent single logical unit of any string, and is therefore, variable length. Byte size of a char depends on the character encoding used for the particular string.

API of the String type exposes two sets of methods that are often used to modify strings, one for modifying strings relative to the byte size of the string, and one relative to the char size of the string.

For example:

String value = new String('😊😊', Encoding.UTF8)
value.byteSize() // 8 bytes
value.charSize() // 2 chars

// ...

value.charAt(2) // Char('😊')
value.byteAt(2) // Byte(0x9F)

Inline strings

There are 4 different ways to define a string.

Enclosed in single quotes ('text') - plain string. Enclosed in double quotes ("text ${field}") - template string. Evaluated for variables. Heredoc with name in single quotes (<< 'EOF' text EOF;) - plain multiline string. Heredoc without name in quotes (<< EOF text ${field} EOF;) - template multiline string.

Important: Strings in heredoc format have first and last newlines trimmed after creation. Always.

Language structures

Types

Types in Franca language are similar to classes in other languages. Types are definitions of functionality for objects that will be created by instantiating type definitions - refered simply as types in Franca.

Interfaces

Franca supports interfaces with multiple inheritance (interface can itself extend one or more other interfaces). Interfaces can define method signatures, but not their behavior. There is no support for default behaviors, static methods or static variables in Franca interfaces. Interfaces can not import partials.

Special feature: in Franca, interfaces CAN require presence of constructor(s) in types implementing the interface. In Franca, type constructors are considered part of type public API and can be therefore required by interfaces.

Special feature: in Franca, interfaces CAN require presence of both public and protected methods - protected methods are considered part of the protected API of a type, but is considered public in a sense that it is still visible outside the immediate type definition, and therefore protected methods can be required to be implemented by an interface to enforce oponness to extension or.

Example:

public external interface MyWorkerInterface
  public external work()
  public external work(String[] args)
Functional interfaces

Functional interfaces allow to define signature for implementations with only one functionality exposed, and can only have one abstract method defined. They are otherwise the same as normal interfaces and follow the same rules.

Example:

public external functional Consumer<T>
  public external consume(T value)
Enums

Enum structure, or enumeration is a static set of types inherent to the enum type itself. Enum type can have any number of enumerated values defined that are effectively types extending enum base type. Enum types are static and can not have constructors or destructors, can not hold state, can not have fields and enums are always serializable.

Enums are effectively final, can not be extended or extend other enums.

Example:

enum TimeZones
  UTC:
  GMT:
  CET:

Enum base type can contain abstract and implemented methods - abstract methods must be implemented by all enum members, and non-abstract methods are inherited by all enum members.

Example:

enum TimeZones:
  UTC:
    String getName():
      'UTC'
      
  GMT:
    String getName():
      'GMT'
  CET:
    String getName():
      'CET'

  abstract String getName()

  String getNameLowerCase()
    return getName().toLowerCase()

Enums, enum methods and enum members follow the same visibility rules as any type does.

Example:

enum TimeZones:
  UTC:
  GMT:
  private CET:
Annotations (TODO: could this be done better?)

Franca language has built in metadata system - annotations. Annotations are designed to contain service information and metadata for the constructs annotated with such annotations.

Values stored in annotations must be static. Once defined, annotations are read-only.

Example:

@RetentionPolicy(RetentionPolicy.RUNTIME)
@AnnotationTarget(AnnotationTarget.TYPE)
@AnnotationTarget(AnnotationTarget.METHOD)
external public annotation MethodHandler:
  String[]? methods default null
  Boolean enabled default true
Predefined annotations

@Deprecated - indicates component is deprecated. Will result in compiler warning when used. @Unstable - indicates component is unstable and not to be used in production. Will result in compiler warning when used. @TODO - indicates incomplete implementation. Will result in compiler warning when used.

Generics

Generics in Franca work one-to-one as they do in Java.

public type Foo<T, V>:
    V bar():
        noop
public type Bar:
    <T> T baz(Object baz):
        baz as T
Partials

Partials are special kind of abstract types that can hold certain implementations of methods and can be used to construct other types.

In contrast to abstract classes, partials are completely stateless.

Partials follow the same visibility rules as types, partials can extend other partials but can not implement interfaces, hold static fields or any other state.

Methods defined within partials follow the same visibility rules as methods in normal types.

Types must import complete partial, it is not possible to single out certain methods from a partial and import selectively. This is due to the fact that methods in partial are aware of the scope and can be reliant on other, perhaps private methods inside a partial.

Example:

public partial MyPartial:
  extends OtherPartial

  public Boolean foo():
    return true

  public Boolean bar():
    return !foo()

type MyType
  imports MyPartial

//...
MyType baz = new MyType()
baz.foo() // true
baz.bar() // false

Transport types and rich data structures

Transport type structures is one of the most powerful features of Franca language. It allows to define complex nested data structures composed from types already defined and types automatically defined within the transport itself.

In essence, transport types allows you to define typed, nested structures for data transport use.

This is extremely useful when, for example, working with external document formats, like JSON etc, for schema definition and other tasks requiring complex nested data types.

Transport types are inverse of abstract types. Transport types can only hold fields with getters and setters, and cast, and operator overloads.

Transport types can not have delegates, methods, constructors and destructors. Transport types can not implement interfaces and import partials.

Example of a basic transport type:

transport MyDataTransport:
  mut String fooBar:
    get:
      'moo'
    set: // Requires field to be mutable.
      print(it)
      // NOOP
      this

Example of transport type with auto-definition:

transport ExtendedTransport:
    mut String foo
    mut UInt32 bar
  
    // This transport type is defined inline
    auto mut NestedTransport buz:
        UInt32 x
        UInt32 y
     
        // As is this
        auto mut Coord faz:
            String? maz
            CustomType baz
        
        // Array of...
        auto mut Thing[] boo:
            String? bing 

Language features

Execution entry point

As point of initialization, main method is defined in the root of a source file. Main method is magic - it is the only globally definable method.

The identity of main method is the location of it’s package and source file.

There can be more than one main method per assembly.

package com.github.addvilz.franca

import static std.io.StdIO.println

main:
    println('Hello world')
    println(it) // it = String[] of command line args.

Assuming the file name for this main method is Main, the address of this main method is com.github.addvilz.franca.Main.

Package naming conventions

Package should always mirror the location of the directory relative to the source tree root. Package names should always be written in lower case.

For example:

Source file src/com/example/product/warehouse/Item.fra
Source root: src/
Source file: Item.fra
Type: Item
Package: com.example.product.warehouse
FQCN: com.example.product.warehouse.Item

Contrary to everything else in Franca language, package names are CASE INSENSITIVE.

For libraries

Package names should reflect the identity of the author and identity of the program being written. Companies should use reverse of their domain name for their root packages, individuals should follow similar conventions where possible.

Example root packages:

com.example.product
com.github.example_user.project
com.jhondoe.my_library
For applications

Standard package name for applications that are standalone, can not be embedded and results in final assembly (executable) is application.

application package MUST NEVER be used in libraries, for any reason.

Special use and reserved package names.

std.* package is reserved for the standard library std.vendor.* package is reserved for compiler vendor extensions for different implementations. application - for use in final assemblies and executables.

Compile time dependency injection

Franca language has a built in dependency inversion and dependency injection system.

// TODO

Constructors

Franca types can have zero or ore constructors defined per unique type.

Constructors are invoked whenever an object is created from type, and all types have default zero-argument constructor unless an explicit constructor is defined in a type.

During object creation, only the topmost constructor is invoked, if the type happens to be a child of some other type.

Unless the parent type has no explicit constructors, child constructor MUST invoke parent constructor as first operation of the child constructor.

Constructors can have visibility, as any other method. By default, constructors inherit visibility of a type.

Destructors

Types can have zero or one destructors defined in type definition.

Destructor is a special method that is invoked right before the object is garbage collected from memory during graceful shutdown or normal operation of a program.

Destructors are designed to allow for program to gracefully dispose of resources and shutdown correctly before being de-allocated from memory.

Destructors will not be invoked during abnormal program termination or if object is never garbage collected.

If type extends other types, parent destructors must be invoked explicitly by child types, in the event child type overrides parent destructor.

Method overloading

Franca supports method overloading, as long as the number of arguments or the type of arguments in order is different.

Overloading language features

Overloading target type resolution rules

Overloads will try to match subjects by inheritance and signature trying the most specific matches first. For example, if there is an overload for both specific type and interface the type implements, the overload that matches the type will be used. If there is no overload for specific type, but there is for an interface, the interface overload will be used.

You can disable the resolution by using strict keyword on overload feature definitoon - then, the defined overload will match the types exactly, and overload will only work for subjects with exactly the defined type. Definining strict overload or strict cast on interface or abstract class is not possible and will result in compile error.

Operator overloading

Franca language supports limited operator overloading in local scope - you can overload an operator if the left hand side object, and configure it to accept right hand side object as argument to the overloaded operator.

Operator overloads are evaluated left to right - only the left hand side object must have appropriate overload for overloads to work.

Overloads can be defined in both types and partials for import.

Overload methods must specify return type explicitly.

By convention, changing the order arguments should generally follow math rules for the operator being overloaded, if any. As a basic example, we know that changing order of arguments for addition has the same result (1 + 3 === 3 + 1). It is language convention that operator overloads should follow the same logic, if overloads are implemented in both sides of the operation. If such is the case that logic is different, it should be clearly documented.

Operator overloading is defined by adding a special overload method to a type with defined return type and operator argument.

Operator overloads are not allowed to throw checked exceptions.

Overload methods are aware of the scope and have access to this.

Operator overloads follow all normal visibility rules any other method has. By default, operator overloads inherit visibility of the defining type.

type MyOverloadType
    overload MyOverloadType add(MyOverloadType other):
        noop

    overload MyOverloadType divide(MyOverloadType other):
        noop

    overload MyOverloadType divide(SomeOtherOverLoadType other):
        noop
        
    overload MyOverloadType append(SomeOtherOverLoadType other):
        noop
}
Cast overloading

Franca allows for custom implementation of casts from one type to another.

Cast overloads are aware of the scope and have access to this.

Cast overloads are not allowed to throw checked exceptions.

Cast overloads follow all normal visibility rules any other method has.

By default, cast overloads inherit visibility of the defining type.

type MyCastableType:
  cast String:
    'This object is ' + toString()

//...
MyCastableType foo = new MyCastableType()
String asString = foo as String // Works fine


//...
MyCastableType foo = new MyCastableType()
String asString = foo // Implicit cast. Also works fine.

Method delegation

Franca allows for shorthand method delegation to objects.

Delegation is only allowed to non-optional, immutable fields.

Delegation is explicit - you must list all methods delegated to a field.

From compiler point of view, delegate methods are no different than normal methods - they follow the same rules for visibility, interface implementation and others.

Delegate methods can override visibility of delegated-to methods locally, but methods being delegated to must be accessible from current type.

type MyType:
  OtherType foo

  delegate foo:
    external public getBaz()
    getBar(String qux)

  constructor(OtherType foo):
    this.foo = foo
}

Event system

Franca comes with a built in event system out of the box. Event system allows any type to emit any other type as event to outside listeners.

Event emission is synchronous and sequential process. Event listeners will be invoked in the same order they were registered to the particular object.

Event listeners are not allowed to throw checked exceptions and are generally discouraged from throwing exceptions of any kind. It is best to handle exceptions in event listeners gracefully.

All exceptions thrown inside event listeners during execution will propagate up the stack like any normal method call would.

Registering listeners to undefined events is a compile time error.

type MyEvent:
 // Some event container

type MyObject:
  event MyEvent myFooEvent

  doSomething():
    emit new MyEvent() to myFooEvent

type Main:
  external public static main():
    MyObject foo = new MyObject()

    on foo.myFooEvent:
      println(it)

    on foo.myFooEvent:
      println(it)

    foo.doSomething()

Scope keywords

this - current instance of current type. Can be used as variable or type hint. static - static reference to current type (like this, but in static scope). parent - during inheritance, parent object if any. parent does not exist if type does not extend other type, this will be a compile error. origin - reference to instance of invoking type. If a invokes b, inside b value origin will refer to instance of a. Type of origin is always Optional<Object> and must be explicitly cast during runtime where needed. origin exists only during an invocation, it is always Optional<Object>. Origin is ALWAYS determined on call time, even in lambdas. For example, if lambda is defined and passed around as reference, this will point to the type where lambda was defined. In contract, origin will only be populated when lambda is invoked and will contain a pointer to the previous type in call stack.

Comparisons

== is equal to (structural, same property signature, equals) === is the same object as (referential, is the SAME object)

TODO: comparison methods.

Values and value definition

Basic syntax <Type> <identifier> = <value>

Mutable syntax mut <Type> <identifier> = <value>

Lazy values

All values can have lazy initializers. Lazy values are computed when first read operation of the value happens (comparisons, transformations, etc.). Passing values around does not trigger value computation, nor does placing the values in structs and arrays.

Lazy values can be both immutable and mutable. Lazy values can be optionals.

Lazy values are computed synchronously and are scoped to the object defining the computation - this, etc will always point to the object type where lazy variable is defined.

<Type> identifier = lazy:
    <value>

mut <Type> identifier = lazy:
    <value>

Field getters and setters

type MyObject:
  extends FooBaz
  implements BazBar

  mut String fooBar =
    get:
      return 'moo'
    set: // Requires field to be mutable.
      print(it)
      // NOOP
      return this

  mut String fooBar =
    get:
      'moo'
    set:
      print(it)
      // NOOP
      this

Pipelines

Franca supports inline pipeline calls - syntax sugar that chains invocations of multiple methods with ability to pass output of one method into another.

Chaining however does require explicit method call definitions, to support for chaining of methods where input arguments are not exactly the output arguments.

type MyObject:
  String doSomething():
    'Hello '

  String doOtherThing(String value):
    value + 'world'

  String doMoreThings(String value, String arbitrary):
    value + arbitrary

  run():
    String result = doSomething() and
        then doOtherThing(it) // value `it` contains the return value of `doSomething()`
        then doMoreThings(it, '!')

    println(result) // Hello world!

Conditionals

if x === 3:
  return x
  
// ...

if 'foo' === x:
  return 'bar'
else if 'maz' === x:
  return 'baz'
else
  return 'ziz'
  
// Guard blocks
// expression <if> condition

return x if 'foo' === x
continue if 'foo' === x
break if 'foo' === x
throw new Exception() if foo === 'x'

// conditional invocation

call_function() if 'bar' === x

Loops

while(true):
    // do something
    
dowhile(true):
    // something
    
for value in iterable:
    // something
    
for (key, value) in iterable_of_tuples:
    // something

for X in 0..10:
    // do [0,...10]

<iterable>.each:
 // it*

Features found in other languages explicitly excluded by design

These features and properties often found in other languages are excluded from Franca by design, for one reason or another. They are not expected to ever become part of the language.

  • Extension functions and properties. Types are defined as-is and are immutable.
  • Smart casts, implicit types.
  • Default and named arguments.
© Matiss Treinis 2019, all rights, some wrongs and most of the lefts reserved.
Unless explicitly stated otherwise, this article is licensed under a Creative Commons Attribution 4.0 International License.
All software code samples available in this page as part of the article content (code snippets and similar) are licensed under the terms and conditions of Apache License, version 2.0.