调用SequenceType.forEach时有没有办法引用实例函数?
考虑typesFoo
:
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 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)
有没有更好的方法来引用实例function? 你更喜欢写这样的expression式?
这里有两个不同的问题。 所以在调用函数时可以使用尾随闭包语法 ,而最后一个参数是闭包
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中发生变化,但可能是不值得的。
另一个问题是关于咖喱饭的function 。
someFoos.forEach(bar2)
编译是因为bar2
的types是Foo -> Void
,而这正是forEach()
方法所期望的。 另一方面, Foo.bar
是一个curried函数(参见http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/ ),它将实例作为第一个参数。 它有typesFoo -> () -> ()
。 所以
Foo.bar(someFoo)
是一个封闭types() -> ()
,和
Foo.bar(someFoo)()
调用someFoo
实例上的bar
方法。
( 注意:以下内容不是实际的build议,而只是作为一个关于咖喱function和closures乐趣的示范!)
要直接将Foo.bar
作为parameter passing给forEach()
我们需要“交换”参数的顺序。 Haskell为此目的有一个“翻转”function,在Swift中也是可能的(请参阅如何在Swift 中编写翻转方法? ):
func flip<A, B, C>(f: A -> B ->C) -> B -> A ->C { return { b in { a in f(a)(b) } } }
然后, flip(Foo.bar)
具有type () -> Foo -> ()
,所以可以应用bar
方法的void参数
flip(Foo.bar)()
得到一个Foo -> ()
闭包,并且
flip(Foo.bar)()(someFoo)
调用someFoo
实例上的bar
方法。 现在我们可以打电话了
someFoos.forEach (flip(Foo.bar)())
不使用闭包expression式{ .. }
!
如果isBaz
是一种方法而不是属性
func isBaz() -> Bool { return false }
那么你可以在ifexpression式中做同样的事情:
if someFoos.contains(flip(Foo.isBaz)()) { // ... }
再一次,这只是一个示范。 另外属性不是curried函数,所以这不能用你的isBaz
属性来完成。
$0
语法可以帮助你创build一个快捷方式,但是如果你不喜欢,你可以使用更完整的表单:
someFoos.forEach { thisFoo in thisFoo.bar() }