Предположим, у вас есть функция
pierce :: ((a -> b) -> a) -> a
Импорт
data Void
из Data.Void
.
Теперь мы можем играть в игры.Мы можем создавать экземпляры a
и b
в типе pierce
для того, что нам нравится.Давайте определим
type A p = Either p (p -> Void)
и создадим экземпляр с помощью
a ~ A p
b ~ Void
Итак
pierce :: ((A p -> Void) -> A p) -> A p
Давайте напишем помощника:
noNegate :: forall p r. (A p -> Void) -> r
noNegate apv = absurd (n m)
where
m :: p -> Void
m = apv . Left
n :: (p -> Void) -> Void
n = apv . Right
Теперь мы можемпойти на убийство:
lem :: Either p (p -> Void)
lem = pierce noNegate
Если бы существовала эта функция, это было бы очень странно.
lem @Void = Right id
lem @() = Left ()
lem @Int = Left ... -- some integer, somehow
Поведение этой функции кажется таким странным, потому что оно нарушает параметричность, которую использует функции Haskell.не может сделать, но все только ухудшается.
Возможно (но немного раздражает) кодировать произвольную машину Тьюринга как тип Haskell.И можно спроектировать тип, представляющий доказательство того, что конкретная машина Тьюринга остановится (в основном трассировка выполнения с индексированием типа).Применение lem
к такому типу трассировки решило бы проблему остановки.
Благодаря лени Хаскелла некоторые «невозможные» функции оказываются полезными, хотя и частичными.Например,
fix :: (a -> a) -> a
формально абсурден, поскольку fix id
утверждает, что дает вам все, что вы хотите.pierce
не такая функция.Давайте попробуем написать это:
pierce :: ((a -> b) -> a) -> a
pierce f = _
Что должно идти с правой стороны? только способ сделать a
- применить f
.
pierce f = f _
Теперь мы должны предоставить что-то типа a -> b
.У нас его нет.Мы не знаем, что такое b
, поэтому мы не можем использовать обычную хитрость, начиная с какого-нибудь конструктора b
, чтобы получить удар.Ничто не может улучшить нашу b
.Поэтому самое лучшее, что мы можем сделать, это
pierce f = f (const undefined)
, который не выглядит отдаленно полезным.