Почему хвосты функций Haskell включают весь список?

Введение и контекст

В 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]]

без пустого списка.

Есть ли формальная/логическая причина, по которой хвосты ведут себя таким образом, или это просто случайно, то есть просто так, как это работало в исходной реализации и никогда не менялось?

Я искал этот вопрос и не нашел этому достойного объяснения.

Он возвращает все суффиксы = «настоящие суффиксы» ≠ строгие суффиксы.

Ry- 02.06.2024 10:06

Что вы предлагаете вернуть за пустой список?

n. m. could be an AI 02.06.2024 11:27

Спорить можно было в любом случае. Почему бы не привести весь список? Легко вычислить «исключающий» вариант tails из «включающего» и наоборот, так что это не составляет большого труда. Тем не менее, любое множество является подмножеством самого себя, поэтому имеет смысл включить и в списки. Также приятно иметь возможность разделить список по всем пунктам как [ .. | (y:ys) <- tails xs ]. Кстати, вы бы тоже поспорили насчет исключения пустого хвоста?

chi 02.06.2024 11:32

@Chi Я считаю эту часть твоего комментария очень значимой: «любой набор является подмножеством самого себя»

Ettore Galli 02.06.2024 14:15

@n.m.couldbeanAI tails [] = [] кажется совершенно разумным выбором, если идти в этом направлении.

Daniel Wagner 03.06.2024 19:54

@DanielWagner, тогда у вас будет закон, согласно которому каждый список имеет пустой суффикс, за исключением пустого списка, который внезапно кажется неочевидным и произвольным.

n. m. could be an AI 03.06.2024 20:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
151
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Как сказано в документации 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 и tailsinits0 и tails0; и определите tails1 = drop 1 . tails0 (опуская полный суффикс) и inits1 = drop 1 . inits0 (опуская пустой префикс).

Вопрос в том, должно ли inits быть inits0 или inits1, и то же самое касается tails.

  1. По возможности нам следует отдавать предпочтение более общим функциям. Однако в этом случае каждое из них может быть выражено через другое, поэтому ни одно из них не является более общим.

  2. zipWith (++) (inits list) (tails list) должен изготовить копии оригинала list. Это означает либо (inits, tails) = (inits0, tails0), либо (inits, tails) = (inits1, tails1), но не смесь этих двух.

  3. inits всех tails должны создавать все смежные сегменты входного списка, например. (inits <=< tails) [1, 2, 3] = [[],[1],[1,2],[1,2,3],[],[2],[2,3],[],[3],[]] (включая [] один раз для каждого сегмента хвоста). Для (inits1, tails1) это не так.

  4. Кроме того, результаты как 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 03.06.2024 02:03

@Bergi: Да, эта часть не является целью, а просто примечанием к примеру. Что ж, это правда, что в списке есть только 1 + length list смещения, из которых можно взять срез нулевой длины, но я не уверен, когда это будет полезно.

Jon Purdy 03.06.2024 02:27

Я думаю, что естественным аналогом вашего drop 1 . tails (т. е. только строгие суффиксы, возможно, включая пустой суффикс) является не drop 1 . inits, а скорее reverse . drop 1 . reverse . inits (т. е. только строгие префиксы, возможно, включая пустой префикс) (или, ну, лучше определенное, но не фрагмент -длинная версия того, что не имеет reverse дважды, что-то вроде liftA2 (zipWith const) inits (drop 1 . inits)).

Daniel Wagner 03.06.2024 19:59

Другие вопросы по теме