как работает "undefined" в Haskell
Мне интересно значение "undefined" в Haskell. Это интересно, потому что вы можете поместить его где угодно, и Хаскелл будет счастлив. Ниже приведены все a-ok
[1.0, 2.0, 3.0 , undefined] ::[Float]
[1, 2 ,3 undefined, 102312] :: [Int]
("CATS!", undefined) :: (String, String)
....and many more
как undefined работает под капотом? Что позволяет иметь данные, относящиеся к каждому типу данных? Можно ли мне определить такое значение, которое я могу поместить везде, или это какой-то особый случай?
6 ответов
в этом нет ничего особенного undefined
. Это просто исключительное значение - вы можете представить его с бесконечным циклом, или сбоем, или сегфолтом. Один из способов написать это как сбой:
undefined :: a
undefined | False = undefined
или цикл:
undefined = undefined
это исключительное значение, которое может быть элементом любого типа.
поскольку Haskell ленив, вы все равно можете использовать такие значения в вычислениях. Е. Г.
> length [undefined, undefined]
2
но в противном случае, это просто естественное следствие полиморфизм и нестрогость.
интересное свойство, которое вы изучаете, это undefined
тип a
для любого типа a
мы выбираем, т. е. undefined :: a
без ограничений. Как отмечали другие,undefined
может рассматриваться как ошибка или бесконечный цикл. Я хотел бы утверждать, что это лучше рассматривать как "бессмысленно истинное утверждение". Это почти неизбежное отверстие в любой системе типов, тесно связанной с проблемой остановки, но интересно думать об этом с точки зрения логика.
один способ думать о программировании с типами заключается в том, что это головоломка. Кто-то дает вам тип и просит вас реализовать функцию, которая имеет этот тип. Например,
Question: fn :: a -> a
Answer: fn = \x -> x
- это легко. Нам нужно произвести a
для любого типа a
, но нам дали один в качестве ввода, поэтому мы можем просто вернуть его.
С undefined
эта игра всегда легко
Question: fn :: Int -> m (f [a])
Answer: fn = \i -> undefined -- backdoor!
так давайте избавимся от него. Смысл в undefined
легче всего, когда живешь в мире без него. Теперь наша игра становится сложнее. Иногда это возможно
Question: fn :: (forall r. (a -> r) -> r) -> a
Answer: fn = \f -> f id
, но вдруг это иногда не возможно!
Question: fn :: a -> b
Answer: fn = ??? -- this is `unsafeCoerce`, btw.
-- if `undefined` isn't fair game,
-- then `unsafeCoerce` isn't either
или это?
-- The fixed-point combinator, the genesis of any recursive program
Question: fix :: (a -> a) -> a
Answer: fix = \f -> let a = f a in a
-- Why does this work?
-- One should be thinking of Russell's
-- Paradox right about now. This plays
-- the same role as a non-wellfounded set.
что является законным, потому что Хаскелл let
привязка ленива и (как правило)рекурсивные. Теперь мы в золоте.
Question: fn :: a -> b
Answer: fn = \a -> fix id -- This seems unfair?
даже без undefined
встроенный мы можем восстановить его в нашей игре, используя любой старый бесконечный цикл. Этот типы проверяют. Чтобы по-настоящему помешать себе иметь undefined
в Haskell нам нужно будет решить проблему остановки.
Question: undefined :: a
Answer: undefined = fix id
теперь, как вы видели,undefined
полезно для отладки, так как он может быть заполнителем для любого значения. К сожалению, это ужасно для операций, так как это приводит либо к бесконечному циклу, либо к немедленному сбою. Это также очень плохо для нашей игры, потому что это позволяет нам обманывать. Наконец, я надеюсь, что продемонстрировал, что это довольно трудно не есть undefined
пока ваш язык имеет (потенциально бесконечные) циклы.
существуют такие языки, как Agda и Coq, которые торгуют петлями, чтобы действительно устранить undefined
. Они делают это, потому что эта игра, которую я изобрел, может, в некоторых случаях, на самом деле быть очень ценным. Он может кодировать логические утверждения и, таким образом, использоваться для формирования очень, очень строгих математических доказательств. Ваши типы представляют теоремы, а ваши программы-гарантии того, что эта теорема подтвержденный. Существование undefined
будет означать, что все теоремы substantiatable и таким образом сделать всю систему ненадежной.
но в Haskell мы больше заинтересованы в циклировании, чем в проверке, поэтому мы бы предпочли fix
чем быть уверенным, что не было undefined
.
Если я попробую undefined
в GHCi я получаю исключение:
Prelude> undefined
*** Exception: Prelude.undefined
поэтому я полагаю, что он просто реализован как бросание exception:
http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html#g:2
как undefined
работы? Ну, лучший ответ, ИМХО, это не работает. Но чтобы понять этот ответ, мы должны проработать его последствия, которые не очевидны для новичка.
в основном, если у нас есть undefined :: a
, что это означает для системы типов, это undefined
может появиться в любом месте. Почему? Потому что в Haskell, когда вы видите выражение, которое имеет определенный тип, вы можете specialize тип путем последовательной замены все экземпляры любой из переменных типа для любого другого типа. Знакомыми примерами могут быть такие вещи:
map :: (a -> b) -> [a] -> [b]
-- Substitute b := b -> x
map :: (a -> b -> c) -> [a] -> [b -> c]
-- Substitute a := Int
map :: (Int -> b -> c) -> [Int] -> [b -> c]
-- etc.
в случае map
, как это работает? Ну, это сводится к тому, что предоставляют все необходимое для получения ответа, независимо от того, какие замены и специализации мы делаем для его переменных типа. Если у вас есть список и функция, которая потребляет значения того же типа, что и элементы списка, вы можете сделать какую карту делает, точка.
но в случае undefined :: a
, что эта подпись будет означать, что независимо от типа a
может специализироваться на,undefined
может производить значение этого типа. Как он может это сделать? Ну, на самом деле, это не может, поэтому, если программа действительно достигает шага, где значение undefined
необходимо, нет способа продолжить. Единственное, что программа может сделать в этот момент, это потерпеть неудачу
история этого другого случая аналогична, но разные:
loop :: a
loop = loop
вот, мы можем это доказать loop
типа a
этим сумасшедшим аргументом: предположим, что loop
типа a
. Он должен произвести значение типа a
. Как он может это сделать? Легко, он просто называет loop
. Престо!
это звучит дико, верно? Ну, дело в том, что это действительно ничем не отличается от того, что происходит во втором уравнении этого определения map
:
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
в ту же секунду уравнение, f x
типа b
и (f x:)
типа [b] -> [b]
; теперь, чтобы завершить доказательство того, что map
действительно имеет тип наших претензий подписи, нам нужно произвести [b]
. Так как мы это делаем? Предполагая, что map
тип мы пытаемся доказать это!
алгоритм вывода типа Haskell работает так, что он сначала догадывается, что тип выражения a
, и тогда он только меняет свою догадку, когда находит что-то, что противоречит этому предположение. undefined
typechecks к a
потому что это откровенная ложь. loop
typechecks к a
потому что рекурсия разрешена, и все loop
does является рекурсивным.
EDIT: Какого черта, я мог бы также разобрать один пример. Вот неофициальная демонстрация того, как вывести тип map
из этого определения:
map f [] = []
map f (x:xs) = f x : map f xs
это звучит так:
- мы начнем с предварительного предположения, что
map :: a
. - но map принимает два аргумента, поэтому
a
не может быть типом. Мы пересматриваем наше предположение следующим образом:map :: a -> b -> c; f :: a
. - но, как мы видим в первом уравнении, второй аргумент-это список:
map :: a -> [b] -> c; f :: a
. - но, как мы также можем видеть в первом уравнении, результат также является списком:
map :: a -> [b] -> [c]; f :: a
. - во втором уравнении мы сопоставляем шаблон со вторым аргументом против конструктора
(:) :: b -> [b] -> [b]
. Это означает, что в этом уравнении,x :: b
иxs :: [b]
. - рассмотрим правую часть второго уравнения. С результатом
map f (x:xs)
должно быть типа[c]
, что означаетf x : map f xs
также должен быть типа[c]
. - учитывая тип конструктора
(:) :: c -> [c] -> [c]
, что значитf x :: c
иmap f xs :: [c]
. - в (7) мы пришли к выводу, что
map f xs :: [c]
. Мы предполагали, что в (6), и если бы мы пришли к другому выводу в (7), это было бы ошибкой типа. Мы также можем теперь погрузиться в это выражение и посмотреть, какие типы это требуетf
иxs
иметь, но чтобы сделать более длинную историю короткой, все будет проверено. - с
f x :: c
иx :: b
, мы должны заключить, чтоf :: b -> c
. Итак, теперь мы получаемmap :: (b -> c) -> [b] -> [c]
. - мы закончили.
тот же процесс, но для loop = loop
:
- мы условно предполагаем, что
loop :: a
. -
loop
не принимает аргументов, поэтому его тип согласован сa
до сих пор. - правая сторона
loop
isloop
, которому мы условно присвоили типa
, так что проверено. - больше нечего рассматривать; мы закончили.
loop
типаa
.
Ну, в принципе undefined = undefined
- Если вы попытаетесь оценить его, вы получите бесконечный цикл. Но Хаскелл-не строгий язык, так что head [1.0, 2.0, undefined]
Не оценивать все элементы списка, поэтому он печатает 1.0
и завершается. Но если вы печатаете show [1.0, 2.0, undefined]
, вы увидите [1.0,2.0,*** Exception: Prelude.undefined
.
как это может быть всех типов... Ну, если выражение типа A
, это означает, что оценка его либо даст значение типа A
, или оценка будет расходиться, не давая никакого значения совсем. Теперь,undefined
всегда расходятся, поэтому он вписывается в определение для каждого мыслимого типа A
.
кроме того, хороший пост в блоге по связанным темам: http://james-iry.blogspot.ru/2009/08/getting-to-bottom-of-nothing-at-all.html
есть две отдельные вещи, чтобы отметить о undefined:
- вы можете поставить неопределенный почти везде, и typechecker будет счастлив. Это потому, что undefined имеет тип (forall a. а).
- вы можете поставить undefined почти везде, и он будет иметь определенное представление во время выполнения.
для второго, GHC комментарий четко написано:
представление ⊥ должно быть указателем: это объект, который при вычислении создает исключение или входит в бесконечный цикл.
для более подробной информации вы можете прочитать в статье Бесхребетный Tagless G-Machine.