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                                      
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to