Static access to non-static functions?

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

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.

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.

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?

@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 :wink::grin:

And here I was under the impression that SE-0002 removed function currying altogether. :see_no_evil:

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)
    }
  }

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?

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

Oh this makes perfect sense. Thanks @marcusrossel!

Terms of Service

Privacy Policy

Cookie Policy