Я просто хочу указать, что типы данных и сопоставление образцов (в первом приближении) являются просто полезными, но избыточными языковыми функциями, которые могут быть реализованы исключительно с использованием лямбда-исчисления. Если вы понимаете, как их реализовать в лямбда-исчислении, вы можете понять, почему они не нуждаются в Eq
для реализации соответствия шаблону.
Внедрение типов данных в лямбда-исчислении называется «Церковное кодирование» их (после церкви Алонсо, которая продемонстрировала, как это сделать). Церковно-кодированные функции также известны как «Стиль продолжения прохождения».
Это называется «стиль продолжения прохождения», потому что вместо предоставления значения вы предоставляете функцию, которая будет обрабатывать значение. Так, например, вместо того, чтобы дать пользователю по электронной 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
Но Алонзо Чёрч был умным и заметил, что для любого типа с несколькими конструкторами, мы можем просто предоставляют отдельную функцию для каждого конструктора. Таким образом, в случае указанного выше типа, вместо обеспечения функции типа (Either a b-> x)
, мы можем вместо этого предоставить две отдельные функции, один для a
, и один для 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
есть только две функции, которые могут быть выше типа:
true :: a -> a -> a
true a _ = a
false :: a -> a -> a
false _ a = a
из-за I софиморфизм, мы можем гарантировать, что все кодовые кодировки будут иметь столько реализаций, сколько возможной стоимости исходного типа данных, поэтому неслучайно существует ровно две функции, которые обитают IndirectBool
, точно так же, как ровно два конструктора для Bool
.
Но как мы можем сопоставить матч на нашем IndirectBool
? Например, с помощью обычного Bool
, мы могли бы просто написать:
expression1 :: a
expression2 :: a
case someBool of
True -> expression1
False -> expression2
Ну, с нашей IndirectBool
он уже поставляется с инструментами для деконструкции себя. Мы бы просто написать:
myIndirectBool expression1 expression2
Обратите внимание, что если myIndirectBool
является 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
, всегда выбирая свой первый аргумент. Если я false
, я докажу, что я «равен» false
, всегда выбирая мой второй аргумент. Короче говоря, конструктивное «равенство» сводится к «позиционному равенству», которое всегда определяется в лямбда-исчислении, и если мы можем отличить один параметр как «первый», а другой как «второй», это все, что нам нужно способность «сравнивать» конструкторы.
Я не ожидал, что коснусь деталей до сих пор, когда я задавал свой вопрос, спасибо за просветление. Когда я читал это, у меня было более одного «ага». Особенно определения «истина» и «ложь» до тех пор, пока я не увижу ниже приведенный ниже пример 6 строк, полностью изменили его. – epsilonhalbe
Хотя это может показаться на первый взгляд теоретическим и надуманным, это сопоставление шаблонов в стиле CPS на самом деле часто проявляется на практике. Например, Smalltalk не имеет операторов if, но логические методы имеют методы, которые получают обратные вызовы для истинного и для ложного случая. – hugomg