"Extension types" for Kotlin

Could you clarify, what does it mean “now” and “from this point on” in terms of compiler scopes? I.e. if I declare such an extension for String, where would I be able to use String as a Monoid?

Can I use List<String> where List<Monoid> is expected? If yes, how the conversion is performed?

@ilya.gorbunov

I haven’t really thought about this in details but my first idea would be to use a similar mechanism as extension functions: you would need to import the Monoid type, obviously, and also the compilation unit that defines the override. In effect, the compiler could either emit a function that performs the conversion (and I understand this is something goes against Kotlin’s focus on avoiding implicit conversions) or it could emit a new synthetic class with the required attributes. The difficulty for the compiler is to remember that a String is also a Monoid wherever these conditions are met.

To mitigate the implicit problem:

  • don’t allow chaining the conversions
  • force the extension types to be declared at the top level; much of the chagrin of Scala conversions it’s hard to know where they came from
  • (maybe?) in the same spirit, force the extension types to be imported explicitly (not included in wildcards) (*)
  • and (of course), mark extension types conversion clearly in the IDE

I’d still be useful and would place the system well into the “not surprising” territory.

I guess all this stuff really belong in a KEEP, no?

(*) Actually a much better solution would be to make the import system smart, and some syntax to toggle wildcard import of extension types on and off (off by default). It’s a bit unorthodox, but a good compromise (also see this).

3 Likes

FYI, I posted a few additional thoughts on ad-hoc polymorphism here.

I know :slight_smile:

Something I don’t like about your proposal though: that the extension functions implementing the extension type are not encapsulated somehow.

If you already have an extension function with the same name as a method in your interface (say zero for Monoid ), things get awkward and you have to disambiguate in some fashion.

I would like it to be like this:

override class String: Monoid<String> {
    fun zero() : T { ... }
    fun append(a1: T, a2: T) : T { ... }
}
val x = "cthulhu".zero() // illegal
val y: Monoid<String> = "cthulhu"
val z = y.zero() // okay

I believe the request to adapt something to something else is quite common, and that Kotlin could support it, but with an explicit conversion rather than implicit.

To make those explicit adapters less wordy, we could first implement KT-505 (delegating by signature), and second — provide some syntax to be able to write Monoid(stringValue) instead of object : Monoid by stringValue {}

While I don’t have a concrete opinion on this subject, I thought in something less aggressive:

[structural] inline fun <reified T> doAdd(a: T, b: T) = a.add(b)

(Yes, I don’t like structural either, just an example.)

Since this method doAdd would be inlined by the compiler, it could be used with any T type which implements a add(other: T) method. The bytecode would be optimal (again, the function would be inlined, so I expect the add method call to be a natural invokevirtual operation.

Another example:

[structural] inline fun generateAccountId(value: Any) = "${value.name}@${value.id}"

Would work for any type which has id and name fields.

1 Like

This looks like poor man’s templates (as in C++ templates) restricted to functions only. Looking at C++ and JVM implementation it will create a lot of duplicate code (no good way to deduplicate it at linker level like is done in C++) without even encapsulating it in a function. The bigger problem is that it tends to create very hard to understand error messages when these are nested.

Also like this .
:joy:

1 Like

I was looking at this and thinking about how to avoid implicits, but it seems like if that’s an important design parameter, typeclasses can/should just be done in the existing type system with an adapter-like pattern. I don’t like it, but having some way to do it is better than nothing. Sticking with @alex_delattre’s proposed syntax, here’s what it would look like:

interface Monoid<A> {
    fun zero(): A
    fun add(a1: A, a2: A): A
}

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

fun<A> multiply(a: A, n: Int, M: Monoid<A>) {
    var result: A = M.zero()
    for (i in 1 to n) {
        result = M.add(result, a)
    }
    return result
}

That seems like more-or-less the least common denominator of what JetBrains is willing to give us and the functionality people want (and it’s already possible).

One language feature that would make this pattern nicer (and maybe not break everything) is currying. Obviously we can mock currying with HOFs, but there’s non-negligible friction in doing things that way. With currying we could just do something like:

fun<A> multiply(M: Monoid<A>)(a: A, n: Int) {
    // ...
}

fun<A, B> funUsingTwoMonoids(aMonoid: Monoid<A>, bMonoid: Monoid<B>) {
    val aMult = multiply(aMonoid)
    val bMult = multiply(bMonoid)
    // do something, multiply some stuff
}

KEEP for type classes Compile-time Extension Interfaces by raulraja · Pull Request #87 · Kotlin/KEEP · GitHub

1 Like

It sounds more like adding extension “static” functions for a “Class” rather than the “instances”. (Maybe is the extension of its companion object?)
So this case is different from that of “trait”.
But I don’t is more useful to inject a static function into another class. It cannot reuse more code. I prefer to write the static function under my own class name, after all doing so can make me less mix them up.

Does anyone know if and when this will ever be added?

I am trying to implement a form of multi-inheritance where a class implements several interfaces from other modules. Perhaps there is another way. Consider the following example.

I have a BaseFragment in my core library module that extends from the support Fragment class.

class BaseFragment:Fragment(){ ... }

Then I have one module that handles fragment routing and one for fragment styling configuration where I have an interfaces that I want the BaseFragment to conform to:

interface IFragmentRoutable {
    fun didReceiveRouteVars(vars: List<RouteVar>?)
}
interface IFragmentStyleable {
   // NOTE: only showing one of the interface methods 
    fun onStyleConfigUpdated()
}

In order solve this, I can simply create extension functions for my BaseFragment, within each respective module to allow it to implement the respective methods like so:

// RouteVar class comes from Routing module
fun BaseFragment.didReceiveRouteVars(vars: List<RouteVar>?){}

fun BaseFragment.onStyleConfigUpdated(){ 
    // code that uses stuff from Style Config module 
}

But then BaseFragment would not be implementing the IFragmentRoutable or IFragmentStyleable interface. Also I have heard and read that using extension functions for something like this is not the best practice anyways.

The other option of course would be to just make a base class for each; BaseRoutableFragment BaseStyleableFragment where each implements their respective interfaces, but then I also need a base class for the combination of these; BaseRoutableStyleableFragment which yields redundant code, which I would like to avoid.

I want to be able to make BaseFragment implement the desired interface within the scope of each module.

Something like this:

// In Routing module
extend class BaseFragment : IFragmentRoutable {
    didReceiveRouteVars(vars: List<RouteVar>?){}
}

But unfortunately that is impossible, but perhaps there is another approach that Im missing?

Classical way to solve that is the adapter pattern.

class FragmentRoutableAdapter(val bf : BaseFragment) : IFragmentRoutable { ... }

But it’s a little heavy.
In java, with aspectj (or spring AOP), you can use ‘declare parents’ that do the trick.

declare parents: Foo implements List<String>

in your case,

declare parents: BaseFragment implements IFragmentRoutable 

I don’t know if it’s transposable in Kotlin.

It is possible to do it in context-oriented way like:

class A{
  fun O.doSomethingA()
}

class B{
  fun O.doSomethingB()
}

with(a){
  with(b){
    o.doSomethingA()
    o.doSomethingB()
  }
}

but this approach is limited to public properties and could look not very pretty without KEEP-176.

While I agree with the general sentiment re the preference toward nominal typing, in this case structural typing feels like the better solution; in my view it’s much less complicated than the other proposals presented here. This should not be an either/or proposition – structural typing should only be used when nominal typing is unsuitable for the task; there’s no reason why they should not coexist… aside from making the type system innards more complicated :wink:

Also as someone pointed out earlier, you can use extensions to make a class satisfy a structural interface. The Manifold framework provides Java with both Structural Interfaces and Extensions, which have proven to be a powerful combination when I’ve needed it.

Maybe pushing it here, but providing a dynamic API for structural typing is also a nice feature to consider. For example a Map can potentially satisfy any structural interface if an extension connects Map with a language-provided “call-handler” API. This is how, for example, JSON Schema and GraphQL SDL are type-safe within Java (via Manifold) without a code generation step. And also how the Manifold IntelliJ plugin makes it all flow after making schema changes – without compiling.

Okok, this has gotten somewhat off topic, but these are the types of features necessary to level the playing field with dynamic languages / metaprogramming. There’s a reason languages like python, ruby, etc. are widely used, and it’s not because they are better languages.

What about something like this?

extension MonoidString for String : IMonoid {
    fun zero() = ""
    fun append(other: String) = this + other
}

fun doMonoidStuff(m: IMonoid) {
    ...
}

fun main() {
    val someString = "hi world"
    doMonoidStuff(MonoidString() => someString)
}

I think it’s as explicit as it gets.

That looks like a type class feature. In my opinion, using the companion values feature keep 106 leads to more idiomatic kotlin code:

interface IMonoid<A> {
  fun zero(): A
  fun A.append(other: A): A
}

class StringMonoid : IMonoid<String> {
  override fun zero() = ""
  override fun String.append(other: String) = this + other
}

fun <A> IMonoid<A>.doMonoidStuff(a: A) {
    ...
}

fun main() {
  // the companion val can go into different scopes.
  // in this example we are scoping it locally in this function:
  companion val stringMonoid = StringMonoid()

  val someString = "hi world"
  doMonoidStuff(someString)
}

In this example, everything is ordinary Kotlin, except for the companion val, which is the proposed feature of keep 106. It basically is just syntactic sugar for:

fun main() = with(StringMonoid()) {
  val someString = "hi world"
  doMonoidStuff(someString)
}

I like the idea, but what if we, for some reason, wanted to use different implementations for different calls:

class StringMonoid : IMonoid<String> {
  override fun zero() = ""
  override fun append(other: String) = this + other
}

class SpacedStringMonoid : IMonoid<String> {
  override fun zero() = ""
  override fun append(other: String) = "$this $other" //probably a bad example
}

fun <A> IMonoid<A>.doMonoidStuff(a: A) {
    ...
}

fun main() {

  private companion val stringMonoid = StringMonoid()

  val someString = "hi world"
  doMonoidStuff(someString)
  doMonoidStuff(someString) //I'd like to use the `SpacedStringMonoid` here
}

I don’t know whether there is a solution to this scenario in keep 106.

If there are natural scopes in your code, you can use them. For example if-blocks.

The ultimate fallback is creating the scope yourself with the “with” scoping function. The cool thing about this feature is that the monoid is an ordinary interface, and its implementations are ordinary classes. Therefore they can be used in ordinary ways also.

(Note: I forgot to declare the receiver for the append function. I fixed that in my code above now)

1 Like