Чому списки мінусів пов'язані з функціональним програмуванням?


22

Я помітив, що більшість функціональних мов використовують єдиний зв'язаний список (список "проти") як їх найбільш фундаментальний тип списку. Приклади включають звичайні Lisp, Haskell і F #. Це відрізняється від основних мов, де типом власного списку є масиви.

Чому так?

Для Common Lisp (динамічно набраний) я розумію, що мінуси є загальними, щоб бути також базою списків, дерев тощо. Це може бути крихітною причиною.

Для мов, що мають статичний тип, я не можу знайти належних міркувань, я навіть можу знайти аргументи:

  • Функціональний стиль заохочує незмінність, тому простота вставки пов'язаного списку є меншою перевагою,
  • Функціональний стиль заохочує незмінність, а також обмін даними; масив легше ділитися "частково", ніж пов'язаний список,
  • Ви також можете зробити відповідність шаблонів у звичайному масиві так само добре, а ще краще (ви можете легко скласти справа наліво, наприклад),
  • Крім того, ви отримуєте випадковий доступ безкоштовно,
  • І (практична перевага), якщо мова введена статично, ви можете використовувати звичайний макет пам'яті та отримувати збільшення швидкості з кешу.

То чому б віддавати перевагу зв'язаним спискам?


4
Виходячи з коментарів до відповіді @ sepp2k, я думаю, що an array is easier to share "partially" than a linked listпотрібно уточнити, що ви маєте на увазі. Зважаючи на їх рекурсивну природу, навпаки, наскільки я розумію, ви можете частково поділитися пов'язаним списком, пройшовши уздовж будь-якого вузла в ньому, тоді як масиву потрібно буде витратити час на створення нової копії. Або з точки зору обміну даними, два пов'язані списки можуть вказувати на один і той же суфікс, що просто просто неможливо з масивами.
Ізката

Якщо масив визначає себе як зсув, довжину, буфер потрійний, то ви можете поділити масив, зробивши новий буфер зміщення + 1, довжина-1, буфер. Або мати спеціальний тип масиву як підмагістраль.
Dobes Vandermeer

@Izkata Коли ми говоримо про масиви, ми рідко просто маємо на увазі буфер, такий як вказівник на початок суміжної пам'яті в C. Ми зазвичай маємо на увазі якусь структуру, яка зберігає довжину та вказівник на початок загорнутого буфера. За такої системи операція нарізки може повернути підрив, чий вказівник буфера вказує посередині в буфер (на першому елементі підсистеми масиву), а кількість яких таке, що старт + count дає тобі останній елемент. Такі операції нарізки - O (1) у часі та просторі
Олександр - Відновити Моніку

Відповіді:


22

Найважливішим фактором є те, що ви можете додати до непорушного однозв’язаного списку за O (1) час, що дозволяє рекурсивно створювати списки n-елементів за O (n) час таким чином:

// Build a list containing the numbers 1 to n:
foo(0) = []
foo(n) = cons(n, foo(n-1))

Якщо ви зробили це за допомогою незмінних масивів, час виконання буде квадратичним, оскільки для кожної consоперації потрібно буде копіювати весь масив, ведучи до квадратичного часу виконання.

Функціональний стиль заохочує незмінність, а також обмін даними; масив легше ділитися "частково", ніж пов'язаний список

Я припускаю, що "частково" поділившись, ви маєте на увазі, що ви можете взяти під масив з масиву в O (1) час, тоді як при пов'язаних списках ви можете взяти хвіст лише в O (1) час, а все інше потребує O (n). Це правда.

Однак у багатьох випадках брати хвіст досить. І ви повинні взяти до уваги, що можливість дешевого створення масивів не допоможе вам, якщо у вас немає можливості дешево створити масиви. І (без розумних оптимізацій компілятора) немає способу дешево наростити масив поетапно.


Це зовсім не так. Ви можете додавати масиви в амортизованому O (1).
DeadMG

10
@DeadMG Так, але не незмінні масивів.
sepp2k

"частково ділитися" - я вважаю, що два списки мінусів можуть вказувати на один і той же список суфіксів (не знаю, чому ви цього хочете), і що ви можете передавати середину замість початку списку іншій функції без копіювання. це (я робив це багато разів)
Ізката

@Izkata ОП говорив про часткове обмінними масивами, а не списками. Крім того, я ніколи не чув того, що ви описуєте, називається частковим спільним доступом. Це просто обмін.
sepp2k

1
@Izkata ОП використовує термін "масив" рівно три рази. Колись скажу, що мови FP використовують зв'язані списки, де інші мови використовують масиви. Одного разу сказати, що масиви краще в частковому обміні, ніж пов'язані списки, і один раз сказати, що масиви можуть бути точно узгоджені (як пов'язані списки). У всіх випадках він протиставляє масиви та пов'язані списки (щоб зробити висновок, що масиви були б кориснішими як основна структура даних, ніж зв'язані списки, що призводить до його запитання, чому пов'язані списки є кращими у FP), тому я не бачу, як він можна використовувати терміни взаємозамінно.
sepp2k

4

Я думаю, що це зводиться до списків, які досить легко реалізуються у функціональному коді.

Схема:

(define (cons x y)(lambda (m) (m x y)))

Haskell:

data  [a]  =  [] | a : [a]

Масиви складніші і майже не такі гарні в реалізації. Якщо ви хочете, щоб вони були надзвичайно швидкими, вони повинні бути написані на низькому рівні.

Крім того, рекурсія працює набагато краще в списках, ніж масиви. Поміркуйте, скільки разів ви рекурсивно споживали / генерували список проти масиву, який індексується.


Я б не сказав, що можна назвати версію вашої схеми реалізацією зв'язаних списків. Ви не зможете використовувати його для зберігання нічого, крім функцій. Також масиви важче (неможливо насправді) реалізувати будь-якою мовою, яка не має вбудованої підтримки (або шматки пам’яті), тоді як зв'язані списки вимагають лише чогось на зразок структур, класів, записів або алгебраїчних типів даних. Це не характерно для функціональних мов програмування.
sepp2k

@ sepp2k Що ви маєте на увазі "зберігати нічого, крім функцій" ?
Паббі

1
Я мав на увазі те, що визначені таким чином списки не можуть зберігати нічого, що не є функцією. Це насправді не так. Данно, чому я так подумав. Вибач за це.
sepp2k

2

Спільно пов'язаний список - це найпростіша стійка структура даних .

Стійкі структури даних мають важливе значення для ефективного, чисто функціонального програмування.


1
це здається лише повторенням, зробленим і поясненим у верхній відповіді , опублікованій більше 4 років тому
gnat

3
@gnat: У верхній відповіді не згадуються стійкі структури даних, або що окремо пов'язані списки є найпростішою стійкою структурою даних, або що вони важливі для виконання чисто функціонального програмування. Я взагалі не можу знайти перекриття з верхньою відповіддю.
Майкл Шоу

2

Ви можете легко використовувати вузли «Мінуси», лише якщо у вас є мова, зібрана зі сміттям.

Мінуси Вузли багато в чому відповідають функціональному стилю програмування рекурсивних викликів та незмінних значень. Так воно добре вписується в модель розумового програміста.

І не забувайте історичних причин. Чому їх досі називають мінусовими вузлами, а ще гірше все-таки використовують автомобіль та компакт-диск як аксесуари? Люди вивчають це з підручників та курсів, а потім користуються ним.

Ви маєте рацію, в реальному світі масиви набагато простіші у використанні, споживають лише половину простору пам’яті та значно продуктивніші через пропуски рівня кешу. Немає підстав використовувати їх із імперативними мовами.


1

Пов'язані списки важливі з наступної причини:

Після того, як ви візьмете число, як 3, і перетворите його в послідовність наступника типу succ(succ(succ(zero))), а потім використовуйте підстановку на нього {succ=List node with some memory space}, і{zero = end of list} ви отримаєте зв'язаний список (довжиною 3).

Фактична важлива частина - це цифри, підміна та простір пам'яті та нуль.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.