"Extension types" for Kotlin

Nat wrote a good summary of Kotlin on his blog recently and this remark in particular:

However, there’s no way to write a generic function that sums its parameters, because add is not defined on an interface that can be used as an upper bound.

I wonder if we could extend the concept of “extension functions” to types (“extension types”?). For example, I could define an adder interface:

interface Addable<T> {
    fun add(a: T, b: T)
}

and then specify that the existing class Int implements Addable via an extension mechanism using this made up syntax:

override class Int: Addable<Int>

Once you have this, you can write your add function on all types that belong to this type class.

The parallel with extension methods should be obvious: extension methods let you add methods to classes that are already defined, extension types would let you add interfaces to types already defined.

Here is a more complete example. Let’s say we have an interface Monoid:

interface Monoid<T> {
    fun zero() : T
    fun append(a1: T, a2: T) : T
}

This is a very generic interface used to write a lot of algorithms. Some instances of monoids are (Int, +, 0) or (Int, *, 1). Strings also form a monoid under appending and with the zero element being the empty string.

Let’s say we are using a library defining such an interface and along with it, a lot of useful algorithms based on monoids. In my own application, I am manipulating strings and I’d like to use the algorithms found in this library on them. Unfortunately, the standard String class is not a monoid (obviously, since it doesn’t know anything about this library).

How can we make String into a monoid so we can use these algorithms?

First, we add these functions to String as extension functions:

fun String.zero() = ""
fun String.append(a1: String, a2: String) = a1 + a2

Next, we tell the compiler that String is now a valid Monoid using my imaginary syntax:

// Not valid Kotlin (yet!)
override class String: Monoid<String>

From this point on, any function that works on a Monoid works on a String.

Wondering if such an approach would be feasible for future versions of Kotlin…

25 Likes

I’m not sure I like the override class... pattern,but I think I understand the basic desire.

What if we instead extend the concept interfaces were split into two types: explicit (current Kotlin interfaces), and implicit (I’ll call them traits just to distinguish them, but they another good name might be 'concept`, akin to the concepts being added to C++).

These new implicit traits would be checked at compile time. No class would need to explicitly implement the trait, instead the compiler just verifies that the class fully fulfill all trait’s requirements given the current scope. To be consistent, they would otherwise behave exactly like interfaces, i.e. have no state but can have default implementations.

To pull from your example, you would have code that looked like this.

trait Monoid<T> {
    fun zero(): T
    fun append(a1: T, a2: T)
}

Nothing would be required for classes that implement all requirements, but if you had a class that does not satisfy the trait, such as String, you could use extension methods to fulfill the missing pieces.

fun String.zero() = ""
fun String.append(a1: String, a2: String) = a1 + a2

The use-case for this would be when you don’t to impose an interface on all clients of an API, you just want to verify some basic behavior is available.

It’s getting close to structural typing at this point and personally, I’m not a fan.

It’s also a bigger burden for the compiler and Kotlin is 100% nominative so far. Also, I don’t really like the existence of two types of interfaces, which looks like a big source of confusion.

7 Likes

I agree 100% with Cedric, structural typing is very problematic. You change a function that implicitly was used to implement an interface and the code stop working. Also it is very hard to analyze the code for classes implementing some specific interface. Much better to have it declared explicitly. I like a lot Cedric idea. It would make adapting existing code and integrate in yours a much easier job.

4 Likes

Yeah, you’re both right, but it seems like structural typing is what you’re really after, you just want to be more explicit about it. Is that really going to avoid the problems that come up with structural typing? To be honest, I don’t know, my experience is limited.

I guess I’m mostly bothered by the idea of overriding a class, it seems counter to the reasons classes are final by default in Kotlin.

I don’t think my suggestion invalidates the final by default approach.

You’re not modifying the existing behavior of the class, you’re just adding an interface to it and possibly, adding a few functions to make this type check.

2 Likes

You’re probably right, it is more about extending rather than modifying.

How about some sugar (borrowing from Swift) like this?

extend class String : IMonoid {
    fun zero() = ""
    fun append(other: String) = this + other
}
6 Likes

If you go this way, then you will have to repeat the entire definition of IMonoid for each class you want to add to this type class.

I think IMonoid should be defined once and then its name can be reused whenever a new class wants to be part of that family.

I’m not married to my suggestion of using override for this, it was just the first idea I had with the constraint of not adding a new keyword, I’m sure we can come up with other syntaxes.

I saw the extend class as just some added sugar to remove the need for repeating the Type.function() syntax, rather than explicitly implementing the interface. I envisioned it being the equivalent to your initial example.

In the past, JetBrains has been strongly against the idea of implicit conversions (which this essentially amounts to - importing the extension would implicitly extend the type of the object).

Ages ago a conversion operator was proposed allowing types to be converted. I kind of liked the idea, but think its been dropped along the way. See: https://youtrack.jetbrains.com/issue/KT-1752#comment=27-318887

I guess your proposal avoids the “boxing” overhead that conversions would introduce however.

1 Like

the “Nice” programming language had an Idea for how to handle this and still work with the Java Type system, Generics.

http://nice.sourceforge.net/manual.html#abstractInterfaces

Abstract Interface are special Interfaces which can only be used as to close a generic argument (like < A : AbstractInterface > ) , you cannot have handle to the type, except in a generic context, it appears to comes down to a compiler trick, like a macro, maybe similar Kotlin’s reified generics, or Go’s interfaces

Nice! (pun semi-intended)
Seems quite similar to what I proposed here: Kotlin and the Expression Problem

But you’re going to have to do that anyway, even with your original suggestion, correct? Or do you mean classes that have a subset of the interface already implemented?

Right, not sure what I was thinking when I wrote that…

Hello,

Here is another (draft) approach to typeclass inspired by scala implicit resolution (but limited to typeclasses use case)

`

//Typeclass interface definition
interface Monoid<T> {
    fun zero(): T
    fun add(a1: T, a2: T): T
}

//String instance
val stringMonoid = object : Monoid<String> {
    override fun zero() = ""
    override fun add(a1: String, a2: String) = a1 + a2
}

//Int instance
val intMonoid = object : Monoid<Int> {
    override fun zero() = 0
    override fun add(a1: Int, a2: Int) = a1 + a2
}

//A generic function working on all type T, where it exists a Monoid<T>
//Such instance is callable in the function body as M
fun <T with Monoid as M> multiply(a: T, n: Int) {
    var t: T = M.zero()
    for (i in 1 to n) {
        M.add(a)
    }
    return t
}

fun main(args: Array<String>) {
    println(multiply("hello", 5)) // should print "hellohellohellohellohello"
    println(multiply(4, 5)) // should print 20
    println(multiply(45.3, 5)) // should not compile because no Monoid<Double> is defined
}

`

1 Like

I think the important part of such suggestions is the resolution of the implicit, which is the Monoid interface in this case. What are your thoughts on that?

  • Is it an import at the call/usage site?
  • How can you be explicit about the type class being used, e.g. can I have intMonoid1 and intMonoid2?
  • What about hierarchies, e.g. having an anyMonoid and a stringMonoid?

To me such resolution of implicit classes should in fact be very very explicit. No compiler burden, but simple straightforward rules that are easy to understand and (relatively) easy to implement.

There are also some other important questions, questions like:

  • What will the java signature be?
  • Or doesn’t it exist on the java site, will it always be inlined?

What may be a solution would be to allow the following to work (and be statically resolved)

inline fun <reified T> add(v1:T, v2:T) = when (T) {
  Int -> v1 + v2
  BigDecimal -> v1.add(v2)
} 

This code should not require an else clause, but a compile error instead. The non-applicable cases should also be elided/omitted from the compilation result as they are statically unreachable. Usage of T.class with the same eliding would be ok as well.

2 Likes

I’d like to throw my $0.02 in also - personally I love the idea of “Extension Types” as well. I do a lot of work with JavaMail, which pre-dates the AutoCloseable/try-with-resources functionality. Even though some of the classes in there have explicit close methods, I still have to add a finally block to manually close them. It’d be awesome to just do something like (using @CodeAnxiety’s sample syntax):

extend class Store : AutoCloseable {
    fun close() {
        (this as Store).close() // well, some kind of way to refer to the base method instead of trying to recurse.
    }
}

createEmailStore().use {
    // blah blah blah 
}

On one hand, I can see this being consider an implicit conversion - it basically is. However, I don’t think it’s terribly different from extension methods. Those are kinda-sorta implicit conversions - you’re converting from a type that doesn’t have the functionality you want into one that does. These would still be a decorator for the original object, just one that implements a specified interface instead of one that just adds a couple of methods.

The only possible gotcha I can see off hand would be if the extended class already possesses (some) methods with the same signature; how would those be resolved? I imagine whatever resolution method is in place for extension methods would work as well, but maybe not.

One other possibility I remember from my C# days was that you had the ability to explicitly define an implementation for an interface in the case of naming conflicts - you could have AutoCloseable.close() and FrontDoor.close(). Depending on the context the object was used in, it would call which ever one was appropriate; if the object was referenced as a FrontDoor instance, it would call the FrontDoor.close() implementation. If you omitted the type qualifier and only had a single implementation, it would just call that regardless of the context.

1 Like

I really like @cedricbeust 's proposal (apart from the syntax). It would give us simple type classes for almost no additional complexity or performance cost. In Kotlin they could appear as regular interfaces. The only problem would be that you couldn’t use those interfaces in Java. But at least you don’t have to learn a new concept on the Java side to interact with those methods. I think this is a fair trade off.

What does the Kotlin team think of it? I couldn’t find any responses in this thread.

The idea is indeed very similar to implicit conversions, for which we’ve made an explicit decision not to include them in the language.

1 Like