4

Я пытаюсь реализовать Church Numerals в Swift 3. В настоящее время у меня есть:Non-спасаясь ошибка при выполнении Чёрча в Swift 3

func numToChurch(n: Int) -> ((Int) -> Int) -> Int { 

    return { (f: (Int) -> Int) -> (Int) -> Int in 
     return { (x : Int) -> Int in 
      return f(numToChurch(n: n - 1)(f)(x)) 
     } 
    } 
} 

func churchToNum(f: ((Int) -> Int) -> (Int)-> Int) -> Int { 
    return f({ (i : Int) -> Int in 
     return i + 1 
    })(0) 
} 

На этой линии в моей функции numToChurch:

return f(numToChurch(n: n - 1)(f)(x)) 

Я продолжаю получать ошибку времени компиляции: «Закрытие параметра без возможности экранирования« f »может позволить ему сбежать». В качестве быстрого исправления, я принял рекомендуемые изменения включают @escaping:

func numToChurch(n: Int) -> ((Int) -> Int) -> Int { 

    return { (f: @escaping (Int) -> Int) -> (Int) -> Int in 
     return { (x : Int) -> Int in 
      return f(numToChurch(n: n - 1)(f)(x)) 
     } 
    } 
} 

Но даже после внесения изменений, я постоянно получаю сказал ту же ошибку, и она рекомендует добавить другой @escaping после «F:». Я понимаю, что это связано с маркировкой функциональных параметров как @escaping, чтобы сообщить компилятору, что параметры могут быть сохранены или захвачены для функционального программирования. Но я не понимаю, почему я продолжаю получать эту ошибку.

Оригинал, не спасаясь вопрос решен

Помощь с пониманием церкви кодировкой в ​​Swift продолжение:

func zero(_f: Int) -> (Int) -> Int { 
    return { (x: Int) -> Int in 
     return x 
    } 
} 

func one(f: @escaping (Int) -> Int) -> (Int) -> Int { 
    return { (x: Int) in 
     return f(x) 
    } 
} 

func two(f: @escaping (Int) -> Int) -> (Int) -> Int { 
    return { (x: Int) in 
     return f(f(x)) 
    } 
} 

func succ(_ f: Int) -> (@escaping (Int) -> Int) -> (Int) -> Int { 
    return { (f : @escaping ((Int) -> Int)) -> Int in 
     return { (x : Int) -> Int in 
      return f(n(f)(x)) 
     } 
    } 
} 


func sum(m: @escaping ((Int) -> (Int) -> Int)) -> ((Int) -> (Int) -> Int) -> (Int) -> (Int) -> Int { 
    return { (n: @escaping ((Int) -> Int)) -> (Int) -> (Int) -> Int in 
     return { (f: Int) -> (Int) -> Int in 
      return { (x: Int) -> Int in 
       return m(f)(n(f)(x)) 
      } 
     } 
    } 
+1

Добро пожаловать в (часто разочаровывающий) мир FP в Свифт, или, как нам нравится называть его: «Хорошо в зоне комфорта Swift». : D Но все же, это может быть отличное упражнение; удачи и добро пожаловать в клуб! –

ответ

7

Вы используете выделки для функций нескольких параметров. Это не очень естественный способ выразить вещи в Свифте, и это усложняет ситуацию. (Swift is not a functional programming language.)

Как указано в вашей статье, «все церковные цифры являются функциями, которые принимают два параметра». Так и сделайте это. Сделайте это двумя параметрами.

typealias Church = (_ f: ((Int) -> Int), _ x: Int) -> Int 

Это функция, которая принимает два параметра, функцию и ее аргумент.

Теперь вы хотите, чтобы обернуть аргумент в функции N раз:

// You could probably write this iteratively, but it is pretty elegant recursively 
func numToChurch(_ n: Int) -> Church { 
    // Church(0) does not apply the function 
    guard n > 0 else { return { (_, n) in n } } 

    // Otherwise, recursively apply the function 
    return { (f, x) in 
     numToChurch(n - 1)(f, f(x)) 
    } 
} 

И получить обратно только применяя функцию:

func churchToNum(_ church: Church) -> Int { 
    return church({$0 + 1}, 0) 
} 

Просто строить на этом, вы может карри его (и я думаю, что я просто говорю, что @kennytm также ответил). Карринг лишь немного сложнее в Swift:

typealias Church = (@escaping (Int) -> Int) -> (Int) -> Int 

func numToChurch(_ n: Int) -> Church { 
    // Church(0) does not apply the function 
    guard n > 0 else { return { _ in { n in n } } } 

    return { f in { x in 
     numToChurch(n - 1)(f)(f(x)) 
     } 
    } 
} 

func churchToNum(_ church: Church) -> Int { 
    return church({$0 + 1})(0) 
} 

Там очень резонный вопрос: «Зачем мне нужно во втором случае @escaping, но не в первую очередь?» Ответ заключается в том, что когда вы передаете функцию в кортеже, вы уже избегаете ее (сохраняя ее в другой структуре данных), поэтому вам не нужно снова отмечать ее @escaping.


Для ваших дальнейших вопросов, используя typealias резко упрощает эту проблему и поможет вам продумать ваши типы гораздо более четко.

Итак, каковы параметры нуля? Ничего. Это постоянный. Итак, какова должна быть его подпись?

func zero() -> Church 

Как это реализовать? Мы применяем f ноль раз

func zero() -> Church { 
    return { f in { x in 
     x 
     } } 
} 

Один и два почти идентичны:

func one() -> Church { 
    return { f in { x in 
     f(x) 
     } } 
} 

func two() -> Church { 
    return { f in { x in 
     f(f(x)) 
     } } 
} 

Что такое подпись succ? Он принимает Церковь и возвращает Церковь:

func succ(_ n: @escaping Church) -> Church { 

Потому что это Свифт, нам нужно немного подтолкнуть, добавив @escaping и _, чтобы сделать вещи более естественно. (Swift не является функциональным языком, он разлагает проблемы по-разному. Составляющие функции не являются его естественным состоянием, поэтому избыточность синтаксиса не должна шокировать нас.) Как реализовать? Нанесите еще один f к n:

func succ(_ n: @escaping Church) -> Church { 
    return { f in { x in 
     let nValue = n(f)(x) 
     return f(nValue) 
     } } 
} 

И опять же, какова природа sum? Ну, у нас настроение, и это означает, что это функция, которая берет Церковь и возвращает функцию, которая берет Церковь и возвращает Церковь.

func sum(_ n: @escaping Church) -> (@escaping Church) -> Church 

Опять же, необходим дополнительный синтаксис, потому что Swift. (И, как описано выше, я добавил дополнительный Пусть связывающую только, чтобы кусочки немного более ясно.)

func sum(_ n: @escaping Church) -> (@escaping Church) -> Church { 
    return { m in { f in { x in 
     let nValue = n(f)(x) 
     return m(f)(nValue) 
     } } } 
} 

Глубокий урок здесь власть Church typealias. Когда вы пытаетесь думать о числах Церкви как о «функциях, которые бла-бла-бла», вы быстро теряетесь в карри и синтаксисе. Вместо этого отрисуйте их как «церковные числа» и просто подумайте о том, что каждая функция должна принимать и возвращать. Помните, что номер церкви всегда функция, которая принимает Int и возвращает Int. Он никогда не растет и не сжимается от этого, независимо от того, сколько раз он был вложен.


Это стоит принимать этот пример в несколько других направлениях, потому что мы можем играть некоторые более глубокие идеи FP, а также как Swift действительно должно быть написана (не то же самое ....)

Во-первых, написание церковных номеров в остром стиле ... неэлегантное. Это просто плохо. Церковные номера определяются с точки зрения функционального состава, а не приложения, поэтому их следует писать в стиле без ИМО. В принципе, в любом месте вы видите { f in { x in ...} }, это просто уродливо и чрезмерно синтаксически. Поэтому нам нужна функциональная композиция. Хорошо, мы можем углубиться в некоторые экспериментальные stdlib features и получить, что

infix operator ∘ : CompositionPrecedence 

precedencegroup CompositionPrecedence { 
    associativity: left 
    higherThan: TernaryPrecedence 
} 

public func ∘<T, U, V>(g: @escaping (U) -> V, f: @escaping (T) -> U) -> ((T) -> V) { 
    return { g(f($0)) } 
} 

Теперь, что это делает для нас?

func numToChurch(_ n: Int) -> Church { 
    // Church(0) does not apply the function 
    guard n > 0 else { return zero() } 
    return { f in f ∘ numToChurch(n - 1)(f) } 
} 

func succ(_ n: @escaping Church) -> Church { 
    return { f in f ∘ n(f) } 
} 

func sum(_ n: @escaping Church) -> (@escaping Church) -> Church { 
    return { m in { f in 
     n(f) ∘ m(f) 
     } } 
} 

Таким образом, мы не должны говорить о x больше. И мы более глубоко фиксируем сущность церковных чисел, ИМО. Суммирование их эквивалентно функциональному составу.

Но все, что сказал, ИМО это не очень быстро. Swift хочет структуру и методы, а не функции. Это определенно не хочет функцию верхнего уровня, которая называется zero(). Это ужасно Свифт. Итак, как мы реализуем церковные числа в Свифте?Подъем в тип.

struct Church { 
    typealias F = (@escaping (Int) -> Int) -> (Int) -> Int 
    let applying: F 

    static let zero: Church = Church{ _ in { $0 } } 

    func successor() -> Church { 
     return Church{ f in f ∘ self.applying(f) } 
    } 

    static func + (lhs: Church, rhs: Church) -> Church { 
     return Church{ f in lhs.applying(f) ∘ rhs.applying(f) } 
    } 
} 

extension Church { 
    init(_ n: Int) { 
     if n <= 0 { self = .zero } 
     else { applying = { f in f ∘ Church(n - 1).applying(f) } } 
    } 
} 

extension Int { 
    init(_ church: Church) { 
     self = church.applying{ $0 + 1 }(0) 
    } 
} 

Int(Church(3) + Church(7).successor() + Church.zero) // 11 
+0

Большое вам спасибо. Я чувствовал себя очень обескураженным и потерянным, пытаясь понять церковные цифры в Свифте. Это было очень полезно. – fayche

3

@escaping является частью типа аргумента, так что вам нужно сделать это как:

func numToChurch(n: Int) -> (@escaping (Int) -> Int) -> (Int) -> Int { 
//       ^~~~~~~~~ 

Полный рабочий код:

func numToChurch(n: Int) -> (@escaping (Int) -> Int) -> (Int) -> Int { 
//       ^~~~~~~~~      ^~~~~~ 
    return { (f: @escaping (Int) -> Int) -> (Int) -> Int in 
//    ^~~~~~~~~ 
     if n == 0 { 
      return { x in x } 
     } else { 
      return { (x : Int) -> Int in 
       return f(numToChurch(n: n - 1)(f)(x)) 
      } 
     } 
    } 
} 

func churchToNum(f: (@escaping (Int) -> Int) -> (Int) -> Int) -> Int { 
//     ^~~~~~~~~ 
    return f({ (i : Int) -> Int in 
     return i + 1 
    })(0) 
} 

let church = numToChurch(n: 4) 
let num = churchToNum(f: church) 

Примечание:

  1. Ваш тип возврата numToChurch неправильный даже без части @escaping. Вам не хватает -> Int.

  2. Я добавил базу n == 0 кейс в numToChurch, в противном случае это будет бесконечная рекурсия.

  3. Поскольку результат numToChurch имеет экранирующее закрытие, необходимо добавить те же аннотации, что и для churchToNum.

+0

Благодарим вас и @ rob-napier за ваши комментарии. Ваши изменения помогли решить мою проблему, не связанную с экранированием, и теперь я понимаю, почему это произошло. Я также пытаюсь понять, как реализовать функции FP, такие как преемник. Я включил больше кода в свой оригинальный пост - если бы вы могли предоставить обратную связь и некоторые рекомендации о том, как действовать, я бы очень признателен! – fayche

Смежные вопросы