Support for clojure protocol like class extensions and/or union types/type aliases?


#1

Hey all,

I was wondering if any one in the Kotlin team had thought about anything akin to clojure protocols [1].  What I’m thinking about is a way of defining a set of methods for an extension contract, something maybe like:

interface Fulfillment<T> {
  fun T.invoice();
  fun T.manifest();
}

then I can have an implementation of:

class OrderFulfillment<Order> }
  fun Order.invoice();
  fun Order.manifest();
}

I suspect what I really want here is support for union types/type aliases:

alias Fullfillment [ Order, Contract ];

fun Fulfillment.invoice()…
fun Fulfillment.manifest()…

and have new code that accepts Fullfillment classes, which is one of its member types ( Order, Contract );

I guess at the compiler level, you’d just have any extension function using the alias compile stubs for each type, and an internal method which took Object.

I don’t ask much do I? :slight_smile:

[1] http://www.ibm.com/developerworks/library/j-clojure-protocols/#expression


#2

We were thinking about this problems. More along the lines of Scala's inmplicit classes (although, we don't want implicits as such) Your suggestion about union types sis not occur to us. Interesting idea, but I don't think it is the right way to go. What has to be solved here is the following set of problems:

  • Currently there's no way to make sure that extensions to some type fulfill some interface
  • Currently there's no way of reusing extension code on unrelated classes. Example: arrays and lists all have the same extensions, but it's all done by code duplication, since there's no means of reusing the code properly.

The second problem suggests some kind of inheritance here. For example (the syntax is imaginary):

``

trait Emptiness {
  fun isEmpty(): Boolean // no implementation
  fun isNotEmpty(): Boolean = !isEmpty // implemented
}

extension class ArrayEmptiness<T>(for Array<T>) : Emptiness {
  override fun isEmpty() = size == 0
}

extension class CollectionEmptiness<T>(for Collection<T>) : Emptiness {}


The “extension class” is nothing but a bag of function that all become extensions to whatever type mentioned in the header.
This way we still have to define extensions explicitly, but we can ruse code.
To me this model looks more flexible than union types, although not perfect.

#3

Something like that based on traits would certainly go a long way, however doesn't really help with the expression problem which I'm trying to think up ways of solving.

In the example given, being to then write something like:

fun processEmpty(e: Emptiness) {
  if (e.isEmpty()) {
  …
  }
}

processEmpty(Collection());

Whilst we have a means to extend classes with a set interface of behaviours, we can’t say “this method takes anything that has been extended to Emptiness”, unless somewhere we define that somewhere. Clojure wins here with its dynamic dispatch, which doesn’t help us in a static language tho - and even less if we somehow want to do it in a modular fashion. I guess this is where going to a more ‘structural subtype’ system comes into play, as that can be statically checked, but enforced/resolved at runtime.


#4

You want to make something extended to be Emptiness conform to it dynamically. We will not do this implicitly (like Scala does), but we could provide explicit means, e.g.

processEmpty(array.toEmptiness())


#5

I've pondered the exactly same question without finding a satisfactory answer. Interfaces in Mozilla's Rust language are the closest I've seen to a statically typed version of Clojure's protocols.

http://dl.rust-lang.org/doc/tutorial.html#interfaces

See 15.6 “Casting to an interface type”. Conversion to the interface (extension) type is done explicitly with a cast. The interface implementation for your class needs to be in scope which is the obivous difference from Clojure but inevitable in a statically typed language.


#6

Interesting - I'd been pondering something similar too.

Its been quite tricky defining extension functions for the standard library which have different behaviours (e.g. iterators v collections v lists v arrays) with eager or lazy behaviour (iterators are lazy) and then try to ensure consistent APIs - whilst reusing mostly the same code for many instances of the extension functions on different types etc. Right now we’ve icky code generation which is really hard to understand & reason about. I’d previously wondered about using structural typed delegation as a hack to check the shapes of extension functions.

As an experiment I've tried an experimental refactor to reimplement the standard library for traversible kinds (collections/arrays/iterators etc) to use stateless traits instead; then have a way to bind the traits to concrete classes with a single parameter (the 'this') to make the extension functions. I tried to stick to using regular kotlin code with just 1 annotation used on the binding of traits to extension functions (which is the only bit thats not working right now, actually making the extension functions from an 'extension class' ;).

I’m quite blown away by this approach! Its really helped find lots of issues with the extension function implementations; has really helped arrange and organise the functions so its much clearer whats going on; the code is now much simpler and more reusable. It’d be easy for anyone to take any of these traits and override/extend and apply to any other classes too. e.g. to apply a set of extension functions to a type we can use the tried and tested classs-implements-trait mechanism and let the IDE auto-complete the methods required.

The nice thing is, there’s no real need for any new language syntax really; its just an annotation; then the compiler just needs to process classes annotated with ‘extension’ in a different way to generate the extension functions. Meanwhile we can reuse all the good stuff in the language/IDE to help arrage extension functions.

An added bonus is, there’s also a real JVM level trait generated for all the APIs and typesafe statically typed proxies applying the traits to the underlying types too. So code could accept a Traversable<T> if it wanted to.


#7

I just raised an issue for this idea BTW

http://youtrack.jetbrains.com/issue/KT-2429

I think its great :). Don’t mind if there’s special language syntax for defining these ‘extension classes’ or whether its just an extension annotation.


#8

I also need this~