Применение length
к неизвестным спискам, как правило, является плохой идеей, как практически из-за бесконечных списков, так и концептуально, потому что часто оказывается, что вы все равно на самом деле не заботитесь о длине.
Вы сказали в комментарии:
Я очень новичок в Haskell, так что теперь, разве бесконечные структуры не делают мои программы очень уязвимыми?
Не совсем. Хотя некоторые из нас хотели бы иметь более эффективные способы различать обязательно конечные и обязательно бесконечные данные, вы всегда в безопасности, когда создаете , обрабатываете и исследуете ленивые структуры постепенно . Вычисление длины явно не является инкрементным, но проверяет, находится ли длина выше или ниже некоторого предельного значения , равного , и очень часто это все, что вы хотели сделать в любом случае!
Тривиальный случай - проверка непустых списков. 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, а также с большей вероятностью работать быстрее и использовать меньше памяти.