-
Notifications
You must be signed in to change notification settings - Fork 368
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
f9f81c5
8b80889
89c90d7
554ab48
82e4758
29442c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. override fun setTitle(text: String) = titleView.setText(text) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you mean "memoize"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
But I would l would like to hear whar others are thinking about it. |
||
} | ||
|
||
class A { | ||
fun a(val a: Int) by Delegate() | ||
} | ||
``` |
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
=
.Unit
as well:override fun deleteListElementAt(position: Int) by adapter::deleteAt
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 :)
There was a problem hiding this comment.
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