From f9f81c5a1b65a59f9da4dcdc23caa9a167bcf696 Mon Sep 17 00:00:00 2001 From: marcinmoskala Date: Sat, 30 Sep 2017 03:04:27 -0700 Subject: [PATCH 1/6] Add function delegation proposition --- proposals/function-delegate.md | 195 +++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 proposals/function-delegate.md diff --git a/proposals/function-delegate.md b/proposals/function-delegate.md new file mode 100644 index 000000000..28ee13a2c --- /dev/null +++ b/proposals/function-delegate.md @@ -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) { + 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 memorise(f: (V) -> T): (V) -> T { + val map = mutableMapOf() + 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 { 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 { 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 +} + +class A { + fun a(val a: Int) by Delegate() +} +``` From 8b80889083382f95ab48ba5fa67cb2e94bb5bce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Moska=C5=82a?= Date: Tue, 10 Oct 2017 17:36:20 +0200 Subject: [PATCH 2/6] Change `memorize` name to `memoize` --- proposals/function-delegate.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proposals/function-delegate.md b/proposals/function-delegate.md index 28ee13a2c..31a2626be 100644 --- a/proposals/function-delegate.md +++ b/proposals/function-delegate.md @@ -111,22 +111,22 @@ 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 memorise(f: (V) -> T): (V) -> T { +fun memoize(f: (V) -> T): (V) -> T { val map = mutableMapOf() 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) } +fun fib(i: Int): Int by memoize { 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: +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 `memoize` should be compiled to: ``` -private val fib$delegate: (Int) -> Int = memorise { i -> if (i <= 2) 1 else fib(i - 1) + fib(i - 1) } +private val fib$delegate: (Int) -> Int = memoize { i -> if (i <= 2) 1 else fib(i - 1) + fib(i - 1) } fun fib(i: Int) = fib$delegate(i) ``` @@ -145,7 +145,7 @@ class MainActivity: MainView { If we are using property instead of function when we can get all the functionalities. For example: ``` -val fib: (Int) -> Int = memorise { a -> +val fib: (Int) -> Int = memoize { a -> if (a <= 2) 1 else (::fib).get()(a - 1) + (::fib).get()(a - 2) } @@ -174,7 +174,7 @@ class MainActivity : MainView { Instead of `as` keyword, we could use simpler notation: ``` -fun fib = memorise { a -> if (a <= 2) 1 else fib(a - 1) + fib(a - 2) } +fun fib = memoize { 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. From 89c90d786eb12862ec674af7a279ce27117544f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Moska=C5=82a?= Date: Thu, 12 Oct 2017 09:34:45 +0200 Subject: [PATCH 3/6] Change functions to single-expression The reason is to eliminate bias from the syntax comparison --- proposals/function-delegate.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/proposals/function-delegate.md b/proposals/function-delegate.md index 31a2626be..236ce187d 100644 --- a/proposals/function-delegate.md +++ b/proposals/function-delegate.md @@ -57,9 +57,7 @@ class ListAdapter { class MainActivity : MainView { val adapter = ListAdapter() - override fun deleteListElementAt(position: Int) { - adapter.deleteAt(position) - } + override fun deleteListElementAt(position: Int) = adapter.deleteAt(position) } ``` @@ -83,9 +81,7 @@ interface MainView { class MainActivity: MainView { - override fun setTitle(text: String) { - titleView.setText(text) - } + override fun setTitle(text: String) = titleView.setText(text) } ``` From 554ab4871569f5912c136e32bc9f83466c36d813 Mon Sep 17 00:00:00 2001 From: Marcin Date: Tue, 2 Jan 2018 05:03:57 +0100 Subject: [PATCH 4/6] Change proposition to more universal notation known from property delegation --- proposals/function-delegate.md | 193 +++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 81 deletions(-) diff --git a/proposals/function-delegate.md b/proposals/function-delegate.md index 236ce187d..d93ab43f2 100644 --- a/proposals/function-delegate.md +++ b/proposals/function-delegate.md @@ -16,36 +16,77 @@ Support function delegates - delegae that can be used on to delegate function ca ## Description -Allow to use function type as a function delegate: +Allow to use object as a function delegate. We can use similar notation as for property delegation: +```kotlin +class Delegate { + // Designed for this function with single argument of type Int + operator fun functionDelegate(thisRef: Any?, function: KFunction<*>, property: Int): Int + + // Desifned for any function + operator fun functionDelegate(thisRef: Any?, function: KFunction<*>, vararg arguments: Any?): Int +} + +class A { + fun a(a: Int) by Delegate() // We use here first method + fun a(a: String) by Delegate() // We use here second method + fun a(a: Int, b: String) by Delegate() // We use here second method +} ``` + +## Motivation / use cases + +Main motivation behind this feature is to allow to extract common logic behind function to delegate instead of writing it every time. +With above definition, every function pattern that is used in the project can be extracted. +Let's see some examples. + +### View binding + +Let's say that we often define functions that are making single change in layout but that needs to be extracted to keep MVP structure: + +```kotlin interface MainView { - fun clearList() + fun setTitle(title: String) } -class ListAdapter { - fun clear() {} +class MainActivity : MainView { + + override fun setTitle(title: String) { + titleView.text = title + } +} +``` + +With function delegation we can define simple function delegate and then use it to replace all such functions with simpler notation: + +```kotlin +class bindToText(val elementProvider: ()->TextView) { + operator fun functionDelegate(thisRef: Any?, function: KFunction<*>, text: String) { + elementProvider().text = text + } } class MainActivity : MainView { - val adapter = ListAdapter() - - override fun clearList() by adapter::clear + + override fun setTitle(title: String) by bindToText { titleView } } ``` -## Motivation / use cases +### Function calls passing -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. +If we often pass function calls, we can define generic extension functions to function type: -### Function calls passing +```kotlin +operator fun (()->Unit).functionDelegate(thisRef: Any?, function: KFunction<*>) = this() -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: +operator fun ((T1)->Unit).functionDelegate(thisRef: Any?, function: KFunction<*>, arg1: T1) = this(arg1) +operator fun ((T1, T2)->Unit).functionDelegate(thisRef: Any?, function: KFunction<*>, arg1: T1, arg2: T2) = this(arg1, arg2) ``` + +And use it to have simpler notation for passing calls to functions. So instead of this notation: + +```kotlin interface MainView { fun deleteListElementAt(position: Int) } @@ -61,10 +102,9 @@ class MainActivity : MainView { } ``` -Simpler and more agile implementation would be if we could use function delegate instead: - +We can use following: -``` +```kotlin class MainActivity : MainView { val adapter = ListAdapter() @@ -72,41 +112,23 @@ class MainActivity : MainView { } ``` -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) = titleView.setText(text) -} -``` - -We would replace it with function delegate: - -``` -class MainActivity: MainView { - - override fun setTitle(text: String) by titleView::setText -} -``` +Advantages are: + * It is shorter and more readable. + * When function signature changes, we don't need to change also parameters passed to next function. This is two times less work when we need to change signature of function that is passed. Also now we can get better support from IDE. -Note, that when we are operaring on function type, we can use all functional features like currying or extension functions. +Note, that when we are operating 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: +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 Fibonacci function: -``` +```kotlin 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: +Without buffering it is very unefficient. Using function delegation and previously defined extension functions to function type we might add buffering this way: -``` +```kotlin fun memoize(f: (V) -> T): (V) -> T { val map = mutableMapOf() return { map.getOrPut(it) { f(it) } } @@ -117,28 +139,62 @@ fun fib(i: Int): Int by memoize { i -> if (i <= 2) 1 else fib(i - 1) + fib(i - 1 Similarly synchronization for single function could be added. -## Implementation details +### Functional style -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 `memoize` should be compiled to: +Function delegation and above definitions would also open a way for more functional style of programming where functions are defined using another functions. +For instance, we could use define functions using [Haskell pointfree stype](https://wiki.haskell.org/Pointfre): -``` -private val fib$delegate: (Int) -> Int = memoize { i -> if (i <= 2) 1 else fib(i - 1) + fib(i - 1) } -fun fib(i: Int) = fib$delegate(i) -``` +```kotlin +infix fun ((T) -> R).compose(f: (R) -> S) : (T) -> S = { f(this(it)) } + +fun add5(i: Int) = i + 5 -similarly `setTitle` will be compiled: +fun add10(i: Int) = i + 10 +fun stringToInt(s: String) = s.toInt() + +fun convertAndAdd15 by ::stringToInt compose ::add5 compose ::add10 ``` -class MainActivity: MainView { - private val setTitle#delegate = titleView::setText - override fun setTitle(text: String) = setTitle#delegate(text) +Or to create new functions using partial application. + +## Implementation details + +Function delegate could be compiled to delegate holden in separate property and its invocation in function body. +Invocation should include all the parameters defined by the function. +For instance, above `a` methods should be compiled to: + +```kotlin +class Delegate { + // Designed for this function with single argument of type Int + operator fun functionDelegate(thisRef: Any?, function: KFunction<*>, property: Int): Int + + // Desifned for any function + operator fun functionDelegate(thisRef: Any?, function: KFunction<*>, vararg arguments: Any?): Int +} + +class A { + val aInt$Delegate = Delegate() + fun a(a: Int) = aInt$Delegate.functionDelegate(a) + + val aString$Delegate = Delegate() + fun a(a: String) = aInt$Delegate.functionDelegate(a) + + val aIntString$Delegate = Delegate() + fun a(a: Int, b: String) = aInt$Delegate.functionDelegate(a, b) } ``` +This is how `fib` should be compiled: + +```kotlin +private val fib$delegate = memoize { i -> if (i <= 2) 1 else fib(i - 1) + fib(i - 1) } +fun fib(i: Int) = fib$delegate.functionDelegate(i) +``` + ## Alternatives -If we are using property instead of function when we can get all the functionalities. For example: +If we are using property instead of function and use property delegation. Also when we use property then it can hold state so we can use functions like memorize: ``` val fib: (Int) -> Int = memoize { a -> @@ -147,7 +203,7 @@ val fib: (Int) -> Int = memoize { a -> } ``` -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: +The problem with this alternative is when we are using recurrence (because property is not defined yet). Also there is problem with interfaces, because we would have to replace functions with properties with function types: ``` interface MainView { @@ -163,29 +219,4 @@ class MainActivity : MainView { override val clearList: ()->Unit = adapter::clear } -``` - -## Other notation proposition - -Instead of `as` keyword, we could use simpler notation: - -``` -fun fib = memoize { 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 -} - -class A { - fun a(val a: Int) by Delegate() -} -``` +``` \ No newline at end of file From 82e47586319a7e25e12a12d35e039d7e7d3ec4c5 Mon Sep 17 00:00:00 2001 From: Marcin Date: Tue, 2 Jan 2018 05:04:34 +0100 Subject: [PATCH 5/6] Visual improvement --- proposals/function-delegate.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/function-delegate.md b/proposals/function-delegate.md index d93ab43f2..564fdfe90 100644 --- a/proposals/function-delegate.md +++ b/proposals/function-delegate.md @@ -196,7 +196,7 @@ fun fib(i: Int) = fib$delegate.functionDelegate(i) If we are using property instead of function and use property delegation. Also when we use property then it can hold state so we can use functions like memorize: -``` +```kotlin val fib: (Int) -> Int = memoize { a -> if (a <= 2) 1 else (::fib).get()(a - 1) + (::fib).get()(a - 2) @@ -205,7 +205,7 @@ val fib: (Int) -> Int = memoize { a -> The problem with this alternative is when we are using recurrence (because property is not defined yet). Also there is problem with interfaces, because we would have to replace functions with properties with function types: -``` +```kotlin interface MainView { val clearList: ()->Unit } From 29442c6dc80d6d60feef3989423d34af85c283f6 Mon Sep 17 00:00:00 2001 From: Marcin Date: Tue, 2 Jan 2018 05:05:59 +0100 Subject: [PATCH 6/6] Correct link to discussion --- proposals/function-delegate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/function-delegate.md b/proposals/function-delegate.md index 564fdfe90..4f9cd7f8b 100644 --- a/proposals/function-delegate.md +++ b/proposals/function-delegate.md @@ -8,7 +8,7 @@ ## Feedback -Discussion of this proposal is held in [this issue](https://github.com/Kotlin/KEEP/issues/25). +Discussion of this proposal is held in [pull request](https://github.com/Kotlin/KEEP/pull/89/commits). ## Summary