マルチパラメーター関数にカリー化を使用しています。これは、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 つ適用f
しn
ます。
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)
} } }
}
ここでの深い教訓は、Church
typealias の力です。チャーチ数を「何とか何とかする関数」と考えようとすると、カレーと構文ですぐに迷子になります。代わりに、それらを「教会の数字」に抽象化し、すべての関数が何を取り、何を返す必要があるかを考えてください。教会数は常に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