Delegating Function Calls — or How to Turbocharge a Function

Good ideas may come from good inspiration, from dedication, from talent, or simply from stumbling onto something in your way to accomplish a completely different task. 'Function delegate' is a little known term (that I just made up) used to describe a very versatile, elaborate and simple concept I came across while on such a stumble.

Suppose there's a task we would like to run every time a function is called — for instance, printing a smile on the screen. The immediate solution might be to simply add that task to the function’s body — and that might usually be the best approach.

func importantFunction() {
print(“ =) “)
// do important stuff
}

Suppose after a while this task ends up being needed on other functions as well. Being coders with decent standards as we are, we might refactor that task into its own function, thus minimizing code repetition, and then add it wherever it's needed.

func printSmile() { print(“ =) “) }
func importantFunction() {
printSmile()
// do important stuff
}

Suppose, however, we decide we need to print a frown every time those functions end. The printSmile function only runs when a function starts, so it won't help here! This means we have to go all over our code, find functions that smile when they start and add a frown at the end. This might be easier with a very smart regex, but let's face it, there's a problem here: the code we so smartly encapsulated in the printSmile function suddenly doesn't help us at all. What's worse, if we decide we don't need a smile anymore, we need yet another code overhaul.

Enter function delegation. Simply put, it is a way to delegate your function call to someone else. That someone will be responsible for calling your function, but is otherwise free to do whatever they need. Rather than trying to describe it in an unavoidably confusing manner, here's an example:

let importantFunctionWithEmotions: () -> () = { _ in
print(“ =) “)
importantFunction()
print(“ =( “)
}
importantFunctionWithEmotions() //Smiles, does its work, then frowns

By enveloping our function call in a closure, we are able to easily encapsulate that behavior. However, doing this for only one function does not solve the whole problem, and we don't wanna repeat this code for every function that needs to smile, right?? Enter generics:

func giveEmotionsToFunction<InputType, OutputType>
(function: InputType -> OutputType) -> (InputType -> OutputType) {
    return { input in
print(“ =) “)
let output = function(input)
print(“ =( “)
return output
}
}
let importantFunctionWithEmotions =
giveEmotionsToFunction(importantFunction)
importantFunctionWithEmotions()

Yes! This function gives us the same functionality we had with importantFunctionWithEmotions, only this time around we can apply it for any function we need— discrimination will not be tolerated.

Why would we wanna stop at printing smiles and frowns, though? Functional programming is so much fun! I won't copy all the code for brevity, but it's right here.

We could, for instance, create a delegate that logs our performance-sensitive function calls, so we can better analyze the critical sections of our code:

func addOne(n: Int) -> Int {
return n + 1
}
let addOneAndLog = createLogDelegate(“addOne”, addOne)
addOneAndLog(2) // Prints "addOne(2) = 3"

We could pretty-print a recursive algorithm in order to understand it and demonstrate it:

let logFibonacci = createRecursiveLogDelegate(“fibonacci”, 
function: naiveFibonacci)
logFibonacci(5)
// Prints:
// fibonacci(2) = result
// fibonacci(1) = result
// fibonacci(3) = result
// fibonacci(2) = result
// fibonacci(4) = result
// fibonacci(2) = result
// fibonacci(1) = result
// fibonacci(3) = result
// fibonacci(5) = result

Or we might want to improve that function's performance:

let memoizedFibonacci = createMemoizeDelegate(fibonacci)
memoizedFibonacci(50) // Runs in O(n) instead of O(holy crap!)
// or O(1) if called in order
let asyncFactorial = createAsyncDelegate(
callback: { (n: Int) in print(n) }, function: factorial)
asyncFactorial(20) // Calculates in a separate thread,
// prints the result in the main thread

Or we might want to create some unit tests:

let testFactorial = createTestDelegate(factorial)
// Tests each input against its expected output
testFactorial(1, 1)
testFactorial(2, 2)
testFactorial(3, 6)
testFactorial(4, 24)
testFactorial(5, 120)

Or chain several delegates together:

let allFibonacci = createMemoizeDelegate(
createLogDelegate(“f”, fibonacci))
allFibonacci(40) // Prints every fibonacci number from 1 to 40

These are just initial thoughts; better possibilities (more useful ones, too) may well arise in actual app programming. Delegating function calls to other code may seem like too much work, but I'm finding the reliability and ease-of-use of these functions actually makes me use those functionalities much more often. Once I add a few of these one-liners to my day-to-day, I feel my code will never be the same :)

// This just works! =)
createAsyncDelegate(function: doSlowStuff)()
Show your support

Clapping shows how much you appreciated Vinicius Vendramini’s story.