Cascaded messages in Kotlin?


#1

Hello,

I would like to do this:

public fun <T> List<T>.yourself() : List<T> {
  return this
}

public fun main(args : Array<String>)
{
  val list = ArrayList<Int> add(1).add(2).add(3).yourself()
  println(list)   // prints [1, 2, 3] - hence list is an instance of List
}

Now this doesn’t work for the same reason it does not work in Java: List.add(…) returns a boolean and the second add method in the calling chain cannot be found in class Boolean and the thing doesn’t compile. In Smalltalk this would work, though:

| c num |
c := OrderedCollection new add: 1; add: 2; add: 3; yourself.
Transcript cr; show: c.  // prints an OrderedCollection(1 2 3)
num := OrderedCollection new add: 1.
Transcript cr; show: num.  // prints 1

The point is that this works in Smalltalk although the OrderedCollection.add method in Smalltalk also does not return this (aka self) but the object added to the collection. In Smalltalk this is called cascaded messages. I would like to ask whether something like this is being considered for Kotlin. IMHO not only syntactic sugar, but let’s you make more concise code that is still easy to read and understand.

Regards, Oliver


#2

Hello Oliver,

Not sure I can take into account all possible usages of cascade messaging in Smalltalk, but you can try to apply the following extension function for your example:

public fun <T>T.process(operations : T.() -> Unit) : T {   this.operations()   return this }

public fun main(args : Array<String>) {   val list = ArrayList<Int>().process { add(1); add(2); add(3) }   println(list) // [1, 2, 3]

  // Remove first two elements - example of multiline syntax
  list.process {
  remove(0)
  remove(0)
  }
  println(list) // [3]
}

Calling “process” function for a newly created ArrayList could be considered as grabbing receiver with further passing it to the parameter extension function. Instead of calling “yourself” “process” returns stored receiver.

Should also mention that in this particular example a standard function arrayList will make code even neater:

public fun main(args : Array<String>) {   val list = arrayList(1, 2, 3)   println(list) }


#3

Hi Nikolay,

I see what you mean. Would be a nice workaround in case those dearly missed cascaded messages won’t make it into the language ;-). I’m not sure how important they are. It was merely a question …

Thanks, Oliver


#4

Hi Oliver,

I think that this feature is too narrowly applicable to be added to the language.
In addition to Nikolay’s workaround, I can offer a different name for your add:

``

fun <T> List<T>.put(x : T) : List<T> {
  add(x)
  return this
}

fun test() {
  val list = ArrayList<Int>().put(1).put(2).put(3)
}


#5

public fun <T>T.process(operations : T.() -> Unit) : T {   this.operations()   return this }

Interesting trick. Does this mean that the process method is being added to any Kotlin object?


#6

Yes, it will look like any object has this method after importing "process" function into your scope.


#7

abreslav schrieb:

Hi Oliver,

I think that this feature is too narrowly applicable to be added to the language.

Yeah, I understand it's merely something you get on the fly when the language is based on message passing anyway. By the way ... I know, this guy can't stop talking about Smalltalk ;-). In Smalltalk a method that does not return something explicitly at the end of the method body always returns self. This feature and cascaded messages make up for a nice combination. I believe when a language is dynamically typed anyway this is an easy thing to do, but makes things much more difficult when the language is statically typed.

Regards, Oliver