データ型とパターンマッチング(最初の概算)は単に有用ですが、ラムダ計算を使用して純粋に実装できる冗長な言語機能であることを指摘したいと思います。Eq
ラムダ計算でそれらを実装する方法を理解していれば、パターンマッチングを実装する必要がない理由を理解できます。
ラムダ計算でデータ型を実装することは、それらを「チャーチエンコード」することとして知られています(これを行う方法を示したAlonzo Churchにちなんで)。チャーチエンコードされた関数は、「継続渡しスタイル」とも呼ばれます。
値を提供する代わりに、値を処理する関数を提供するため、これは「継続渡しスタイル」と呼ばれます。したがって、たとえば、ユーザーにを与えるInt
代わりに、次のタイプの値をユーザーに与えることができます。
type IndirectInt = forall x . (Int -> x) -> x
上記のタイプは、と「同型」Int
です。IndirectInt
「同型」は、任意のものをInt
:に変換できることを示すための空想的な言い方です。
fw :: IndirectInt -> Int
fw indirect = indirect id
Int
...そして私たちは任意のものをIndirectInt
:に変換することができます
bw :: Int -> IndirectInt
bw int = \f -> f int
... そのような:
fw . bw = id -- Exercise: Prove this
bw . fw = id -- Exercise: Prove this
継続渡しスタイルを使用すると、任意のデータ型をラムダ計算項に変換できます。次のような単純なタイプから始めましょう。
data Either a b = Left a | Right b
継続渡しスタイルでは、これは次のようになります。
type IndirectEither a b = forall x . (Either a b -> x) -> x
しかし、Alonzo Churchは賢く、複数のコンストラクターを持つタイプの場合、コンストラクターごとに個別の関数を提供できることに気づきました。したがって、上記のタイプの場合、タイプの関数を提供する代わりに、代わりに(Either a b -> x)
2つの別々の関数を提供できます。1つは、、もう1a
つは、b
です。
type IndirectEither a b = forall x . (a -> x) -> (b -> x) -> x
-- Exercise: Prove that this definition is isomorphic to the previous one
Bool
コンストラクターに引数がないようなタイプはどうですか?ええと、 (演習:これを証明する)Bool
と同型Either () ()
なので、次のようにエンコードできます。
type IndirectBool = forall x . (() -> x) -> (() -> x) -> x
そして、(演習:これを証明する)() -> x
と同型であるx
ため、さらに次のように書き直すことができます。
type IndirectBool = forall x . x -> x -> x
上記のタイプを持つことができる関数は2つだけです。
true :: a -> a -> a
true a _ = a
false :: a -> a -> a
false _ a = a
同型であるため、すべてのチャーチエンコーディングに、元のデータ型の可能な値と同じ数の実装が含まれることを保証できます。IndirectBool
したがって、のコンストラクタが2つあるのと同じように、に生息する関数が2つあるのは偶然ではありませんBool
。
しかし、どのようにパターンマッチングを行うのIndirectBool
でしょうか。たとえば、通常の場合、次のBool
ように書くことができます。
expression1 :: a
expression2 :: a
case someBool of
True -> expression1
False -> expression2
まあ、私たちIndirectBool
にはすでにそれ自体を分解するためのツールが付属しています。私たちはただ書くでしょう:
myIndirectBool expression1 expression2
の場合は最初の式を選択し、そうである場合myIndirectBool
は2番目の式を選択することに注意してください。これは、その値が何らかの形でパターン一致している場合と同じです。true
false
で同じことをしてみましょうIndirectEither
。通常Either
のを使用して、次のように記述します。
f :: a -> c
g :: b -> c
case someEither of
Left a -> f a
Right b -> g b
を使用して、次のIndirectEither
ように記述します。
someIndirectEither f g
要するに、継続渡しスタイルで型を書く場合、継続はcase構造のcaseステートメントのようなものなので、私たちがしているのは、それぞれの異なるcaseステートメントを引数として関数に渡すことだけです。
Eq
これが、型のパターンマッチングの感覚を必要としない理由です。ラムダ計算では、型は、提供された引数から選択する引数を定義するだけで、「等しい」ものを決定します。
したがって、私がである場合は、常に最初の引数を選択することで、true
私が「等しい」ことを証明します。true
私がである場合、常に2番目の引数を選択することにより、false
私が「等しい」ことを証明します。false
要するに、コンストラクターの「等式」は、ラムダ計算で常に定義される「位置の等式」に要約されます。あるパラメーターを「最初の」として、別のパラメーターを「2番目の」として区別できる場合、必要なのはそれだけです。コンストラクターを「比較」する機能。