Есть ли способ узнать, бесконечен ли список в Haskell? Причина в том, что я не хочу применять такие функции, как length
, к бесконечным спискам.
Как узнать, бесконечен ли список?
Ответы (6)
Применение length
к неизвестным спискам, как правило, является плохой идеей, как практически из-за бесконечных списков, так и концептуально, потому что часто оказывается, что вам все равно на самом деле не нужна длина.
Вы сказали в комментарии:
Я новичок в Haskell, так что теперь, разве бесконечные структуры не делают мои программы очень уязвимыми?
Не совсем. Хотя некоторые из нас хотели бы иметь лучшие способы различать обязательно конечные и обязательно бесконечные данные, вы всегда в безопасности, когда создаете, обрабатываете и проверяете< /em> ленивые структуры инкрементно. Вычисление длины явно не инкрементальное, а проверка того, является ли длина выше или ниже некоторого порогового значения, равного, и очень часто это все, что вам нужно сделать!
Тривиальный случай — проверка непустых списков. isNonEmpty xs == length xs > 0
— плохая реализация, потому что она проверяет неограниченное количество элементов, тогда как проверки одного будет достаточно! Сравните это:
isNonEmpty [] = False
isNonEmpty (_:_) = True
Мало того, что это безопасно применять к бесконечному списку, это также намного эффективнее для конечных списков - это занимает только постоянное время, а не время, линейное по длине списка. Это также как стандарт реализована библиотечная функция null
.
Чтобы обобщить это для тестирования длины относительно отсечения, вам, очевидно, потребуется изучить столько же списка, сколько и длину, с которой вы сравниваете. Мы можем сделать именно это и не более того, используя стандартную библиотечную функцию drop
:
longerThan :: Int -> [a] -> Bool
longerThan n xs = isNonEmpty $ drop n xs
Учитывая длину n
и (возможно, бесконечный) список xs
, он отбрасывает первые n
элементов xs
, если они существуют, а затем проверяет, не является ли результат пустым. Поскольку drop
создает пустой список, если n
больше длины списка, это работает корректно для всех положительных n
(увы, в стандартных библиотеках нет неотрицательных целочисленных типов, например, натуральных чисел).
Ключевым моментом здесь является то, что в большинстве случаев лучше думать о списках как об итерационных потоках, а не о простой структуре данных. Когда это возможно, вы хотите делать такие вещи, как преобразование, накопление, усечение и т. д., и либо создавать другой список в качестве вывода, либо проверять только известную конечную часть списка, а не пытаться обработать весь список за один раз.
Если вы используете этот подход, ваши функции не только будут правильно работать с конечными и бесконечными списками, но они также получат больше преимуществ от лени и оптимизатора GHC и, вероятно, будут работать быстрее и использовать меньше памяти. .
if length xs < n then ...
— неправильный способ сделать это :)
- person MatrixFrog; 12.09.2011
Проблема остановки сначала была доказана неразрешимой, если предположить, что оракул остановки существует, а затем была написана функция, выполняющая противоположное тому, что сказал оракул, произойдет. Давайте воспроизведем это здесь:
isInfinite :: [a] -> Bool
isInfinite ls = {- Magic! -}
Теперь мы хотим создать список impossibleList
, который делает противоположное тому, что isInfinite
говорит, что должен. Итак, если impossibleList
бесконечно, то на самом деле это []
, а если не бесконечно, то something : impossibleList
.
-- using a string here so you can watch it explode in ghci
impossibleList :: [String]
impossibleList =
case isInfinite impossibleList of
True -> []
False -> "loop!" : impossibleList
Попробуйте это сами в ghci с isInfinite = const True
и isInfinite = const False
.
isInfinite
были бы строгими (просто строгими, а не строгими по позвоночнику)... это было бы разумно, верно? Тогда impossibleList
это просто _|_
и нет противоречия.
- person luqui; 11.09.2011
isInfinite
на isNonEmpty
. Но isNonEmpty
, очевидно, можно реализовать.
- person interjay; 11.09.2011
[a] -> Bool
вместо isInfinite
сделает impossibleList
= ⊥. Я не уверен, что это доказывает, помимо того, что Haskell завершен по Тьюрингу: P
- person Tom Crockett; 14.09.2011
isInfinite
не может работать со всеми списками. В нем ничего не говорится о реализациях, которые работают для некоторых входных данных, и потребуются дополнительные аргументы, чтобы показать, что в чистом коде реализация в ответе sclv является наилучшей из возможных.
- person C. A. McCann; 14.09.2011
isInfinite
не может работать со всеми списками, потому что impossibleList
не является списком (это ⊥). В противном случае вы могли бы сказать, что это также показывает, что никакая возможная реализация isNonEmpty
не может работать со всеми списками, как я сказал в своем предыдущем комментарии.
- person interjay; 14.09.2011
const x
для некоторого определенного x
.
- person C. A. McCann; 14.09.2011
isInfinite
, но, учитывая полноту по Тьюрингу, строить списки с расходящимися хвостами в любой точке тривиально. Вам также понадобится какой-то индуктивный аргумент, чтобы показать, что реализация sclv является наилучшей из возможных.
- person C. A. McCann; 14.09.2011
isInfinite
, но и о разрешимости или проблеме остановки. Рассмотрим функции вида f::Int32->Bool
. Поскольку существует конечное число возможных входных данных, все они разрешимы. Но было бы легко использовать конструкцию, почти идентичную приведенной выше, чтобы якобы доказать, что любое нетривиальное f
не может быть вычислено для всех значений. Например, для f x = x>0
используйте impossibleValue = if (f impossibleValue) then 0 else 1
- person interjay; 14.09.2011
isInfinite
определяет бесконечность данного списка, т. е. возвращает True
для любого бесконечного списка и False
для любого конечного списка... то, что он делает, если вы даете ему ⊥, не здесь и не там. Мы можем вывести противоречие, только передав ему конечный список и показав, что он не возвращает False
(оценивается как True
или ⊥), или передав ему бесконечный список и показав, что он не возвращает True
(оценивается как False
или ⊥). Присвоение ему ⊥ и демонстрация того, что он производит ⊥, показывает только, что isInfinite
является строгим.
- person Tom Crockett; 15.09.2011
"[1..]"
), только значение [1,2,3,...]
, созданное им, независимо от того, упаковано ли оно в преобразователь или нет.
- person Will Ness; 31.03.2012
Нам не нужно решать проблему остановки, чтобы безопасно вызывать 'length'. Нам просто нужно быть консервативными; принять все, что имеет доказательство конечности, отвергнуть все, что нет (включая множество конечных списков). Именно для этого и нужны системы типов, поэтому мы используем следующий тип (t — это тип нашего элемента, который мы игнорируем):
terminatingLength :: (Finite a) => a t -> Int
terminatingLength = length . toList
Класс Finite будет содержать только конечные списки, поэтому средство проверки типов гарантирует, что у нас есть конечный аргумент. принадлежность к Конечному будет нашим доказательством конечности. Функция toList просто превращает конечные значения в обычные списки Haskell:
class Finite a where
toList :: a t -> [t]
Теперь, что наши экземпляры? Мы знаем, что пустые списки конечны, поэтому мы создаем тип данных для их представления:
-- Type-level version of "[]"
data Nil a = Nil
instance Finite Nil where
toList Nil = []
Если мы «включаем» элемент в конечный список, мы получаем конечный список (например, «x:xs» конечен, если «xs» конечен):
-- Type-level version of ":"
data Cons v a = Cons a (v a)
-- A finite tail implies a finite Cons
instance (Finite a) => Finite (Cons a) where
toList (Cons h t) = h : toList t -- Simple tail recursion
Любой, кто вызывает нашу функцию termatingLength, теперь должен доказать, что его список конечен, иначе его код не скомпилируется. Это не устранило проблему остановки, но мы перенесли ее на время компиляции, а не на время выполнения. Компилятор может зависнуть при попытке определить принадлежность к Finite, но это лучше, чем зависание рабочей программы, когда она получает какие-то неожиданные данные.
Небольшое предостережение: «специальный» полиморфизм Haskell позволяет объявлять в значительной степени произвольные экземпляры Finite в других точках кода, и terminatingLength примет их как доказательства конечности, даже если это не так. Это не так уж плохо; если кто-то пытается обойти механизмы безопасности вашего кода, он получает заслуженные ошибки ;)
Нет - вы можете в лучшем случае оценить. См. Проблему остановки.
isFinite
, которая работает с произвольными списками, обязательно является останавливающимся оракулом.
- person C. A. McCann; 10.09.2011
Существует также возможность разделения конечных и бесконечных списков по дизайну и использования для них разных типов.
К сожалению, Haskell (в отличие, например, от Agda) не позволяет структура данных всегда конечна, вы можете использовать методы полного функционального программирования, чтобы гарантировать это.
И вы можете объявить бесконечные списки (потоки AKA) как
data Stream a = Stream a (Stream a)
у которого нет способа завершить последовательность (в основном это список без []
).
isInfinite [1,2,3]
дает False
, а isInfinite [1..]
также дает False
.
- person alexraasch; 11.09.2011
seq
там гарантирует, что он вычисляет длину, что, конечно, никогда не закончится, если список бесконечен. Таким образом, он либо дает False
, если список конечен (что правильно), либо никогда не завершается и, следовательно, вообще никогда не отвечает. А ответ, которого не существует, нельзя назвать неправильным, не так ли? :] И да, это шутка, если это не очевидно.
- person C. A. McCann; 12.09.2011