Purpose
This proposal introduces the concept of “elastic” types, classes, function, and products to solve the compile-time and runtime problems when we want to add fields and default values in a backward compatible way.
Typing
An elastic type Foo
of arity n
has the expandable property where Foo[A1, A2, ..., An] =:= Foo[A1, A2, ... An, An+1, An+2, ..., An+m]
if and only if An+i =:= Nothing
, for all i=1...m
.
Syntax and Compile-time Rules
Introduce the (soft) keyword modifier elastic
; applicable for classes, functions, case classes.
elastic def foo(...): ... =
elastic class Foo(...)
elastic case class FooCC(...)
The elastic
keyword modifies the function type for definitions, and both the constructor type and class type for classes and case classes:
elastic class ElasticFunctionN
is introduced and is a subtype ofFunctionN
for any arity. Examples:
ElasticFunctionN[T1, T2] <:< Function2[T1, T2]
ElasticFunctionN[T1, T2, Nothing] <:< Function2[T1, T2]
ElasticFunctionN[T1, T2, T3] <:< Function3[T1, T2, T3]
elastic abstract class ElasticProduct
is introduced and is a subtype ofProduct
.
Runtime Elastic Dispatcher
To achieve runtime backward compatibility when adding fields to an elastic function/class, we change the runtime encoding to go through a dispatcher array for default values.
Example
The following def:
elastic def foo[A, B, C](arg1: A, arg2: B, arg3: Int = 1, arg4: Option[C] = None): Unit = {}
Will be encoded like
elastic def foo[A, B, C](
arg1: A = foo$default[A](0),
arg2: B = foo$default[B](1),
arg3: Int = foo$default[Int](2),
arg4: Option[C] = foo$default[C](3)
): Unit = {}
//default arguments encoding
def foo$default[T](argIdx: Int): T =
if (argIdx >= foo$default$dispatcher.size) throw new UnreachableArgumentException("foo", argIdx)
else foo$default$dispatcher(argIdx)().asInstanceOf[T]
val foo$default$dispatcher: Array[() => Any] = Array(
() => throw new MissingArgumentException("foo", "arg1"),
() => throw new MissingArgumentException("foo", "arg2"),
() => 1,
() => None
)
Discussion
(Maybe it’s a totally idiotic idea, so feel free to chime in and let me know)
- What would be the overloading rules? Can we allow more than one elastic signature with the same name?
- What would an elastic case class encoding look like? Should field access go through a dispatcher as well?
- Should Named Tuples extend
ElasticProduct
? Would that enable “intuitive” subtyping rules so tuples are subtypes of unnamed tuples? - How do we expand this to multi-block functions?
- The proposed subtyping rule for elastic types is
=:=
and not<:<
. This actually enables forward binary and source compatibility, with runtime exceptions that can be handled. Should this be possible?