4

私はSwift 3でChurch Numeralsを実装しようとしています.現在、私は持っています:

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))
        }
    }
}

しかし、変更を行った後でも、同じエラーが表示され続け、「f:」の後に別の @escaping を追加することをお勧めします。これは、関数パラメーターを @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))
            }
        }
    }
4

2 に答える 2

8

マルチパラメーター関数にカリー化を使用しています。これは、Swift で物事を表現するための非常に自然な方法ではなく、物事を複雑にしています。(Swift は関数型プログラミング言語ではありません。)

リンクされた記事にあるように、「すべての教会の数字は 2 つのパラメーターを取る関数です。」そうしてください。2 パラメーター関数にします。

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

これは、関数とその引数の 2 つのパラメーターを取る関数です。

ここで、引数を関数で 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
        } }
}

1 と 2 はほぼ同じです。

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

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

の署名はsucc何ですか? これは Church を受け取り、Church を返します。

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

これは Swift であるため、 と を追加@escaping_て、物事をより自然にするために少し微調整する必要があります。(Swift は関数型言語ではありません。問題を別の方法で分解します。関数を合成することは自然な状態ではないため、構文が多すぎても驚かないでください。) 実装方法? にもう 1 つ適用fnます。

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

繰り返しますが、 の性質はsum何ですか? つまり、Church を受け取り、Church を受け取って返す関数です。

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

繰り返しますが、Swift. (そして、上記のように、部分をもう少し明確にするために、追加の let バインディングを追加しました。)

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

ここでの深い教訓は、Churchtypealias の力です。チャーチ数を「何とか何とかする関数」と考えようとすると、カレーと構文ですぐに迷子になります。代わりに、それらを「教会の数字」に抽象化し、すべての関数が何を取り、何を返す必要があるかを考えてください。教会数は常にInt を取り、Int を返す関数であることを思い出してください。何回ネストされても、そこから成長したり縮小したりすることはありません。


FP のいくつかのより深いアイデアと、Swift が実際にどのように書かれるべきか (これらは同じではありません....)

まず、尖ったスタイルで教会番号を書くことは...エレガントではありません。気分が悪いだけです。チャーチ番号は、アプリケーションではなく機能構成の観点から定義されるため、ポイントフリー スタイルの IMO で記述する必要があります。基本的に、あなたが見るところはどこでも{ f in { x in ...} }、それはただ醜く、過度に構文化されています. したがって、機能的な構成が必要です。OK、いくつかの実験的なstdlib 機能を掘り下げて、それを取得できます

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ません。そして、私たちは教会の数の本質をより強力に捉えています.IMO. それらを合計すると、機能合成に相当します。

しかし、そうは言っても、これは素晴らしい Swift ではありません。Swift が必要とするのは、関数ではなく、構造とメソッドです。と呼ばれるトップレベルの関数は絶対に必要ありませんzero()。恐ろしいスイフトです。では、Swift で教会番号を実装するにはどうすればよいでしょうか。タイプに持ち上げることによって。

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
于 2016-10-03T17:10:20.500 に答える
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ます。

于 2016-10-03T17:17:06.457 に答える