By Convention vs Interfaces


#1

Kotlin provides many features by convention, like operator overloading; however, there is no way for a method to specify it only accepts objects which impliments a given operator (or set of operators).  For example if I wanted to write a generic method which requires its arguments implement basic math operations plus(+) and minus(-) there is no way to specify this.  

“By convention” operator overloading is great, it just works; however, in this case there is no super interface to represent the objects support basic math, the function cannot be written with generics.  The only option is to rewriting the same code for different types.

A solution is to allow duck typing, or more duck casting.  For example

trait BasicMath<T>: Comparable<T> {

  fun plus( b: T) :T

  fun minus( b: T): T

}

fun <T:BasicMath<T>> subtractTo(a: T, b: T, limit: T) : T {

  val value = a - b

  if (value < limit) {

  return limit

  } else {

  return value

  }

}

//and to use the function, compiler does magic

val result = subtractTo(1,2,0)

//or you do it for it

val r2 = subtractTo(3 as BasicMath<Int>, 2 as BasicMath<Int>, 0 as BasicMath<Int>)

This comes down to allowing forcefully casting an object to a trait it did not impliment but does satisfy.  

Thoughts:

  1. Syntax: what is perposed looks bad on the second method call?
  2. Many Traits: how could this be supported?
  3. Other ways of doing this?
  4. Cyclon: supports type intersections and unions, maybe something like that?


Thanks

  • Matt

#2

The biggest question is how this is to be implemented in the byte code. The only way would be to emit interfaces and/or classes whenever such a "magic" function is written. This looks like too many classes (permgen/jar size overhead) for little gain.


#3

C++ like macro/generics would work, but kotlin has more java like generics


#4

You can always do all that magic manually: implement the necessary interfaces in-place, for example, using with object expressions.

You are right that C++ templates are more powerful than a generic type system like Kotlin’s, and there are always cases when you can do something with templates that you can’t do in Kotlin.


#5

May be add code to check and get method in runtime by reflection?

for example:

Interface: I {
  fun val() : int
}

fun sum( f : duck I, s duck I ) : Int {
  return f.val() + s.val()
}

compiled any similar to:
fun sum( f : Any, s : Any ) {
  if (! f has method val() -> Int ) throw DuckException();
  if (! s has method val() -> Int ) throw DuckException();
  m1 = get method from f
  m2 = get method from s

  return m1(f) + m2(s)
}


#6

There is another peculiarity of calls by conventions: function which actually gets called can be extension, which is present in context. Finding appropriate extension functions in runtime with the same semantics can't be implemented.


#7

Reflection is very slow