[swift-evolution] [Proposal/Pitch] Function decorators
Aleksandar Petrovic
apetrovic at outlook.com
Tue May 3 17:46:14 CDT 2016
Hi swift-evolution,
I want to apologize in advance for my clumsy English. It's (obviously) not my first language.
Recent discussion about property behaviours reminded me of function decorators in Python. I think decorators can nicely fit in Swift, too.
First, a bit of explanation for the uninitiated. Decorator is a function that transform other function - it receives some function and returns a function of the same signature. Lets make some dead simple decorator:
typealias Decorated = (Double, Double) -> Double
func hiDecorator(fn: Decorated) -> Decorated {
return { x, y in
print("Hi from decorator!")
return fn(x, y)
}
}
func multiply(a: Double, _ b: Double) -> Double {
return a * b
}
let decoratedMultiply = hiDecorator(multiply)
print("Result: \(decoratedMultiply(2, 3))")
The above code should print:
Hi from decorator!
Result: 6
We can use decorators with the current Swift, but they're a bit cumbersome - either we need to store decorated function and remember to use it instead of the original one, or remember to do the decoration on every call.
Instead, we can write something like this:
[hiDecorator]
func multiply(a: Double, _ b: Double) -> Double {
return a * b
}
... and that code should be transformed into following during compilation:
func multiply(a: Double, _ b: Double) -> Double {
let fn: (Double, Double) -> Double { _a, _b in
return _a * _b
}
let decorated = hiDecorator(fn)
return decorated(a, b)
}
Outside, the function looks like before, so the change is compatible with virtual or interface extension functions.
Sometimes we'll need to pass some additional data to the decorator. That's fine, as long as the last argument is the target function:
func logUsage<I, O>(fnName: String, fn: (I) -> O) -> ((I) -> O) {
return { _i in
print("Function \(fnName) enter")
defer { print("Function \(fnName) exit") }
return fn(_i)
}
}
Let's use it:
[logUsage("increment")]
func increment(a: Int) -> Int { return a + 1 }
[logUsage("justPrint")]
func justPrint(s: String) { print(s) }
In the above code, the compiler should generate internal decorator call by combining provided parameter and decorated function.
Why decorators?
It's important to say that proposed decorators change are pure syntactic sugar. Everything we can do with this change can be accomplished with the current Swift language. But, it gives to programmer a tool to express intentions more clearly and move repeating code away.
Sometimes decorators are just a convenient way to quickly add code. In the example above, logging is added with just one line of code and can be easily removed - the function body is not changed.
Sometimes decorators can be used as a poor man dependency injection mechanism, so we can write functions like this:
// if conn is nil, dbInit decorator will provide (global) one, and properly close it after doSomeDbWork exits
[dbInit]
func doSomeDbWork(conn: DbConnection? = nil) { ... }
We can use decorators in the standard library to, in a way, extend language:
[asyncMain]
func doSomething() { ... } // this function will be posted to main queue
[synchronized]
func foo() { ... } // synchronized decorator uses either objc_sync_enter/exit or post on some queue to ensure mutual exclusion
Or to do some more exotic operations:
[repeatUntilDone(maxAttempt: 5)]
func connectToServer(url: String) -> Bool { ... }
Abstract functions are frowned upon in Swift community, but if we can't live without it, we can signal our intent more clearly with decorator:
class T {
[abstract] func bar() {} // abstract decorator will assert on call
}
Etc, etc. Sounds interesting?
Alex
More information about the swift-evolution
mailing list