Implicit interfaces


#1

I think Kotlin as a language could be even greater if it has a form implicit interfaces (after the release of 1.0).

By that I’m referring to Go style interfaces. For those unfamiliar with this: basically an interface in Go is a type and any class that conforms to that interface can be used as that type. All this without implementing/inheriting from that type. This still has the typesafety we expect from a statically compiled language and the flexibility from a dynamic typed language. We can avoid poluting our models completely with all types of interfaces just to conform to some framework. In a way it’s ducktyping done right.

Let me elaborate this with a reincarnation of the trait keyword, e.g:

trait Foo { //imagine this is an implicit interface declaration   fun close() }

class Bar { //no inheritance
  fun close()
}

fun doSomething(foo:Foo) {//…}

fun main(args:Array<String>) {
  val bar = Bar()
  doSomething(bar) //works because Bar has the same traits as Foo
}


This allows for all kinds of libraries to provide a well defined interfaces without enforcing all implementors to polute their models with all kinds of interfaces. This can avoid all kind of ‘nonsense’ like the enormous amound of logging frameworks and delegation implementations required by most java projects nowadays.  

The hard part (for me) is of course the implementation. I can imagine something like this:

trait Foo {   fun one() : String   fun two() : Int }

fun bar(foo: Foo) {
  //…
}

being compiled down to:

interface FooOne {   String one(); } interface FooTwo {   int two(); }

publlic void bar(Object fooObj, FooOne one, FooTwo two) {
//…
}

This has the serious drawback parameter limits of java and probably some others difficulties. But I can imagine this trait/implicit interface construct comes (and should come) with serious limits.


For-each on Iterator?
#2

What do you gain from this? You saved having to write " : Foo" after "class Bar", but now the compiler won't tell you when you are failing to implement Foo correctly, which is an easy mistake when you have interfaces with more complex method signatures.

Rob


#3

The benefit of such things is that you can create a new interface and existing classes automatically implement the interfaces. Just like it is done now with operator, if your class has a "plus()" method, it would implement the "Plus"-Interface implicitly without the need to change the class.

Personally, i dislike this idea, because something happens implicitly just like scala implicits.
You’d have to look up an existing Interface to check if your class implements it,
and if you want to do it in an IDE, it would mean a lot of work for the IDE to check every available interface against all classes.


#4

The gain is that you can write code that accepts classes which comply to some signature without having to implement a specific interface. For instance classes you don't have control over because they come from an external jar. Note this is a proposition that actually does let the compiler fail if the signature requirement hasn't been met. It a statically proveable proposition, not some runtime magic.


#5

Oh no, please don't say Scala. I'm proposing nothing of such sort. No classpath magic or that sort of incomprehensible things. It's (in theory) quiet simple. It's ducktyping, when something looks like a duck, it is a duck. This is something that can statically be proved as Go-lang demonstrates. When I have for instance a signature:

trait Disposable {   fun close() }

and some class:

class FileResource() {   fun close() }

And some method:

fun doClose( d: Disposable)

I can statically prove that FileResource conforms to the signature of Disposable and thus can be passed into the doClose() method. To make this work this can be compiled down to some lambdas and thus by passing method handles. With the earlier stated limitations of course. The result is I have a function that just accepts classes that have a close() method, statically proven.


#6

So does implicits in scala, they are statically proven and they work well in a statically typed language, but its the implicit thing that bothers me. Just like implicits, there is not really an indication about what the class implements or not.

You have to check what your class needs to implement to be compliant with an interface,
atm intellij will tell you what you need when implementing an Interface (Yes intellij could do that for you too, but it needs to check every interface against every class and this in realtime)

If you change a class, suddenly another part fails because your class doesnt implement an interface anymore.

Another example if you have a large hierarchy of classes/interfaces. If i want to know which class in the hierarchy implements a specific interface, currently i can just look at the class definitions,
if it is implicit i need to look up the specific methods needed for the interface, the methods can even be scattered through the whole hierarchy.

I dont say its bad per se, it got some advantages,
but personally I dont like it


#7

You have to check what your class needs to implement to be compliant with an interface, atm intellij will tell you what you need when implementing an Interface (Yes intellij could do that for you too, but it needs to check every interface against every class and this in realtime)

No really you don't. There is no class and interface matching at all. What I'm proposing is more like syntax sugar than actual enrichment, wrapping or other sorts enhancement. Compare it to extension methods if you will, It's just a different way of writing things. Let me explain with Kotlin code:

trait Foo {   fun one()   fun two() }

class Bar {
  fun one() = //…
  fun two() = //…
}

fun some(foo:Foo) = //…
val b = Bar()
some(b)


method some(foo:Foo) could be translated (syntax sugar) as:


fun some(src:Any, one: () -> Unit, two: () -> Unit) = //…
val b = Bar()
some(b, b::one, b::two)


So nothing changes to actual class Bar and all calls just give the regular feedback like any other method.

So does implicits in scala, they are statically proven and they work well in a statically typed language, but its the implicit thing that bothers me.

Don't stumble over the word `implicit` here, it's just what they happened to be called in Go-lang, they just as easily could have been called "happy-fluffy-bunny" ;-)


#8

So there won't be class/interface matching inside Intellij? Does that mean if you change a class you'll notice that you break another part of your program only when compiling?

Small example:

class A {   fun a() { .. } }

class B: A() {
  fun b() { … }
}

class C: B() {
  fun c() { … }
}

trait TraitA {
  fun a()
}

fun some(traitA: TraitA) {
  …
}

val c = C()

some©


So when you change A.a() to A.a(String), you’ll break the code some©, because suddenly C doesnt have the right methods for TraitA anymore.

This means that Intellij won’t tell you that you broke the contract of an interface, instead you have to keep track of that yourself
or else get errors when compiling.


#9

This means that Intellij won't tell you that you broke the contract of an interface, instead you have to keep track of that yourself or else get errors when compiling.

I do not know the innerworkings of IntelliJ enough to make a decent claim. Why do you think(/know?) IntelliJ can't tell you such thing?

I can imagine that IntelliJ maintains an abstract syntax tree which has al sorts of relations, meaning changing a trait will lead to events that instantly tell you which code breaks. Much in the same way changing the signature of a method parameter from, for example, long to byte. This wouldn’t be any different. As for code completion I can imagine you can do boolean searches on that same abstract syntax tree so that would be quick too, without rescanning or indexing. But just to be clear, that’s just me imagining.  


#10

Yes, that would mean that Intellij needs to check every interface against every class, which you said earlier wouldn't be needed. If Intellij would present you the currently implemented interfaces of a class, that would be ok.

But what about looking at source code directly on github or using vim/emacs/whatever to code in kotlin?


#11

On what bases do you claim this? Do you really know this by a fact or do you just think this is necesary?

Because looking at the PSI Structure of an IntelliJ project I can just as easily query for / lookup classes implementing interfaces as classes containing methods (this would be traits). Seems no difference there.


#12

IMO it should be necessary, because what I really like about Intellij compared to VS is, that it filters/sorts the list according to what type of field is required. So if the method requires a String, all available Strings are shown at the top of the list.

If you check if class A implements all methods from Trait B only when passing A for B, then you’ll get an alphabetically ordered list of available instances
and get errors after choosing an instance.


#13

Okay, so you are just assuming. That's fine but please be clear about it as it helps the discussion.


#14

The Nice Programming Language has this (an experimental JVM language, pretty old)

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

Nice adds a new interface type called an Abstract Interface, which can only be used as the upper bounds on a Generic.  Also Class can not explicitly implement Abstract Interfaces.  However you could pass a class in for an Abstract Interface if it satisfied the Interface, the compiler would check this for you at compile time.  Nice approach works because abstract interface are contract, not a type, more a compiler trick, like a type safe marco.  Similar to Kotlin’s Reified Generics

http://kotlinlang.org/docs/reference/inline-functions.html

An exmple of this (made up) in Kotlin

abstract trait ACloseable {   fun close() }

//This “use” function works on any class with a “close” method, not just those that implement "Closeable"
inline fun <reified T: ACloseable,R> T.use(fn: T->R) {
  try {
  fn(this);
  } finally {
  this.close();  //This works because ACloseable.close
  }
}

val bytes = InputStream(“made up”).use { stream->
  stream.read();
}

You can never introspect a Class to see if it is a AClosable because it is an Abstract Interface and no classes can implements it.

//this would create a compiler error val stream = InputStream("made up") if ( stream is AClosable ) {   stream.close() }


#15

My point of view is that an interface is a contract about how an object INTERACT with other objects . If so, it is actually good that a class must implement an interface explicitly. On the other hand, duck typing really make it easier to extend an existing hierarchy of classes. I think that in the spirit of kotlin, that already rely heavily on extension functions, the language could add the concept of "extension trait" (sorry cannot think of a good name), in other words something like an extension function that is actually an implementation of an interface for a specific class. The IDE can automatically generate the method implementations based on existing methods whose signature matches the method to implement). This way you have the flexibility of duck typing and still a well defined contract on how a class implement some specific interface


#16

I agree with you that `interface` denotes a contract that must be implemented explicitly. I'd like to think of the two concepts as follows:

  • interface: `is-a` relationship. So this is actually a concrete type.
  • trait: `has-a` relationship. A class just has, as the word implies, some traits.
So I won't expect this to work:
trait A {...} val obj = foo()

if (obj is A) // won’t expect this to work as a trait wouldn’t be a concrete type


And indeed, there is great benefit in extending and external libraries on which you don’t have any control of but don’t want tight coupling with.


#17

This is exactly what I mean. Thanks. I think the language could benift from this.


#18

This was discussed earlier. See this thread.


#19

How embarrassing, I even contributed to that thread :_|. I searched though before opening this thread. No results showed up.


#20

Don't blame yourself. The search function is totally broken.