- Introduction
- Editor
- Get started
- Runtime / VM
Language Guide
Design-Notes
Generics
Generics allow you to define classes and functions that can work with any data type. AutoLang supports custom generic classes, multiple type parameters, and generic inheritance.
Under the hood, AutoLang uses monomorphization. This means strict type safety and the ability to instantiate generic types directly, as each instantiation creates a unique concrete class.
Defining Generic Classes
You can define a class with one or more type parameters using angle brackets <T>. Since the type is known at compile time, you can even instantiate T directly using T().
// A simple generic container
class Box<T> {
lateinit val value: T
// We can instantiate T directly because of monomorphization
constructor() {
this.value = T()
}
func getValue(): T = value
}
class User {
func toString() = "I am a User"
}
// Box<String> and Box<User> become two different concrete classes
val stringBox = Box<String>()
val userBox = Box<User>()
println("\"${stringBox.getValue()}\"") // "" (default empty string)
println(userBox.getValue().toString())Multiple Type Parameters
Classes can accept multiple type parameters. This is useful for mapping keys to values or wrapping multiple distinct types.
class Pair<K, V>(val key: K, val value: V)
val data = Pair<String, Int>("Age", 25)
println("${data.key} : ${data.value}")Generic Inheritance
A generic class can inherit from another generic class. You can pass type parameters from the child to the parent, or fix specific types for the parent.
class Wrapper<T> {
val content: T?
func get(): T? = content
}
// SmartWrapper inherits Wrapper and adds a secondary type R
class SmartWrapper<T, R> extends Wrapper<T> {
lateinit val metadata: R
constructor(item: T, meta: R) {
super()
this.content = item
this.metadata = meta
}
}
val item = SmartWrapper<String, Int>("Hello", 404)
println(item.get()) // Inherited from Wrapper
println(item.metadata) // Property of SmartWrapperPolymorphism & Runtime Checks
Because of monomorphization, each generic instantiation has its own identity at runtime. You can use is, as, and as? with fully-instantiated generic types.
class Animal { func sound() = "..." }
class Dog extends Animal {
constructor() { super() }
func sound() = "Woof"
}
class Cat extends Animal {
constructor() { super() }
func sound() = "Meow"
}
class Tester<T> {
val subject: T = T()
func check() = subject.sound()
}
val dogTest = Tester<Dog>()
val catTest = Tester<Cat>()
println("Dog says: ${dogTest.check()}")
println("Cat says: ${catTest.check()}")
// Runtime type checks work on full generic signatures
if (dogTest is Tester<Dog>) {
println("Verified: This is a Tester<Dog>")
}
val unknown: Any = dogTest
val casted = unknown as? Tester<Cat>
println("Is dogTest a Tester<Cat>? ${casted != null}")Design Constraints
- Monomorphization:
Box<Int>andBox<String>are compiled as completely different classes. This offers maximum performance with no boxing overhead. - Instantiation:
val x: T = T()is supported, allowing for powerful generic factory patterns. - Invariance:
Array<Dog>is not a subtype ofArray<Animal>. However, class inheritance hierarchies (likeSmartWrapperextendingWrapper) are fully supported.
