prathameshk
(Prathamesh Kowarkar)
1
How is it possible that I can access a non-static method defined in a type, assign it to a local variable and then pass an object of that type to that variable which returns a closure that I can then execute? Aka how does this work? I don’t understand what’s happening here.
Here’s a quick example in case my question isn’t clear enough:
struct Adder {
let base: Int
func add(value: Int) -> Int { base + value }
}
let adder = Adder(base: 3)
// adder: Adder
let addMethod = Adder.add
// addMethod: (Adder) -> (Int) -> Int
let magic = addMethod(adder)
// magic: (Int) -> Int
let sum = magic(2)
// sum: Int = 5
michelf
(Michel Fortin)
2
What's the actual implementation under the hood I'm not sure (and it's probably subject to change anyway), but I see this as the same thing:
let addMethod = { (adder: Adder) -> (Int) -> Int in
return { (value: Int) -> Int in
return adder.add(value)
}
}
Basically, you have a closure returning another closure. Closures can capture parameters from their outer scope, and here the inner one is capturing adder.
prathameshk
(Prathamesh Kowarkar)
3
It’s the under the hood implementation exactly that I’m trying to understand. Accessing add through and object returns the method but accessing the same add through the type, even though it isn’t a static method, takes an object of that type that returns a closure.
Lantua
4
The only reference I remember regarding this is SE-0042 though the proposal is (eventually) rejected. So only the Introduction section applies.
1 Like
You can do something like this:
func addMethod(_ adder: Adder) -> (Int)->Int {
return { arg in
return adder.add(value: arg)
}
}
If I may explain a little further :
let adder = Adder(base: 3)
… creates an instance of the Adder type
let addMethod = Adder.add
… takes a reference to the add(:_) method
let methodWithSelf = addMethod(adder)
Since add(:_) is a method of a struct (or it could be of a class), it has to have an implicit self parameter. Calling this "init method" passes the instance adder to fulfil the requirement of the method to have that self parameter.
Don't forget, Swift allows you to omit the 'self` word from instance method code. The full code is really :
func add(value: Int) -> Int { return self.base + value }
Hence the requirement to "construct" the add(:_) method, by passing in an instance, so that you can access base on that instance, before calling the method thus :
let sum = methodWithSelf(2)
Which is why this code works and is the indirect and somewhat convoluted equivalent to calling
let sum = adder.add(2)
I would be interested to know why you would want to jump through all these hoops instead of simply calling the instance method on the instance itself?
prathameshk
(Prathamesh Kowarkar)
7
@Lantua “unapplied method reference” and its explanation is exactly what I was looking for. Thanks!
@Joanna_Carter I came across this behaviour while working on something else. Xcode autocomplete was suggesting instance methods with a self parameter when I wanted to use a static method and that sent me down this rabbit hole.
Which then means you can also use this "unusual" syntax :
let adder = Adder(base: 3)
let sum = Adder.add(adder)(value: 2)
Now, if only there were a worthwhile reason to use it 

prathameshk
(Prathamesh Kowarkar)
9
And here I was under the impression that SE-0002 removed function currying altogether. 
Lantua
10
SE-0002 only remove syntactic sugar for currying. The idea itself is independent from the language, and Swift easily support it even without said sugar (since function is a first-class citizen).
1 Like
OK, so I couldn't resist playing further, in order to find something vaguely useful. How about this?
struct Adder
{
let base: Int
func add(_ value: Int) -> Int { return base + value }
}
protocol Printable
{
func prettyPrint(_ str: String) -> String
func print() -> String
}
struct NumberValue : Printable
{
let value: Int
func prettyPrint(_ str: String) -> String { return "\(type(of: self))(\(value)) \(str)" }
func print() -> String { return "\(type(of: self))(\(value))" }
}
func visit<subjectT, paramT, returnT>(method: (subjectT) -> (paramT) -> returnT, subject: subjectT?, param: paramT)
{
guard let subject = subject else
{
return
}
print("\(method(subject)(param))")
}
func visit<subjectT, returnT>(method: (subjectT) -> () -> returnT, subject: subjectT?)
{
guard let subject = subject else
{
return
}
print("\(method(subject)())")
}
{
var adders = [Adder]()
for i in 1...10
{
adders.append(Adder(base: i))
}
adders.forEach
{
visit(method: Adder.add, subject: $0, param: 2)
}
var printers = [Printable]()
for i in 1...10
{
printers.append(NumberValue(value: i))
}
printers.forEach
{
visit(method: NumberValue.prettyPrint, subject: $0 as? NumberValue, param: "visited")
visit(method: NumberValue.print, subject: $0 as? NumberValue)
}
}
prathameshk
(Prathamesh Kowarkar)
12
Oh my that seems pretty convoluted and contrived.
Now that I've understood what's going on here, now I wonder why this functionality exists. As in why would I want to call Adder.add(Adder(base: 3))(2) instead of Adder(base: 3).add(value: 2)?
Edit: Is there any instance of this feature being used in the standard library?
DeFrenZ
(Davide De Franceschi)
13
It's the only way to access a method without having an instance of a type. Similar in concept to KeyPaths
2 Likes
In addition to what @DeFrenZ said, it's also a useful feature when you want to call multiple methods on an instance by iterating over the methods. I have used this pattern before as follows:
struct X {
// A list of unapplied method references to methods of X.
let methods: [(X) -> () -> Y?] = [
xMethod1,
xMethod2,
xMethod3,
xMethod4
]
func firstThatSucceeds() -> Y? {
for method in methods {
if let y = method(self)() {
return y
}
}
return nil
}
}
Calling the methods directly would require the following:
func firstThatSucceeds() -> Y? {
if let y = xMethod1() {
return y
}
if let y = xMethod2() {
return y
}
if let y = xMethod3() {
return y
}
if let y = xMethod4() {
return y
}
return nil
}
And adding more methods would only make this problem worse.
3 Likes
prathameshk
(Prathamesh Kowarkar)
15
Oh this makes perfect sense. Thanks @marcusrossel!