Почему `transpose [[]] == []` в Haskell?

Рассмотрим следующее?

ghci> transpose []
[]
ghci> transpose [[]]
[]
ghci> transpose [[[]]]
[[[]]]
ghci> transpose [[[[]]]]
[[[[]]]]
ghci> transpose [[[[[]]]]]
[[[[[]]]]]
ghci> transpose [[[[[[]]]]]]
[[[[[[]]]]]]

Одна из этих вещей не похожа на другую :p

Мне очень странно видеть что-то столь неизящное в base, но я полагаю, что есть какая-то превосходная алгебраическая или теоретическая причина, почему они решили использовать transpose [[]] == []. Может кто-нибудь пролить некоторый свет на это?

Это тот же результат, который дает sequenceA @[] @ZipList [ZipList []] по модулю новых типов.

Iceland_jack 24.05.2024 15:48
base полон частичных функций, которые гораздо менее элегантны, чем эта.
chepner 24.05.2024 19:03
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
4
2
152
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

transpose меняет местами строки и столбцы.

[] имеет 0 строк и не совсем четко определенное количество столбцов, которые функция воспринимает как 0. Замена 0 строк на 0 столбцов ничего не меняет.

[[[]]] и далее по 1 строке и 1 столбцу. Замена 1 строки на 1 столбец тоже ничего не меняет.

[[]] имеет 1 строку и 0 столбцов. Замена 1 строки и 0 столбцов дает результат с 0 строками: пустой список.

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

Тип

transpose :: [[a]] -> [[a]] 

заставляет transpose взять список списков a. Здесь a — произвольный тип, поэтому transpose не проверяет (и не может) проверять вводимые данные за пределами двух уровней списков.

Следовательно, опубликованные вами дела «видятся» transpose следующим образом:

ghci> transpose []
[]
ghci> transpose [[]]
[]
ghci> transpose [[something]]
[[something]]
ghci> transpose [[something]]
[[something]]
ghci> transpose [[something]]
[[something]]
ghci> transpose [[something]]
[[something]]

Теперь это легче понять. Как уже объяснил пользователь 2357112 в своем ответе, ввод [] равен 0*? матрица, [[]] — матрица 1*0, а [[something]] — матрица 1*1.

Мне нравится соображение, упомянутое в других ответах, когда я думаю о [[a]] как о матрице. Но я немного возражаю на том основании, что не все [[a]] на самом деле являются матрицами; довольно часто полезно иметь «неровные» вложенные списки, и transpose все еще может быть в них разумным и полезным. Поэтому я хотел бы привести некоторые аргументы, не предполагающие прямоугольности. У меня два.

Во-первых, предположим, что мы жили в альтернативной вселенной, где transpose [[]] = [[]]. Что же делать transpose [[], []]? На самом деле не существует хорошего способа отразить в выводе наличие двух пустых списков во входных данных. Итак, попробуем выбрать transpose [[], []] = [[]] еще раз. Но теперь у нас есть альтернативный сломанный шаблон:

> transpose [[], [], []]
[[]]
> transpose [[], []]
[[]]
> transpose [[]]
[[]]
> transpose []
[] -- whoops, not [[]] like the pattern would suggest

Если мы внесем все описанные выше изменения и изменим на transpose [] = [[]], чтобы этот шаблон работал, то ваш исходный шаблон снова сломается.

> transpose [[[[]]]]
[[[[]]]]
> transpose [[[]]]
[[[]]]
> transpose [[]]
[[]]
> transpose []
[[]] -- whoops, not [] like the pattern would suggest

Кажется, просто невозможно дать определение transpose, которое позволило бы реализовать все желаемые закономерности.

Во-вторых, текущее определение transpose действительно обладает некоторыми хорошими алгебраическими свойствами. Один из таких вот:

length' (transpose xs) = foldMap length' xs

Здесь length' аналогичен length, но возвращает беззнаковое число, экземпляр Monoid которого использует mappend = max; mempty = 0. Это свойство заставляет transpose [[]] = [], потому что

length' (transpose [[]]) = foldMap length' [[]]
                         = fold [length' []]
                         = fold [0]
                         = 0

и единственный способ получить length' foo = 0 — это foo = [].

(Этот второй аргумент очень и очень похож на аргумент, приведенный в других ответах, хотя он изложен совсем на другом языке!)

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