有没有办法在调用SequenceType.forEach时引用实例函数?

时间:2015-12-02 17:25:56

标签: ios swift

考虑类型var game = function( options ){ options = options || []; var target = options.target || '.gameboard', size = options.size || [ 3, 3 ], players = options.players || 2, toWin = options.toWin || 3, symbols = [ 'o', 'x', '+', '!' ], current, grid, setup = function ( ) { $(target).empty(); current = 0; grid = []; _.each(_.range(size[0]), function(r) { var row = $('<div>') .addClass('row'); grid.push([]), _.each(_.range(size[1]), function(c) { grid[r].push(undefined), row.append( $('<button>') .addClass('square open') .attr('data-pos',r + ',' + c) .html('&nbsp;') ); }) $(target).append(row ); }); $(target).append( $('<div>') .addClass('player') .append( $('<span>') .html('Player') ) .append( $('<span>') .addClass('symbol') .html(symbols[0]) ) ); $('.square').click( function(){ if (!($(this).hasClass('disabled'))) makeMove(this) }); }, makeMove = function (btn) { var btn = $(btn) .addClass('disabled') .html(symbols[current]), pos = btn.attr('data-pos').split(','); grid[pos[0]][pos[1]] = current; checkBoard(); }, checkBoard = function () { var winners = []; checkArray() winners = checkArray(grid,winners); // check rows winners = checkArray(_.zip.apply(_, grid) ,winners,'vert'); // check columns var diag = [], diagReverse = [], i = 0, di = 0, map = [], mapR = []; _.each(grid, function(g, gk){ _.each(g, function(cell, ck){ di = ck + i; diag[di] = diag[di] || []; diagReverse[di] = diagReverse[di] || []; map[di] = map[di] || []; mapR[di] = mapR[di] || []; diag[di].push(grid[gk][ck]); diagReverse[di].push( grid[ grid.length - gk - 1 ][ ck ] ); map[di].push([gk,ck]); mapR[di].push([(grid.length - gk - 1), ck]); }); i++; }); winners = checkArray(diag ,winners, map); // check diagonal winners = checkArray(diagReverse ,winners, mapR); // check reverse diagonal if ( winners.length > 0 ) playerWin(winners); current++; current = current % players; $(target + ' .symbol') .html(symbols[current]); }, checkArray = function (arr,winners,map) { map = map || 0; var winner = [], setRow = [], count = 0, cur = -1; _.each(arr, function(g, key){ count = 0; _.each(g, function(cell, ckey){ if (cell===undefined || cell !== cur) { count = 0; setRow = []; } cur = cell; count++; if (map) { if ( map === 'vert'){ setRow.push([ckey,key]); } else if ( _.isArray(map)) { setRow.push([map[key][ckey]]); } } else { setRow.push([key,ckey]); } if (count >= toWin) winner = winner.concat(setRow); }); }); if ( winner.length > 0 ) winners = winners.concat(winner); return winners; }, playerWin = function (winners) { $('.gameboard button') .addClass('disabled'); _.each(winners, function(w){ $('.gameboard button[data-pos="' + w.join() + '"]') .addClass('win'); }); $(target).append( $('<div>') .addClass('winner') .append( $('<h1>') .html('Congratulations, player ' + symbols[current] + '!') ) .append( $('<button>') .addClass('replay') .html('Play Again?') ) ); $('.replay').click ( function(){ setup(); }); }; setup(); } game();

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
}

但是从Swift 2.1开始,这不是一个正确的语法。这种引用函数的方式类似于以下内容:

someFoos.forEach(Foo.bar)

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

2 个答案:

答案 0 :(得分:8)

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

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 functions

someFoos.forEach(bar2)

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

Foo.bar(someFoo)

是类型() -> ()

的闭包
Foo.bar(someFoo)()

调用bar实例上的someFoo方法。

注意:以下内容并非实际建议, 但仅作为关于咖喱功能和乐趣的演示 关闭!)

直接将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)的类型为() -> Foo -> (),所以 可以应用bar方法的void参数

flip(Foo.bar)()

获得Foo -> ()关闭,

flip(Foo.bar)()(someFoo)

调用bar实例上的someFoo方法。 现在我们可以打电话了

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

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

如果isBaz方法而不是属性

func isBaz() -> Bool { return false }
那你呢 可以在if-expression中执行相同的操作:

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

同样,这只是一个示范。还有属性 不是curry函数,所以这不能用 你的isBaz财产。

答案 1 :(得分:1)

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

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