YAFL generics, again

I have been stuck in a thought loop. In Rust traits provide generic constraints and they provide the equivalent of class/interface in other languages. The distinction is in how you use them. Traits are very similar to the Haskell concept of class, which has nothing to do with classes as you or I know them. And class/interfaces as we know them from C++/C#/Java etc have nothing to do with traits.

For my first attempt at formalising the grammar around generics I went for the Rust model of traits and trait implementations, which I kept separate from the OO class/interface model I borrowed from C#/Java. This still left me feeling uneasy. Rust managed to cross over these concepts. Should I try harder, or is it ok that I have two generic domains.

Then I went back to the origins of what a class means, where the original concept in Scheme was as a kind of module. I thought further that really it’s got a lot in common with modules, but with additional parameterised state. But it is still a set of functions in a scope. And I thought about Kotlin’s extension methods, and how they are used for passing in domain APIs to code blocks. Scope, state, modules. Generics. All coming together.

At this point my head was starting to hurt, until something suddenly struck me. The fundamental concept I was seeing here is that the C#/Java concept of class/interface is equivalent to the Rust concept if trait, IFF the class has a zero parameter constructor. In that case, it might as well be a singleton, and it might as well be implicitly in scope.

Suddenly the pieces are falling into place. Let’s play.

# Interface defines some functions that can operate on T
interface Addable<T> {
    fun `+`(a: T, b: T): T
}

# Zero param constructable class implements the functions
class Int32Addable() : Addable<Int32> {
    fun `+`(a: T, b: T): T = plusFunction(a, b)
}

# This function can add anything if there is an implementation
# of Addable available at the call site
fun addSomething<T>(a: T, b: T): T where Addable<T> = a + b

# Or we could construct an instance of Int32Addable and pass
# it in as an object.
fun addSomething<T>(a: T, b: T, impl: Addable<T>): T = impl.`+`(a, b)

fun main() {
    # These two calls are functionally equivalent, but
    # semantically and in terms of runtime code generation
    # are passing functionality in different ways.
    print(addSomething(1, 2))
    print(addSomething(1, 2, Int32Addable()))
}

This isn’t everything, not by a long shot. It looks nice, but I have just re-introduce the diamond inheritance problem that Rust so elegantly avoided. So it’s still a work in progress.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s