Passing curried instance method as closure parameter that accepts instance method?

Hi! I've been reading through the (excellent) posts on Doug's Compiler Corner[1] from @Douglas_Gregor . I learn new things! I did not have much experience with "curried instance methods"[2]:

struct Point {
  var x: Int
  var y: Int
  
  func flippedOverXAxis() -> Point { ... }
  func flippedOverYAxis() -> Point { ... }
}

let memberFunction = Point.flippedOverXAxis

var p = Point(x: 1, y: 2)
p = memberFunction(p)()

I have a situation with a function that accepts a closure that operates on a Point instance:

func f(_: (Point) -> Point) { }

f({ point in Point.flippedOverXAxis(point)() })

This works… but I am wondering if there is some way to pass that curried instance method directly without the ceremony of wrapping it in a closure and passing a point instance:

f(Point.flippedOverXAxis)

This fails:

18 | f({ point in Point.flippedOverXAxis(point)() })
19 | 
20 | f(Point.flippedOverXAxis)
   |         `- error: cannot convert value of type '@Sendable () -> Point' to expected argument type '(Point) -> Point'
   |   `- error: instance member 'flippedOverXAxis' cannot be used on type 'Point'; did you mean to use a value of this type instead?
21 | 

Is there any way to pass that curried method directly to a closure that expects an "instance" method? I am unblocked (because I can always wrap the curried method in a closure)… but it would be interesting if there was a shortcut there that worked. Thanks!


  1. https://www.douggregor.net ↩︎

  2. Swift for C++ Practitioners, Part 10: Operator Overloading | Doug's Compiler Corner ↩︎

You forgot to include the parameters in the instance method signature.

func f(_: (Point) -> () -> Point) { }

The generic form looks like this:

func f<Instance, each Parameter, Output>(
  _: (Instance) -> (repeat each Parameter) -> Output
) { }
f(Point.flippedOverXAxis)
3 Likes

Forever unhappy that we didn't get time to implement SE-0042.

5 Likes

@Slava_Pestov, @Quedlinbug: I am trying out @Quedlinbug's generic function above.

But the following code fails to compile (on Xcode 15.4).

f (Foo (), p: 1, 2, 3, 4, 5, o: Foo.bar)

struct Foo {
    func bar <each U>(_ u: repeat each U) -> Foo {
        let v = (repeat each u)
        print (#function, v)
        return self
    }
}

func f <Instance, each Parameter, Output> (
    _ i:Instance,
    p: repeat each Parameter,
    o v: (Instance) -> (repeat each Parameter) -> Output
) {
    print (#function)
    _ = v (i) (repeat each p)
}
Compilation Errors
Command SwiftCompile failed with a nonzero exit code
error: compile command failed due to signal 11 (use -v to see invocation)

Crashes in Xcode 16b3 as well.

Sure… but if we are changing the function signature then that's not exactly my original question. If we were stuck with the original function (because it came from a library or other source we don't control)… then we still need some kind of ceremony to forward that to the original function:

func f(_ body: (Point) -> Point) { }

func g(_ body: (Point) -> () -> Point) {
  f({ point in body(point)() })
}

g(Point.flippedOverXAxis)

Unless I am missing something… there's no shortcut to get directly to the compact version without the SE-0042 changes (as referenced by @jrose)

If I change Foo.bar to a regular method (I think this is what the orginal discussion was about), it works fine.

I suspect the crash is because variadic generic function can't be used as closure. Below is a simpler example to reproduce the crash:

struct Foo { 
    func bar <each U>(_ u: repeat each U) -> Foo { 
        let v = (repeat each u)
        print (#function, v)
        return self
    } 
}

func f(_ i: Foo, p1: Int, p2: Int, c: (Foo) -> (Int, Int) -> Foo) {
    c(i)(p1, p2)
}

f(Foo(), p1: 1, p2: 2, c: Foo.bar)
1 Like

What you are describing is converting a method to a property*.

func convertMethodToProperty<Instance, Property>(
  _ method: @escaping (Instance) -> () -> Property
) -> (Instance) -> Property {
  { method($0)() }
}

Even disregarding the need for @escaping, is that an ergonomic improvement?

f { $0.flippedOverXAxis() }
f(convertMethodToProperty(Point.flippedOverXAxis))

* While it wouldn't work for the article, flippedOverXAxis should be a property. Change it to

var flippedOverXAxis: Point

and then you have what you want:

f(\.flippedOverXAxis)

That would not do it. You want (Point) -> Point, but that would provide (Point, Void) -> Point.