Anonymous type constructions

There have been discussions on tuple and union types at various locations and the Kotlin Features Survey #2 also contains these proposals. The purpose of this topic is NOT:

  • To continue the discussion on whether they should be added to the language or not. In fact, I’m also not a fan of complex languages which allow several different ways to reach the same goal and since such type operations compete with the traditional class-based type system, I cannot say that it would be good to add them. Nonetheless, I find them compelling and I want to explore their benefits.
  • To discuss the exact syntactical realizations.

Instead:

  • I want to put them into a bigger picture and evaluate their potential in tandem.
  • Collect and summarize simple but compelling use-cases to see what could be achieved.

Please leave your feedback and contribute to the list of examples and use-cases below.

Type operations

The basic and simple idea is to view types as sets of values and to introduce ways of creating new types mimicking basic set operations.

Product

This is the tuple type. Common syntax for the type definition is (String, Int). To enhance readability of value usage, one can give names to the factors (name: String, id: Int). Classes model products naturally, as in

class Entity(val name: String, val id: Int)

Union

For example String | Int. For more expresiveness in when blocks, one could name the summands String as Result | Int as ErrorCode. With classes, this can be modelled via inheritance as in

sealed interface Outcome {
    class Result(val res: String) : Outcome
    class ErrorCode(val code: Int) : Outcome
}

The named syntax with as is also useful to force disjoint unions. For example, String | String is equivalent to String whereas String as Left | String as Right is basically two copies of String.

val x: String as Left | String as Right = "hello" as Left
val y = when(x) {
    is Left -> x + "!"
    is Right -> x + "?"
}
assert(y=="hello!")

Intersection

For example MyInterface & OtherInterface. The real potential only arises in combination with proper subtypes, see below.

Singleton

Defines a type with a single value. Basically, only one such type is necessary, e.g. Unit. However, naming that value makes such types useful, for example to emulate enumerations with the help of union types (see below). Syntax is debatable, I just choose $Singleton for now. This is a type having just one value named Singleton. Class-based equivalent would be object Singleton.
A little differently, If x is already some value of a type, then $x is the type with the only value x. Example: $"hello".

Subtype

Given a type like Int, we want to define a new type where the set of values is a proper subset, for example all even integers. To define which values are allowed, one specifies a predicate on that type. Syntax is again debatable, but the type of even integers could be written like this Int @ { it%2==0 }. The compiler could provide smart-casts if possible, povided that the predicate is a compile time function KT-14652.

Examples and use-cases

Natural numbers

Just a theoretical exercise:

typealias N = $Zero | (N)
val three: N = (((Zero)))

Enumerations

typealias MyEnum = $One | $Two | $Three

is an enum with three values which is equivalent to

enum class MyEnum { One, Two, Three }

The following would also be a type with three values, but they are integers:

typealias MyEnum = $1 | $2 | $3

Return types

Anonymous types can be used as return types directly without defining them separately, for example to model multiple returns or different outcomes.

fun parseNextInt(line: String): (value: Int, nextPosition: Int)
fun String.parseData(): Data | Failure

Parameters

Union types as input parameters are an alternative to overloading. Subtypes are good to model requirements on inputs. For example:

// usually we do this
fun calculate(num: Int): Double {
    require(num >= 1)
	return sqrt(num - 1.0)
}
// maybe this is better
fun calculate(num: Int@{it>=1}): Double {
	return sqrt(num - 1.0)
}

Combination of subtypes

Use union and intersection types on subtypes to create new subtypes.

typealias ShortString = String @ { it.length <= 8 }
typealias Capitalized = String @ { it.firstOrNull()?.isUpperCase() ?: false }
typealias UserIds = ShortString & Capitalized
typealias AdminIds = $"admin" | String @ { it in ["alice", "bob"] }
typealias AllIds = AdminIds | UserIds
5 Likes