Is there a way to reference instance function when calling SequenceType.forEach?

前端 未结 2 946
渐次进展
渐次进展 2020-12-19 16:52

Consider type Foo:

class Foo {

    var isBaz: Bool {
        return false
    }

    func bar() {
        print(\"some boring print\")
    }
}
         


        
相关标签:
2条回答
  • 2020-12-19 17:21

    There are two different problems here. The trailing closure syntax can be used when calling a function and the last parameter is a closure, so

    let b1 = someFoos.contains({ $0.isBaz })
    let b2 = someFoos.contains { $0.isBaz }
    

    are fully equivalent. However, the trailing closure syntax can be problematic in the condition of an if-statement:

    if someFoos.contains({ $0.isBaz }) { }  // OK
    if someFoos.contains { $0.isBaz } { }   // Compiler error
    if (someFoos.contains { $0.isBaz }) { } // OK, as noted by R Menke
    

    We can only speculate why the second one does not work. It could be that the compiler takes the first { as the start of the if-body. Perhaps this will change in a future version of Swift but probably it is not worth the effort.


    The other problem is about curried functions.

    someFoos.forEach(bar2)
    

    compiles because bar2 has the type Foo -> Void, and that is exactly what the forEach() method expects. Foo.bar, on the other hand, is a curried function (see http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/) which takes the instance as the first argument. It has the type Foo -> () -> (). So

    Foo.bar(someFoo)
    

    is a closure with type () -> (), and

    Foo.bar(someFoo)()
    

    calls the bar method on the someFoo instance.

    (Note: The following is not meant as an actual recommendation, but only as a demonstration about curried functions and fun with closures!)

    To pass Foo.bar directly as an argument to forEach() we need to "swap" the order of the parameters. Haskell has a "flip" function for that purpose, and it is also possible in Swift (see e.g. How to write a flip method in Swift?):

    func flip<A, B, C>(f: A -> B ->C) -> B -> A ->C {
        return { b in { a in f(a)(b) } }
    }
    

    Then flip(Foo.bar) has the type () -> Foo -> (), so the void argument of the bar method can be applied

    flip(Foo.bar)()
    

    to get a Foo -> () closure, and

    flip(Foo.bar)()(someFoo)
    

    calls the bar method on the someFoo instance. And now we can call

    someFoos.forEach (flip(Foo.bar)())
    

    without using a closure expression { .. } !!

    If isBaz were a method instead of a property

    func isBaz() -> Bool { return false }
    

    then you could do the same in the if-expression:

    if someFoos.contains(flip(Foo.isBaz)()) { 
        // ...
    }
    

    Again, this is only meant as a demonstration. Also properties are not curried functions, so this cannot be done with your isBaz property.

    0 讨论(0)
  • 2020-12-19 17:45

    The $0 syntax is there to help you create a shortcut, but if you don't like it you can use the more complete form:

    someFoos.forEach { thisFoo in thisFoo.bar() }
    
    0 讨论(0)
提交回复
热议问题