【问题标题】:Is there a way to reference instance function when calling SequenceType.forEach?调用 SequenceType.forEach 时有没有办法引用实例函数?
【发布时间】:2015-12-03 03:59:06
【问题描述】:

考虑输入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 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)

有没有更好的方法来引用实例函数?你喜欢怎样写这样的表达方式?

【问题讨论】:

  • 不清楚问题出在哪里。 someFoos.forEach { $0.bar() }到底有什么不喜欢的?还不清楚您的 if 构造的目的是什么。
  • @matt someFoos.forEach { $0.bar() } 很好,但 IMO 之类的 someFoos.forEach(Foo.bar) 会更好(更容易阅读)。 if 语句只是为了说明在某些情况下需要额外的括号才能使用 someFoos.forEach { $0.bar() } 语法,这会降低代码的可读性。
  • @deville 但是你回答了你自己的问题。 bar() Foo 的一个实例方法。
  • 实例方法是将实例作为第一个参数的柯里化函数。所以someFoos.forEach { Foo.bar($0)() } 编译并工作。 – 您的“所需”someFoos.forEach(Foo.bar)someFoos.forEach { Foo.bar($0) } 相同,后者不同且无法编译
  • 我认为您将两个问题混为一谈:#1:如何在闭包中将实例方法用作柯里化函数,以及 #2:为什么尾随闭包语法在 if 语句中不起作用. – 也许你应该把这些问题分开。

标签: ios swift


【解决方案1】:

这里有两个不同的问题。 尾随闭包语法 可以在调用函数并且最后一个参数是闭包时使用, 所以

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 的未来版本中进行更改,但可能不值得 努力。


另一个问题是关于curried 函数

someFoos.forEach(bar2)

编译是因为bar2 的类型为Foo -> Void,而这正是 forEach() 方法所期望的。另一方面,Foo.bar, 是一个柯里化函数(参见http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/),它将实例作为第一个 争论。它的类型为Foo -> () -> ()。所以

Foo.bar(someFoo)

是一个类型为() -> ()的闭包,并且

Foo.bar(someFoo)()

someFoo 实例上调用bar 方法。

(注意:以下内容并非实际推荐, 但仅作为关于咖喱函数和乐趣的演示 有闭包!)

要将Foo.bar 作为参数直接传递给forEach(),我们需要 “交换”参数的顺序。为此,Haskell 有一个“翻转”功能, 在 Swift 中也是可能的(参见例如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) } }
}

那么flip(Foo.bar) 的类型为() -&gt; Foo -&gt; (),所以 可以应用bar方法的void参数

flip(Foo.bar)()

获得Foo -&gt; () 闭包,并且

flip(Foo.bar)()(someFoo)

someFoo 实例上调用bar 方法。 现在我们可以调用

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

不使用闭包表达式{ .. }!!

如果isBaz 是一个方法而不是一个属性

func isBaz() -> Bool { return false }

那么你 可以在 if 表达式中做同样的事情:

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

再次重申,这仅作为演示。还有属性 不是咖喱函数,所以这不能用 你的isBaz 属性。

【讨论】:

  • 感谢您花时间写出如此详尽的答案!有很多值得深思的地方!
【解决方案2】:

$0 语法可以帮助您创建快捷方式,但如果您不喜欢它,可以使用更完整的形式:

someFoos.forEach { thisFoo in thisFoo.bar() }

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2017-08-07
  • 2012-02-17
  • 2021-06-19
  • 1970-01-01
  • 2010-09-06
  • 2011-12-11
  • 1970-01-01
  • 2011-04-27
相关资源
最近更新 更多