как работает "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

это звучит так:

  1. мы начнем с предварительного предположения, что map :: a.
  2. но map принимает два аргумента, поэтому a не может быть типом. Мы пересматриваем наше предположение следующим образом:map :: a -> b -> c; f :: a.
  3. но, как мы видим в первом уравнении, второй аргумент-это список:map :: a -> [b] -> c; f :: a.
  4. но, как мы также можем видеть в первом уравнении, результат также является списком:map :: a -> [b] -> [c]; f :: a.
  5. во втором уравнении мы сопоставляем шаблон со вторым аргументом против конструктора (:) :: b -> [b] -> [b]. Это означает, что в этом уравнении, x :: b и xs :: [b].
  6. рассмотрим правую часть второго уравнения. С результатом map f (x:xs) должно быть типа [c], что означает f x : map f xs также должен быть типа [c].
  7. учитывая тип конструктора (:) :: c -> [c] -> [c], что значит f x :: c и map f xs :: [c].
  8. в (7) мы пришли к выводу, что map f xs :: [c]. Мы предполагали, что в (6), и если бы мы пришли к другому выводу в (7), это было бы ошибкой типа. Мы также можем теперь погрузиться в это выражение и посмотреть, какие типы это требует f и xs иметь, но чтобы сделать более длинную историю короткой, все будет проверено.
  9. с f x :: c и x :: b, мы должны заключить, что f :: b -> c. Итак, теперь мы получаем map :: (b -> c) -> [b] -> [c].
  10. мы закончили.

тот же процесс, но для loop = loop:

  1. мы условно предполагаем, что loop :: a.
  2. loop не принимает аргументов, поэтому его тип согласован с a до сих пор.
  3. правая сторона loop is loop, которому мы условно присвоили тип a, так что проверено.
  4. больше нечего рассматривать; мы закончили. 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.