@ sacundim відповідь здебільшого правдива, але є й деякі інші важливі уявлення про торг щодо мовних конструкцій та практичних вимог.
Об'єкти та посилання
Ці мови, як правило, призначають (або припускають) об'єкти, що мають незв'язані динамічні розширення (або, кажучи мовами , термін служби , хоча не зовсім однакові через різницю значень об'єктів серед цих мов, див. Нижче) за замовчуванням, уникаючи посилань на першокласні ( наприклад, вказівники на об'єкти в C) і непередбачувана поведінка в семантичних правилах (наприклад, невизначена поведінка ISO C, пов'язана з семантикою).
Далі, поняття (першокласного) об'єктів у таких мовах є консервативно обмежувальним: жодні "локативні" властивості не визначені та гарантовані за замовчуванням. Це зовсім інше в деяких мовах, схожих на ALGOL, об'єкти яких не мають незв'язаних динамічних розширень (наприклад, на C і C ++), де об'єкти в основному означають певний тип "типового сховища", як правило, поєднаного з місцями пам'яті.
Кодування пам’яті в об'єктах має додаткові переваги, такі як можливість приєднувати детерміновані обчислювальні ефекти протягом усього їхнього життя, але це інша тема.
Проблеми моделювання структур даних
Без першокласних посилань однозначно пов'язані списки не можуть імітувати багато традиційних (нетерплячих / змінних) структур даних ефективно та портативно, через характер представлення цих структур даних та обмеженість примітивних операцій на цих мовах. (Навпаки, в C ви можете отримати зв'язані списки досить легко навіть у строго відповідній програмі .) І такі альтернативні структури даних, як масиви / вектори, мають деякі переважні властивості порівняно зі списками, що зв'язані одночасно. Ось чому R 5 RS вводить нові примітивні операції.
Але існують типи векторів / масивів відмінностей порівняно з подвійно пов'язаними списками. Масив часто передбачається з O (1) складністю часу доступу та меншою накладними витратами, які є чудовими властивостями, які не поділяються списками. (Хоча строго кажучи, це не гарантується ISO C, але користувачі майже завжди цього очікують, і жодна практична реалізація не порушує ці неявні гарантії занадто очевидно.) OTOH, подвійно пов'язаний список часто робить обидва властивості навіть гіршими, ніж окремо пов'язаний список , тоді як ітерація назад / вперед також підтримується масивом або вектором (разом з цілими індексами) з ще меншими накладними витратами. Таким чином, подвійно пов'язаний список не працює в цілому краще. Ще гірше, продуктивність щодо ефективності кеш-пам'яті та затримки в динамічному розподілі пам'яті списків катастрофічно гірша, ніж продуктивність для масивів / векторів при використанні алокатора за замовчуванням, що надається базовим середовищем реалізації (наприклад, libc). Тому без дуже специфічного та «розумного» часу виконання, що сильно оптимізує такі створення об’єктів, масиви / векторні типи часто віддають перевагу зв'язаним спискам. (Наприклад, за допомогою ISO C ++, є застереження, щоstd::vector
слід віддати перевагу std::list
за замовчуванням.) Таким чином, введення нових примітивів для спеціально підтримуваних (подвійно) пов'язаних списків, безумовно, не настільки вигідно, як підтримувати масиви / векторні структури даних на практиці.
Справедливості, списки все ще мають деякі конкретні властивості краще, ніж масиви / вектори:
- Списки засновані на вузлах. Видалення елементів зі списків не приводить до недійсного посилання на інші елементи в інших вузлах. (Це також справедливо для деяких структур даних про дерево або графік.) OTOH, масиви / вектори можуть посилатися на визнання недійсним позиції (з великим перерозподілом в деяких випадках).
- Списки можуть зрощуватися в O (1) час. Реконструкція нових масивів / векторів з поточними - набагато дорожче.
Однак ці властивості не надто важливі для мови із вбудованою підтримкою односхилених списків, яка вже здатна до такого використання. Незважаючи на те, що все ще існують відмінності, у мовах із закріпленими динамічними розширеннями об'єктів (що зазвичай означає, що там є сміттєзбірник, який тримає подалі звисаючі посилання), недійсність може бути також менш важливою, залежно від намірів. Отже, єдиними випадками, коли виграють подвійні списки, можуть бути:
- Потрібні як гарантія нерозподілу, так і вимоги до двонаправленої ітерації. (Якщо продуктивність доступу до елементів важлива і набір даних досить великий, я б вибрав двійкові дерева пошуку або хеш-таблиці.)
- Потрібні ефективні операції двостороннього сплайсингу. Це значно рідко. (Я відповідаю лише вимогам лише щодо впровадження в браузер щось подібне до лінійних записів історії.)
Незмінюваність та згладжування
Чистою мовою, як Haskell, об'єкти незмінні. Об'єкт схеми часто використовується без мутації. Такий факт дозволяє ефективно підвищити ефективність пам’яті за допомогою інтернування об’єктів - неявного спільного використання декількох об’єктів з однаковим значенням на льоту.
Це агресивна стратегія оптимізації високого рівня в мовному дизайні. Однак це пов'язано з проблемами впровадження. Він фактично вводить неявні псевдоніми в основні комірки зберігання. Це ускладнює аналіз збудження. В результаті, можливо, буде менше можливостей усунути накладні витрати не першокласних посилань, навіть користувачі їх взагалі ніколи не торкаються. У таких мовах, як схема, коли мутація не виключається повністю, це також заважає паралелізму. Це може бути гаразд у ледачій мові (яка вже має проблеми з роботою, спричинені громом), хоча.
Для програмування загального призначення такий вибір дизайну мови може бути проблематичним. Але з деякими загальними функціональними моделями кодування мови, здається, все ще працюють добре.