Подтвердить что ты не робот

Как создать рекурсивное закрытие _inline_ в Swift?

Рекурсия тривиальна с глобальными функциями в Swift. Например:

func f()
{
    f()
}

Однако закрытие не может ссылаться на себя. Например:

var f: (Void -> Void) =
{
    f()
}

выдает следующую ошибку:

Variable used within its own initial value

Есть ли обходной путь? Как создать рекурсивное закрытие inline?

4b9b3361

Ответ 1

Существует обходное решение:

func unimplemented<T>() -> T
{
    fatalError()
}

func recursive<T, U>(f: (@escaping (((T) -> U), T) -> U)) -> ((T) -> U)
{
    var g: ((T) -> U) = { _ in unimplemented() }

    g = { f(g, $0) }

    return g
}

recursive - это функция, которая принимает замыкание (((T) -> U), T) -> U, где ((T) -> U) является ссылкой на разделенную версию замыкания и возвращает полезную функцию g.

g изначально назначается фальшивая функция (которая падает при вызове). Это делается для включения рекурсии для нового значения g, где g передается на f вместе с входным значением T. Важно отметить, что g в g = { f(g, $0) } относится к самому себе, а не к фальшивой функции, назначенной ему ранее. Поэтому всякий раз, когда в f указывается параметр ((T) -> U), это ссылка на g, которая, в свою очередь, ссылается сама.

Эта функция допускает встроенную рекурсию, такую ​​как:

recursive { f, x in x != 10 ? f(x + 1) : "success" }(0)

Эта функция повторяется в общей сложности 11 раз, без необходимости объявлять одну переменную.

Обновление: Теперь это работает с Swift 3 preview 6!


Лично говоря, на данный момент я считаю это довольно элегантным решением, потому что я чувствую, что он упрощает мой код до минимума. A Y combinator, например, ниже

func recursive<T, U>(_ f: (@escaping (@escaping (T) -> U) -> ((T) -> U))) -> ((T) -> U)
{
    return { x in return f(recursive(f))(x) }
}

хотел бы, чтобы я возвращал функцию, закрывающую закрытие в пределах закрывающейся крышки!

recursive { f in { x in x != 10 ? f(x + 1) : "success" } }(0)

Приведенный выше код недействителен, если не для внутреннего атрибута @escaping. Он также требует другого набора фигурных скобок, что делает его более подробным, чем то, что мне нравится при написании встроенного кода.

Ответ 2

Ограничение состоит в том, что два объекта не могут быть созданы одновременно и связаны друг с другом. Одно должно быть создано раньше другого. Вы можете пометить функцию неявно развернутой необязательно. Таким образом, вы инициализируете функцию с помощью nil, но "обещаете", что она будет иметь значение позже.

var f: (Void -> Void)!

f = {
    f()
}

Обновление: другой подход без неявно развернутых опций:

var f: (Void -> Void)

var placeholder: (Void -> Void) = {
    f()
}

f = placeholder