Quick Tip: Kotlin Scope Functions in Swift

by Marcel on June 5, 2020

When I recently reviewed a colleagues’ Android code, I stumbled upon a function call named with that allows for omitting a variable’s name within its scope (for example for accessing multiple members). As pointed out by that colleague, Kotlin actually has a bunch of nifty APIs that are being described as Scope Functions .

The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. […] In this scope, you can access the object without its name. Such functions are called /scope functions/.

While we can not simply omit a variable’s name in Swift (without tweaking the compiler), we can at least use the shorthand syntax within a closure to access its members - this already makes it more pleasant than writing a variable’s name multiple times on several lines and can even simplify our lives from time to time.

So, what are these functions all about? Let’s take a look at the previously mentioned with function that is defined like this in Kotlin:

inline fun <T, R> with(receiver: T, block: T.() -> R): R

It essentially that takes a value T and a block that provides the previously passed value as an argument within the block and returns a value R from the same block which is then being forwarded by the function itself. When being used, it might look like this:

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    val firstItem = first()
    val lastItem = last()        
    println("First item: $firstItem, last item: $lastItem")
}

When looking at the function signature in Kotlin, it should become quite apparent that we can /steal/ this bit of handiness and make use of it in Swift. Implementing it is simple and we could even simplify it further by making T an inout argument and being able to remove R completely:

func with<T>(_ object: T, using closure: (inout T) throws -> Void) rethrows -> T {
    var mutableObject = object
    try closure(&mutableObject)
    return mutableObject
}

When being used, our function will appear like this:

let item = ShoppingBagItem(name: "Cappucino", quantity: 3)

with(item) {
    print("Buying \($0.quantity)x \($0.name)")
}

In fact, our with function is quite flexible and could also be used for configuring values within its scope and returning a ready-to-use object (with a couple of changes).

This is one of the many examples for why Swift is a "bottom-up" language that allows us to extend it as we like (to a certain degree) and customize the way we interact with our code that fits our needs and preferences.