In Swift – the new programming language introduced by Apple – functions are first class citizens. This basically means, that a function can be passed as a parameter, returned from another function or assigned to a value. It allows us to do a lot of useful stuff with them.
Function decorators
Let’s use the concepts of function as a first class citizen to implement some basic function decorator.
Assign functions to variables
func greeting(firstName: String, lastName: String) -> String { return "Hello, " + firstName + " " + lastName + "!" } let greetSomeone = greeting println(greetSomeone("John", "Doe")) // => Hello, John Doe!
Functions can be passed as parameters to other functions
func johnDoeFunction(function: (String, String) -> String) -> String { return function("John", "Doe") } println(johnDoeFunction(greeting)) // => Hello, John Doe!
Functions can return other functions
func composeHelloFunction() -> (String -> String) { func hello(name: String) -> String { return "Hello, " + name } return hello } let helloFunc = composeHelloFunction() println(helloFunc("John")) // => Hello, John
This concept allows us to implement function decorators in Swift.
Function decorators work as wrappers to existing functions, modifying the behavior of the code before and after a target function, without the need to modify the function itself. They augment the original functionality, thus decorate it.
Using the concepts above let’s write a simple decorator that wraps the String output of another function by some html tag.
func hello(name: String) -> String { return "Hello, " + name } func bold(function: String -> String) -> (String -> String) { func decoratedBold(text: String) -> String { return "" + function(text) + "" } return decoratedBold } let boldHello = bold(hello) println(boldHello("Vladimir")) // => Hello, Vladimir
We wrote a function that takes another function as an argument, generates a new function, augmenting the work of the original function, and returns the generated function so we can use it anywhere. Decorating function also allows us to insert some behaviour before/after function call or combine multiple functions into one.
In the example above we had to explicitly specify the signature of the function to decorate: func bold(function: String -> String)
. We’ve implemented bold decorator so that it decorates a function from one String
value, which returns another String
value. But what if one would like to create bold decorator which accepts functions with different signatures?
Abstract Functor
Let’s define an abstract Functor class. By ‘Functor’ let’s assume a class, which wraps any function and allows to call that function.
class F<T1, T2> { let f: T1 -> T2 init(function: T1 -> T2) { self.f = function } func run(args: T1) -> T2 { return f(args) } }
This class allows us to wrap any function and call it somewhere:
func logger(text: String) { println("LOG: \(text)") } let loggerFunc = F(logger) loggerFunc.run("Hello") // => LOG: Hello
Using the Functor class we can now rewrite bold decorator to accept functions with different number of parameters:
func bold(function: F<T1, String>) -> F<T1, String> { func decoratedBold(args: T1) -> String { return "" + function.run(args) + "" } return F(decoratedBold) } let boldGreeting = bold(F(greeting)) println(boldGreeting.run("John", "Doe")) // => Hello, John Doe! let boldHello = bold(F(hello)) println(boldHello.run("Vladimir")) // => Hello, Vladimir
Composing functions
Another interesting approach of using function decorators is composing several functions into single one.
Swift allows overloading basic operators. Let’s try to overload +
operator to accept two functions and compose them into one function.
func +<T1, T2>(beforeHook: F<(), ()>, function: F<T1, T2>) -> (T1 -> T2) { func composedFunc(args: T1) -> T2 { beforeHook.run() return function.run(args) } return composedFunc } func +<T1, T2>(function: F<T1, T2>, afterHook: F<T2, ()>) -> (T1 -> T2) { func composedFunc(args: T1) -> T2 { var result = function.run(args) afterHook.run(result) return result } return composedFunc }
Above you can see two overloads of +
operator which accept functors of different types and combine them into a single function.
The first overloaded version runs a Void -> Void
function before main function, and the second one runs main function,
then passes its result to another T2 -> Void
function and returns the result of the main function.
Let’s write an example of using this approach:
func logger(text: String) { println("LOG: \(text)") } func request(url: String) -> String { return "Success 200" } let logRequest = F(request) + F(logger) logRequest("http://some.awesome.url") // => "LOG: Success 200" let composedRequest = F({ println("Request is fired!") }) + F(request) println(composedRequest("http://some.awesome.url")) // => Request is fired! // => Success 200
We could also implement +
operator overload for plain functions, not for Functor type, but overloading operators for basic types may be a bad practice.
Another approach of composing functions may be implemented with some helper classes and without overloading operators. As an example of doing this
take a look at Before
and After
classes here. They allow us to compose functions in another way:
let composedRequest = Before(request).run({ println("Request is fired!") }) let loggedRequest = After(request).run(logger)
Retry/repeat function call
One another way of using function decorators and Functor class, implemented above, is to write Repeat and Retry decorators. It will allow to run a single function multiple times or until some condition is met.
class F<T1, T2> { ... func repeat(args: T1, times: Int) -> T2 { for i in 1..times { f(args) } return f(args) } func retry(args: T1, maxTries: Int, condition: () -> Bool) -> T2 { var tries = 0 var result: T2? while !condition() && tries < maxTries { result = f(args) tries++ } return result! } }
Code above allows us to implement multiple calling of a function mechanism like this:
func greeting(firstName: String, lastName: String) { print("Hello, " + firstName + " " + lastName + "!") } F(greeting).repeat(("John", "Doe"), times: 3) // => Hello, John Doe! Hello, John Doe! Hello, John Doe!
Or we can run a function until some condition is met or until we have performed some number of tries:
var requestResult: Int = 0 func randomRequest(url: String) { let result = Int(arc4random_uniform(UInt32(2))) if result > 0 { requestResult = 200 } else { requestResult = 404 } } F(randomRequest).retry("http://some.awesome.url", maxTries: 5, condition: { requestResult == 200 })
Conclusion
This article demonstrates a basic approach of decorating and composing functions in Swift. It is pretty easy to implement Decorator pattern in Swift and many useful decorators may be implemented using the described approach.
Having functions as first class citizens may allow creating clean, short and extensible code, which will be pretty easy to understand.
All the code examples for this article could be found in this repository.