currying functions
On July 28, an important event happened in the Android world: announced Jetpack Compose 1.0… Along with this innovation, the place for the keyword class
even less. Kotlin supports a paradigm functional programming (FP), and Google developers make good use of this.
Object-oriented approach (OOP) is often put as opposed to FP. This is a mistake: they are not rivals and can complement each other. One of the concepts of FP is function currying. It allows you to make partial application while waiting for data, minimizing the number of errors in the code: this can be useful when developing in Compose and not only 🙂
Let’s take a look at what currying is and how you can use it in practice.
A few words about function arguments
It’s common in the OOP world to think that functions can have multiple arguments. In fact, this is not the case. The function describes the relationship between the source (scope) and the final set of data (range). It does not connect several initial sets with a finite set.
Thus, a function cannot have multiple arguments. Basically, arguments are many:
// Привычный вид функции
fun f(a: Int, b: Int) = a + b
// Скрытый вид функции
fun f(vararg a: Int) = a.first() + a.last()
By convention, the extra code has been omitted. But it is still a function of one argument, not two.
What is function currying
So now we know that there is one argument – an array of data (tuple). Then the function f(a, b)
can be considered as the set of all functions on N from the set of all functions on N. It would look like this: f(a)(b)
… In this case, you can write: f(a) = f2; f2(b) = a + b
…
When the function is applied f(a)
, argument a
ceases to be a variable and becomes a constant for a function f2(b)
… Execution result f(a)
– a function that can be performed on a delayed basis.
Converting a function to a view f(a)(b)
and is called currying, after the mathematician Haskell Curry. Although he did not invent this transformation 🙂
// Пример каррированой функции с применением fun
fun f(a: Int) = { b: Int -> a + b }
// Пример каррированой функции с применением переменной
val f2: (Int) -> (Int) -> Int = { b -> { a -> a + b } }
Jetpack Compose and Partial Function Application
Currying allows you to split a function into several blocks and delay them as needed. There is a term for this approach – “Partial application”…
I will give a practical example of partial use in Jetpack Compose. Let’s add a simple BottomNavigation with switching logic to the application, kindly generated for us by Android Studio.
CurryingTheme {
var currentTab by remember { mutableStateOf(0) }
Scaffold(
bottomBar = {
BottomNavigation {
BottomNavigationItem(
selected = currentTab == 0,
icon = { Icon(Icons.Filled.Person, null) },
onClick = {
currentTab = 0
}
)
BottomNavigationItem(
selected = currentTab == 1,
icon = { Icon(Icons.Filled.Phone, null) },
onClick = {
currentTab = 1
}
)
}
}
) {
Greeting("Android")
}
}
Let’s tweak Greeting to switch text depending on the state of the BottomNavigation.
when (currentTab) {
0 -> Greeting("Selected - Person")
1 -> Greeting("Selected - Phone")
}
It works, but this approach is fraught with bugs: using random constants will confuse them. Let’s replace arbitrary constants with sealed class
which will help us avoid possible problems.
sealed class BottomNavigationTab {
object Person : BottomNavigationTab()
object Phone : BottomNavigationTab()
}
It looks much more reliable. Let’s add another small feature – the Snackbar output when switching. In this case, he emphasizes well the importance of simplifying onClick
and currying thereafter.
CurryingTheme {
val scope = rememberCoroutineScope()
val scaffoldState: ScaffoldState = rememberScaffoldState()
var currentTab: BottomNavigationTab by remember { mutableStateOf(BottomNavigationTab.Person) }
Scaffold(
scaffoldState = scaffoldState,
bottomBar = {
BottomNavigation {
BottomNavigationItem(
selected = currentTab == BottomNavigationTab.Person,
icon = { Icon(Icons.Filled.Person, null) },
onClick = {
currentTab = BottomNavigationTab.Person
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Selected - ${currentTab::class.simpleName}")
}
}
)
BottomNavigationItem(
selected = currentTab == BottomNavigationTab.Phone,
icon = { Icon(Icons.Filled.Phone, null) },
onClick = {
currentTab = BottomNavigationTab.Phone
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Selected - ${currentTab::class.simpleName}")
}
}
)
}
}
) {
when (currentTab) {
BottomNavigationTab.Person -> Greeting("Selected - Person")
BottomNavigationTab.Phone -> Greeting("Selected - Phone")
}
}
}
Let’s say there will be more than one BottomNavigationItem. onClick
looks depressing – a lot of duplicate code. This can be easily tweaked by placing the code in a nested function.
fun onClick(tab: BottomNavigationTab) {
currentTab = tab
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Selected - ${currentTab::class.simpleName}")
}
}
Using this function onClick
will look like this:
BottomNavigationItem(
selected = currentTab == BottomNavigationTab.Person,
icon = { Icon(Icons.Filled.Person, null) },
onClick = {
onClick(BottomNavigationTab.Person)
}
)
BottomNavigationItem(
selected = currentTab == BottomNavigationTab.Phone,
icon = { Icon(Icons.Filled.Phone, null) },
onClick = {
onClick(BottomNavigationTab.Phone)
}
)
This is a working option, but currying with partial application will help to simplify even further. onClick
…
// каррированая функция
fun onClick(tab: BottomNavigationTab): () -> Unit = {
currentTab = tab
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Selected - ${currentTab::class.simpleName}")
}
}
// частичное применение
BottomNavigationItem(
selected = currentTab == BottomNavigationTab.Person,
icon = { Icon(Icons.Filled.Person, null) },
onClick = onClick(BottomNavigationTab.Person)
)
BottomNavigationItem(
selected = currentTab == BottomNavigationTab.Phone,
icon = { Icon(Icons.Filled.Phone, null) },
onClick = onClick(BottomNavigationTab.Phone)
)
We’ve covered one example of using a curried function in Jetpack Compose, but the purpose of this transformation is much broader.
Currying is widely used in programming languages that support the functional paradigm. All languages that support closures allow writing curried functions: for example JavaScript, C #, Kotlin, Haskell. It makes sense to master this technique in order to minimize bugs in the code, make it easier to understand, reduce the number of lines, and enrich the OOP code. Good luck!
The project is available on Github