考虑类型Foo:
Foo
class Foo { var isBaz: Bool { return false } func bar() { print("some boring print") } }
现在假设我要遍历一个类实例的集合并在每个实例上调用一些函数:
let someFoos: [Foo] = [Foo(), Foo(), Foo()] someFoos.forEach { $0.bar() }
该语法非常紧凑,但感觉有点尴尬。另外,它不能在任何地方使用。例如,在if语句条件中:
if
if someFoos.contains { $0.isBaz } { // compiler error: statement cannot begin with a closure expression } if someFoos.contains($0.isBaz) { // compiler error: anonymous closure argument not contained in a closure } if someFoos.contains({ $0.isBaz }) { // this is correct, but requires extra pair of parentheses }
理想情况下,写类似
someFoos.forEach(Foo.bar)
但是从Swift 2.1开始,这不是正确的语法。这种引用该函数的方式将类似于以下内容:
func bar2(foo: Foo) -> Void { print("some boring print") } someFoos.forEach(bar2)
有没有更好的方法来引用实例函数?您如何喜欢写这样的表达式?
这里有两个不同的问题。的 拖尾闭合语法 可以在调用函数时,可以使用与最后一个参数是一个闭合,所以
let b1 = someFoos.contains({ $0.isBaz }) let b2 = someFoos.contains { $0.isBaz }
完全等效。但是,在if语句的情况下,尾随闭包语法可能会出现问题:
if someFoos.contains({ $0.isBaz }) { } // OK if someFoos.contains { $0.isBaz } { } // Compiler error if (someFoos.contains { $0.isBaz }) { } // OK, as noted by R Menke
我们只能推测第二个为什么不起作用。可能是编译器将第一个{ 作为if-body的开始。也许这会在Swift的未来版本中发生变化,但是这不值得付出努力。
{
另一个问题是关于 咖喱函数 。
someFoos.forEach(bar2)
进行编译,因为bar2具有类型Foo -> Void,这正是该forEach()方法所期望的。Foo.bar另一方面,是咖喱函数(请参见http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/),该函数将实例作为第一个参数。它具有类型Foo -> () -> ()。所以
bar2
Foo -> Void
forEach()
Foo.bar
Foo -> () -> ()
Foo.bar(someFoo)
是类型为的闭包() -> (),并且
() -> ()
Foo.bar(someFoo)()
bar在someFoo实例上调用方法。
bar
someFoo
( 注意: 以下内容并不是实际建议,而只是作为有关咖喱函数和闭包乐趣的演示!)
要Foo.bar直接作为参数传递给forEach()我们,我们需要“交换”参数的顺序。Haskell为此具有“翻转”功能,并且在Swift中也是:
func flip<A, B, C>(f: A -> B ->C) -> B -> A ->C { return { b in { a in f(a)(b) } } }
然后flip(Foo.bar)具有类型() -> Foo -> (),因此bar可以应用方法的void参数
flip(Foo.bar)
() -> Foo -> ()
flip(Foo.bar)()
得到Foo -> ()关闭,并且
Foo -> ()
flip(Foo.bar)()(someFoo)
bar在someFoo实例上调用方法。现在我们可以打电话
someFoos.forEach (flip(Foo.bar)())
不使用闭包表达式{ .. }!
{ .. }
如果isBaz是 方法 而不是属性
isBaz
func isBaz() -> Bool { return false }
那么您可以在if-expression中执行相同的操作:
if someFoos.contains(flip(Foo.isBaz)()) { // ... }
再次,这仅是示范。此外, 属性 不是咖喱函数,因此无法使用您的isBaz属性来完成。