Receiver Closures
Hello,
I would like to get some feedback on a pitch idea.
Idea
allow specifying the receiver of a closure. (what a closure is being called on)
This would modify how self
behaves inside a closure body.
Kotlin already has something similar Higher-Order Functions and Lambdas - Kotlin Programming Language and imo its really useful.
This is somewhat possible in current Swift using self
rebinding.
let closure: (String) -> Void = { `self` in
print(self)
}
closure("string") // prints: "string"
However referencing self
always has do be done explicitly.
struct Person { var name: String }
var bruce = Person(name: "bruce")
var name = "not bruce"
let closure: (Person) -> Void = { `self` in
print("self.name = \(self.name)")
print("name = \(name)") // references outer variable name
}
closure(bruce)
// prints: "self.name = bruce"
// prints: "name = not bruce"
With this proposal it would be possible to define a ReceiverClosure
inside the closure implicit (end explicit) self
refers to the receiver (the thing the function is being called on).
// assuming there is a way to define this
typealias ReceiverClosure<Receiver, Args, ReturnType>
// and assuming a way to call it
func call<Receiver, Args, ReturnType>(
_ closure: ReceiverClosure<Receiver, Args, ReturnType>
on receiver: Receiver,
arguments: Args
)
// the example before would behave differently
struct Person { var name: String }
var bruce = Person(name: "bruce")
var name = "not bruce"
let printNames: ReceiverClosure<Person, Void, Void>
= { // it is no longer necessary to bind self
print("self.name = \(self.name)")
print("name = \(name)") // references inner variable name
}
// asummes call function exists
call(closure, on: bruce, arguments: ())
// prints: "self.name = bruce"
// prints: "name = bruce"
// behaviour inside closure body should be same as:
private extension Person {
func printName() {
print("self.name = \(self.name)")
print("name = \(name)")
}
}
UseCases
Simplifying weak self in closures
func weakCall <Receiver: AnyObject>(
on: Receiver,
_ closure: @escaping ReceiverClosure<Receiver, Void, Void>
) -> () -> Void {
return { [weak value] in
guard let strongValue = value else {
return
}
call(closure, on: strongValue, arguments: ())
}
}
class Computer {
var computedValue: Int?
func done() {}
func computeNumber(_ completion: @escaping (Int) -> Void)
func start() {
computeNumber(weakCall(receiver: self) { number in
// can directly refer to self without unwrapping from weak self
self.done()
// implicit self also works
computedValue = number
})
}
}
Nicer DSL Syntax
With the current Swift a DSL for building HTML could be defined like
this:
typealias Builder<T> = (T) -> Void
class HtmlElement {}
final class HTML: HtmlElement {
var head = Head()
var body = Body()
}
func html(_ build: Builder<HTML>) -> HTML {
var result = HTML()
build(result)
return result
}
final class Body: HtmlElement {
var text: String?
var children: [HtmlElement] = []
func div(_ build: Builder<Div>) {
var div = Div()
div.apply(build)
children.append(div)
}
}
final class Head: HtmlElement {
var title: String?
}
final class Div: HtmlElement {
var text: String?
}
extension HTML {
func head(_ build: Builder<Head>) {
build(head)
}
func body(_ build: Builder<Body>) {
build(body)
}
}
Usage:
let website = html {
$0.head { $0.title = "Awesome Title" }
$0.body {
$0.div { $0.text = "div1" }
$0.div { $0.text = "div2" }
}
}
Having to always specify $0
really hurts readability.
By replacing the Builder
closures with ReceiverClosure
s the same behaviour can acheived in a more concise way.
// before:
typealias Builder<T> = (T) -> Void
// after:
typealias Builder<T> = ReceiverClosure<T, Void, Void>
the example could be rewritten as:
let website = html {
head { title = "Awesome Title" }
body {
div { text = "div1" }
div { text = "div2" }
}
}
How to define a ReceiverClosure
To work with Value semantics it should be made possible to define ReceiverClosure
and MutatingReceiverClosure
a MutatingReceiverClosure
behaves the same as a normal ReceiverClosure
but in addition is also allowed to mutate self
typealias MutatingReceiverClosure<Receiver, Args, ReturnType>
// a way to call it
func apply(
closure: MutatingReceiverClosure,
to receiver: inout Receiver,
arguments: Args
) // generics ommited for brevity
Some Options to define those are:
Prefix function definition with ReceiverType
typealias ReceiverClosure<Receiver, Args, ReturnType>
= Receiver.(Args) -> ReturnType
// uses mutating keyword to create mutable variant
typealias MutatingReceiverClosure<Receiver, Args, ReturnType>
= mutating Receiver.(Args) -> ReturnType
// Example:
let printUppercased: String.() -> Void {
print(uppercased())
}
let clear: mutating String.() -> Void {
self = ""
}
This is also the a "Receiver Lambda" is defined in kotlin
Introduce receiver
function parameter attribute
// new attribute `reveiver`
typealias ReceiverClosure<Receiver, Args, ReturnType>
= (receiver Receiver, Args) -> ReturnType
// only one parameter can be marked as `receiver`
typealias MutatingReceiverClosure<Receiver, Args, ReturnType>
= (receiver inout Receiver, Args) -> ReturnType
Calling a ReceiverClosure
The previous examples avoided dealing with this problem by assuming the existence of a function call
function which allows calling a receiver lambda.
A way to think about a ReceiverClosure
is a function KeyPath
This could also be the syntax used for calling / creating a ReceiverClosure
struct Person {
var name: String
func introduce() {
print("i am \(self.name)")
}
mutating func setName(_ newName: String) {
name = newName
}
}
// could potentially be named:
// FunctionKeyPath<Base, Args, ReturnType>
// MutatingFunctionKeyPath<Base, Args, ReturnType>
let greet: ReceiverClosure<Person, Void, Void>
= \Person.introduce
let compare: MutatingReceiverClosure<Person, String, Void>
= \Person.setName
let person = Person(name: "tim")
person[keyPath: greet]()
// prints "i am tim"
person[keyPath: setName]("peter") // compiler error cannot use mutating on immutable person
var mutablePerson = person
mutablePerson[keyPath: setName]("peter") // works
mutablePerson[keyPath: greet]()
// prints "i am peter"
A potential way to call a ReceiverClosure
on a receiver is via dot syntax
struct Person {
var name: String
func introduceSelf() {
print("i am \(self.name)")
}
}
let greetPerson: ReveiverClosure<Person, Person, Void> = {
print("hi \($0.name)!")
introduceSelf()
}
let han = Person(name: "han")
let luke = Person(name: "luke")
// method is not defined on Person but can still be called
han.greetPerson(luke)
// prints:
// hi luke!
// i am han
This could be used to make member functions "replaceable"
struct Person {
var name: String
var introduceSelf = ReceiverClosure<Self, Void, Void> = {
print("i am \(self.name)")
}
// is equivalent to
// func introduceSelf() {
// print("i am \(self.name)")
// }
}
var luke = Person(name: "luke")
luke.introduceSelf()
// prints:
// i am luke
luke.introduceSelf = {
print("I AM \(name.upperCased())")
}
luke.introduceSelf()
// prints:
// I AM LUKE
Please let me know what you think,
Simon