В Haskell у нас есть функция tail
, которая предоставляет суффикс списка.
Например:
tail [1,2,3]
дает
[2, 3]
Вместо этого функция Tails выдаст все суффиксы:
tails [1,2,3]
дает
[[1,2,3],[2,3],[3],[]]
Почему он возвращает весь список в качестве первого аргумента? Я ожидал вернуть все настоящие суффиксы, т. е. ожидал
tails [1,2,3]
давать
[[2,3],[3],[]]
или, может быть, даже
[[2,3],[3]]
без пустого списка.
Есть ли формальная/логическая причина, по которой хвосты ведут себя таким образом, или это просто случайно, то есть просто так, как это работало в исходной реализации и никогда не менялось?
Я искал этот вопрос и не нашел этому достойного объяснения.
Что вы предлагаете вернуть за пустой список?
Спорить можно было в любом случае. Почему бы не привести весь список? Легко вычислить «исключающий» вариант tails
из «включающего» и наоборот, так что это не составляет большого труда. Тем не менее, любое множество является подмножеством самого себя, поэтому имеет смысл включить и в списки. Также приятно иметь возможность разделить список по всем пунктам как [ .. | (y:ys) <- tails xs ]
. Кстати, вы бы тоже поспорили насчет исключения пустого хвоста?
@Chi Я считаю эту часть твоего комментария очень значимой: «любой набор является подмножеством самого себя»
@n.m.couldbeanAI tails [] = []
кажется совершенно разумным выбором, если идти в этом направлении.
@DanielWagner, тогда у вас будет закон, согласно которому каждый список имеет пустой суффикс, за исключением пустого списка, который внезапно кажется неочевидным и произвольным.
Как сказано в документации Tails :: [a] -> [[a]] [Hackage]:
Функция Tails возвращает все последние сегменты аргумента, начиная с самого длинного.
итак, все суффиксы. Список всегда является суффиксом самого себя. tails
по существу реализуется как:
tails :: [a] -> [[a]]
tails [] = [[]]
tails xs@(_:xs') = xs : tails xs'
таким образом, он возвращает сам список и tails
его хвоста, если таковой имеется. Если вас интересуют только строгие хвосты, мы можем поработать с хвостом сказки:
tail . tails
который гарантированно будет работать, поскольку для пустого списка tails []
вернет одноэлементный список и, таким образом, (tail . tails) []
затем вернет пустой список.
inits
и tails
были добавлены в отчет Haskell 98 в разделе «Список» отчета библиотеки. У меня возникли проблемы с поиском хороших цитат, поскольку они, похоже, не упоминались в архивах списков рассылки. Однако я считаю, что было несколько оригинальных мотивов.
Я буду называть текущие версии inits
и tails
inits0
и tails0
; и определите tails1 = drop 1 . tails0
(опуская полный суффикс) и inits1 = drop 1 . inits0
(опуская пустой префикс).
Вопрос в том, должно ли inits
быть inits0
или inits1
, и то же самое касается tails
.
По возможности нам следует отдавать предпочтение более общим функциям. Однако в этом случае каждое из них может быть выражено через другое, поэтому ни одно из них не является более общим.
zipWith (++) (inits list) (tails list)
должен изготовить копии оригинала list
. Это означает либо (inits, tails) = (inits0, tails0)
, либо (inits, tails) = (inits1, tails1)
, но не смесь этих двух.
inits
всех tails
должны создавать все смежные сегменты входного списка, например. (inits <=< tails) [1, 2, 3]
= [[],[1],[1,2],[1,2,3],[],[2],[2,3],[],[3],[]]
(включая []
один раз для каждого сегмента хвоста). Для (inits1, tails1)
это не так.
Кроме того, результаты как inits
, так и tails
должны быть непустыми, чтобы поддерживать использование частичных функций, таких как maximum
, которые не определены в пустых списках, поскольку это было до того, как Data.List.NonEmpty
стало стандартом. Опять же, (inits1, tails1)
не удовлетворяет этому требованию, например. tails0 [] == [[]]
но tails1 [] == []
.
Итак, похоже, нам стоит выбирать (inits, tails) = (inits0, tails0)
.
Это также согласуется с другими функциями списка, например map sum . inits
= scanl' (+) 0
для вычисления префиксной суммы.
Именование немного неудачное, поскольку init
и tail
всегда отбрасывают один элемент, а inits
и tails
— нет. Возможно, takes
и drops
было бы лучше.
«включая []
один раз для каждого сегмента хвоста» — это кажется произвольным. Хотя мне нравятся другие аргументы!
@Bergi: Да, эта часть не является целью, а просто примечанием к примеру. Что ж, это правда, что в списке есть только 1 + length list
смещения, из которых можно взять срез нулевой длины, но я не уверен, когда это будет полезно.
Я думаю, что естественным аналогом вашего drop 1 . tails
(т. е. только строгие суффиксы, возможно, включая пустой суффикс) является не drop 1 . inits
, а скорее reverse . drop 1 . reverse . inits
(т. е. только строгие префиксы, возможно, включая пустой префикс) (или, ну, лучше определенное, но не фрагмент -длинная версия того, что не имеет reverse
дважды, что-то вроде liftA2 (zipWith const) inits (drop 1 . inits)
).
Он возвращает все суффиксы = «настоящие суффиксы» ≠ строгие суффиксы.