または具体的に言うと、なぜfoldrを使用してリストをエンコードし、反復を使用して数値をエンコードするのですか?
長文の導入で申し訳ありませんが、質問したいことの名前をどのように挙げればよいかよくわからないので、最初にいくつかの説明をする必要があります. これは、この CAMcCann の投稿から大きく引き出されていますが、これは私の好奇心を完全に満足させるものではありません。また、ランク n 型と無限の怠惰な問題についても手短に説明します。
データ型を関数としてエンコードする 1 つの方法は、ケースごとに 1 つの引数を受け取る「パターン マッチング」関数を作成することです。各引数は、そのコンストラクターに対応する値を受け取り、すべての引数が同じ結果の型を返す関数です。
これはすべて、非再帰型の場合に期待どおりに機能します
--encoding data Bool = true | False
type Bool r = r -> r -> r
true :: Bool r
true = \ct cf -> ct
false :: Bool r
false = \ct cf -> cf
--encoding data Either a b = Left a | Right b
type Either a b r = (a -> r) -> (b -> r) -> r
left :: a -> Either a b r
left x = \cl cr -> cl x
right :: b -> Either a b r
right y = \cl cr -> cr y
ただし、パターン マッチングとの優れた類似性は、再帰型ではうまくいきません。私たちは次のようなことをしたくなるかもしれません
--encoding data Nat = Z | S Nat
type RecNat r = r -> (RecNat -> r) -> r
zero = \cz cs -> cz
succ n = \cz cs -> cs n
-- encoding data List a = Nil | Cons a (List a)
type RecListType a r = r -> (a -> RecListType -> r) -> r
nil = \cnil ccons -> cnil
cons x xs = \cnil ccons -> ccons x xs
しかし、これらの再帰的な型定義を Haskell で書くことはできません! 通常の解決策は、コンス/成功ケースのコールバックを、再帰の最初のレベルだけでなく、すべてのレベルに強制的に適用することです (つまり、フォールド/イテレータを記述します)。このバージョンr
では、再帰型が次のような戻り値の型を使用します。
--encoding data Nat = Z | S Nat
type Nat r = r -> (r -> r) -> r
zero = \cz cf -> cz
succ n = \cz cf -> cf (n cz cf)
-- encoding data List a = Nil | Cons a (List a)
type recListType a r = r -> (a -> r -> r) -> r
nil = \z f -> z
cons x xs = \z f -> f x (xs z f)
このバージョンは機能しますが、一部の関数の定義が非常に難しくなります。たとえば、リストの "tail" 関数や数値の "predecessor" 関数を書くことは、パターン マッチングを使用できる場合は簡単ですが、代わりに折り畳みを使用する必要がある場合は注意が必要です。
だから私の本当の質問に:
フォールドを使用したエンコーディングが、仮想の「パターン マッチング エンコーディング」と同じくらい強力であることをどのように確認できますか? パターンマッチングを介して任意の関数定義を取得し、代わりに折り畳みのみを使用して機械的に変換する方法はありますか? (もしそうなら、これはまた、foldr に関して、tail や foldl などのトリッキーな定義をあまり魔法のようにしないようにするのにも役立ちます)
Haskell 型システムが「パターンマッチング」エンコーディングで必要な再帰型を許可しないのはなぜですか? . 経由で定義されたデータ型で再帰型のみを許可する理由はあり
data
ますか? 再帰代数データ型を直接消費する唯一の方法はパターン マッチングですか? 型推論アルゴリズムと関係がありますか?