Skip to content

Add function delegation proposition #89

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions proposals/function-delegate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Function delegate

* **Type**: Design proposal
* **Author**: Marcin Moskała
* **Contributors**:
* **Status**: Proposition
* **Prototype**: Proposition

## Feedback

Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/25).

## Summary

Support function delegates - delegae that can be used on to delegate function calls.

## Description

Allow to use function type as a function delegate:

```
interface MainView {
fun clearList()
}

class ListAdapter {
fun clear() {}
}

class MainActivity : MainView {
val adapter = ListAdapter()

override fun clearList() by adapter::clear
}
```

## Motivation / use cases

Two main motivations behind this feature are:
* Simplify function calls passing that is opened for function signature changes.
* Allow wrapper functions with state to add catching, synchronization and other functionalities that needs state.
Let's discusse them one after another.

### Function calls passing

When we are keeping strict architectures, like MVP or Clean Architecture, we often need to pass call from one class to another. For example, when Presenter is deciding that list needs to be changed it needs to call Activity function which is passing this call to adapter which is managing elements on list:

```
interface MainView {
fun deleteListElementAt(position: Int)
}

class ListAdapter {
fun deleteAt(position: Int) {}
}

class MainActivity : MainView {
val adapter = ListAdapter()

override fun deleteListElementAt(position: Int) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make comparison fair I'd convert that to expression body:

override fun deleteListElementAt(position: Int) = adapter.deleteAt(position)

Copy link
Author

@MarcinMoskala MarcinMoskala Oct 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It changes nothing, and I am not using single expression functions for expressions that are returning Unit because this is misleading for a person who is reading it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It changes nothing

Current example takes 3 lines of code without delegate, with delegate it takes 1. But in simple cases delegate only omits function arguments in compare to expression body =.

and I am not using single expression functions for expressions that are returning Unit because this is misleading for a person who is reading it.

  • In your own examples delegate hides return type Unit as well: override fun deleteListElementAt(position: Int) by adapter::deleteAt
  • You can always specify return type explicitly if you want override fun deleteListElementAt(position: Int): Unit = adapter.deleteAt(position)

Just want to remove bias from the syntax comparison, so far I'm positive about this proposal :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sound fair. I will change it then

adapter.deleteAt(position)
}
}
```

Simpler and more agile implementation would be if we could use function delegate instead:


```
class MainActivity : MainView {
val adapter = ListAdapter()

override fun deleteListElementAt(position: Int) by adapter::deleteAt
}
```

Similarly, when we are passing calls to change specific trait of layout, we need to pass function call to specific change on layout:

```
interface MainView {
fun setTitle(text: String)
}

class MainActivity: MainView {

override fun setTitle(text: String) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

override fun setTitle(text: String) = titleView.setText(text)

Copy link
Author

@MarcinMoskala MarcinMoskala Oct 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It changes nothing, and I am not using single expression functions for expressions that are returning Unit, because this is misleading for a person who is reading it.

titleView.setText(text)
}
}
```

We would replace it with function delegate:

```
class MainActivity: MainView {

override fun setTitle(text: String) by titleView::setText
}
```

Note, that when we are operaring on function type, we can use all functional features like currying or extension functions.

### Function wrapper implementation

Sometimes we need to add functionalities to function that needs to hold state. For example, when we need to add buffering to function. Let's say we have fibbonacci function:

```
fun fib(i: Int) = if (i <= 2) 1 else fib(i - 1) + fib(i - 1)
```

Without buffering it is very unefficient. Using function delegation we might add buffering this way:

```
fun <V, T> memorise(f: (V) -> T): (V) -> T {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean "memoize"?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, memoize is a better name for this function

val map = mutableMapOf<V, T>()
return { map.getOrPut(it) { f(it) } }
}

fun fib(i: Int): Int by memorise { i -> if (i <= 2) 1 else fib(i - 1) + fib(i - 1) }
```

Similarly synchronization for single function could be added.

## Implementation details

Function delegate could be compiled to delegate holden in seperate property and its invocation in function body. Invocation should include all the paramters defined by function. For example, above `fib` function with `memorise` should be compiled to:

```
private val fib$delegate: (Int) -> Int = memorise { i -> if (i <= 2) 1 else fib(i - 1) + fib(i - 1) }
fun fib(i: Int) = fib$delegate(i)
```

similarly `setTitle` will be compiled:

```
class MainActivity: MainView {

private val setTitle#delegate = titleView::setText
override fun setTitle(text: String) = setTitle#delegate(text)
}
```

## Alternatives

If we are using property instead of function when we can get all the functionalities. For example:

```
val fib: (Int) -> Int = memorise<Int, Int> { a ->
if (a <= 2) 1
else (::fib).get()(a - 1) + (::fib).get()(a - 2)
}
```

The problem with this alternative is when we are using recurrence (because property is not defined yet). Also there is probem with interfaces, because we would have to replace functions with properties with function types:

```
interface MainView {
val clearList: ()->Unit
}

class ListAdapter {
fun clear() {}
}

class MainActivity : MainView {
val adapter = ListAdapter()

override val clearList: ()->Unit = adapter::clear
}
```

## Other notation proposition

Instead of `as` keyword, we could use simpler notation:

```
fun fib = memorise<Int, Int> { a -> if (a <= 2) 1 else fib(a - 1) + fib(a - 2) }
```

Type of arguments would be inferred from delegate. Argument types would be inferred from delegate.

## Open questions

Is `by` keyword a good choice while it is reminding property delegation, and as oposed to it, here are are not passing context and function reference? What if one day we would want to make function delegate that actually acts like property delegate:

```
class Delegate {
operator fun functionDelegate(thisRef: Any?, function: KFunction<*>, val property: Int): Int // Designed for this function
operator fun functionDelegate(thisRef: Any?, function: KFunction<*>, vararg arguments: Any?): Int // Desifned for any functio
Copy link

@damianw damianw Oct 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a better and more general solution -- there's no reason that the rest of the above proposal couldn't be implemented using such operator functions on KFunction[X]. I feel like the proposal can just be reduced to support of something like operator fun functionDelegate(thisRef: Any?, function: KFunction<*>, vararg arguments: Any?). I'd like to see this idea expanded upon... especially the arguments part.

Copy link
Author

@MarcinMoskala MarcinMoskala Oct 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fact is that would be more coherent with property delegation, and I can also make extension functions that would allow previous use-cases:

fun (()->Unit).functionDelegate(thisRef: Any?, function: KFunction<*>) = this()

fun <T1> ((T1)->Unit).functionDelegate(thisRef: Any?, function: KFunction<*>, arg1: T1) = this(arg1)

fun <T1, T2> ((T1, T2)->Unit).functionDelegate(thisRef: Any?, function: KFunction<*>, arg1: T1, arg2: T2) = this(arg1, arg2)

But I would l would like to hear whar others are thinking about it.

}

class A {
fun a(val a: Int) by Delegate()
}
```