Как Data.Void.absurd отличается от ⊥?
Я видел обратную абсурдную функцию ранее, и хотя мне ясно, что любая возможная реализация drusba :: a -> Void
никогда не закончится (в конце концов, это невозможно для построения Void
), я не понимаю, почему то же самое не относится к absurd :: Void -> a
. Рассмотрим реализацию GHC:
newtype Void = Void Void
absurd :: Void -> a
absurd a = a `seq` spin a where
spin (Void b) = spin b
spin
, как мне кажется, бесконечно распутывает бесконечную серию оберток Void
newtype и никогда не вернется, даже если вы можете найти Void
для его передачи. Реализация, которая была бы неразличимой, была бы такой:
absurd :: Void -> a
absurd a = a `seq` undefined
Учитывая это, почему мы говорим, что absurd
- это правильная функция, которая заслуживает того, чтобы жить в Data.Void, но
drusba :: a -> Void
drusba = undefined
- функция, которая не может быть определена? Это что-то вроде следующего?
absurd
- это полная функция, дающая не-нижний результат для любого ввода в его (пустой) области, тогда как drusba
является частичным, давая нижние результаты для некоторых (действительно всех) входов в его домене.
Ответы
Ответ 1
Data.Void
переместился из пакета void
в base
в базовой версии 4.8
(GHC 7.10). Если вы посмотрите на файл Cabal для void
, вы увидите, что он включает только Data.Void
для старых версий base
. Теперь void
определяется как chi предлагает:
data Void
absurd :: Void -> a
absurd a = case a of {}
что вполне справедливо.
Я думаю, что идея старого определения выглядит примерно так:
Рассмотрим тип
data BadVoid = BadVoid BadVoid
Этот тип не выполняет задание, потому что на самом деле можно определить недвоенное (коиндуктивное) значение с этим типом:
badVoid = BadVoid badVoid
Мы можем исправить эту проблему, используя аннотацию строгости, которая (по крайней мере, вроде) заставляет тип быть индуктивным:
data Void = Void !Void
В предположениях, которые могут или не могут фактически выполняться, но, по крайней мере, морально удерживаться, мы можем законно провести индукцию по любому индуктивному типу. Так
spin (Void x) = spin x
всегда заканчивается, если, предположительно, у нас есть что-то типа void
.
Последний шаг заменяет тип данных single-strict-constructor новым типом:
newtype Void = Void Void
Это всегда законно. Однако определение spin
изменило значение из-за различной семантики соответствия шаблонов между data
и newtype
. Чтобы точно сохранить значение, spin
должно быть написано
spin !x = case x of Void x' -> spin x'
(избегая spin !(Void x)
, чтобы обрезать теперь исправленную ошибку во взаимодействии конструкторов newtype и шаблонов ударов, но для GHC 7.10 (ha!) эта форма фактически не выводит желаемое сообщение об ошибке, поскольку оно "оптимизировано" в бесконечный цикл), в котором точка absurd = spin
.
К счастью, на самом деле это не имеет большого значения, потому что все старое определение - это немного глупое упражнение.
Ответ 2
По историческим причинам любой тип данных Haskell (включая newtype
) должен иметь хотя бы один конструктор.
Следовательно, для определения Void
в "Haskell98" нужно полагаться на рекурсию уровня newtype Void = Void Void
. Нет (без дна) значения этого типа.
Функция absurd
должна опираться на рекурсию (уровень значения), чтобы справиться с "странной" формой типа Void
.
В более современном Haskell с некоторыми расширениями GHC мы можем определить тип данных с нулевым конструктором, что приведет к более четкому определению.
{-# LANGUAGE EmptyDataDecls, EmptyCase #-}
data Void
absurd :: Void -> a
absurd x = case x of { } -- empty case
Случай исчерпывающий - он обрабатывает все конструкторы Void
, все они равны нулю. Следовательно, оно является полным.
В некоторых других функциональных языках, таких как Agda или Coq, вариант вышеприведенного случая является идиоматическим при работе с пустыми типами, такими как Void
.