Чи є певна мета гетерогенних списків?


13

Виходячи з фону C # та Java, я звик, щоб мої списки були однорідними, і це має для мене сенс. Коли я почав збирати Лісп, я помітив, що списки можуть бути неоднорідними. Коли я почав накручувати dynamicключове слово в C #, я помітив, що, як і для C # 4.0, також можуть бути неоднорідні списки:

List<dynamic> heterogeneousList

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


1
Ви мали на увазі ... Я помітив, що списки можуть бути неоднорідними ...?
Світовий інженер

Чим List<dynamic>відрізняється (на ваше запитання) від простого здійснення List<object>?
Петро К.

@WorldEngineer так, я. Я оновив свою публікацію. Спасибі!
Jetti

@PeterK. Я думаю, що для щоденного використання різниці немає. Однак, не кожен тип C # походить від System.Object, тому не було б реальних випадків, коли є відмінності.
Jetti

Відповіді:


16

У статті сильно типізовані гетерогенні колекції Олега Кисельова, Ральфа Леммеля та Кіана Шупке є не лише реалізація гетерогенних списків у Хаскеллі, але й мотивуючий приклад того, коли, чому і як ви використовуєте HLists. Зокрема, вони використовують його для безпечного доступу до бази даних, що перевіряється під час компіляції. (Подумайте, LINQ, насправді, документ, на який вони посилаються, - це документ Haskell Еріка Мейєра та ін., Який призвів до LINQ.)

Цитуючи з вступного пункту статті HLists:

Ось відкритий список типових прикладів, які вимагають неоднорідних колекцій:

  • Таблиця символів, яка повинна зберігати записи різних типів, є неоднорідною. Це кінцева карта, де тип результату залежить від значення аргументу.
  • Елемент XML гетерогенно набирається. Насправді, елементи XML - це вкладені колекції, які обмежені регулярними виразами та властивістю двозначності.
  • Кожен рядок, повернутий за допомогою SQL-запиту, є неоднорідною картою від імен стовпців до комірок. Результатом запиту є однорідний потік різнорідних рядків.
  • Додавання вдосконаленої об'єктної системи до функціональної мови вимагає різнорідних наборів, які поєднують розширювані записи з підтипом та інтерфейсом перерахування.

Зауважте, що приклади, які ви подали у своєму запитанні, насправді не є гетерогенними списками в тому сенсі, що це слово зазвичай використовується. Вони є слабо типізованими або нетипізованими списками. Насправді вони є насправді однорідними списками, оскільки всі елементи одного типу: objectабо dynamic. Тоді ви змушені виконувати кастинг чи неперевірені instanceofтести чи щось подібне, щоб насправді вміти грамотно працювати з елементами, що робить їх слабо набраними.


Дякуємо за посилання та вашу відповідь. Ви добре зазначаєте, що списки справді не є неоднорідними, але слабо набрані. Я з нетерпінням чекаю прочитати цей документ (напевно, завтра, сьогодні я отримав середину сьогодні :))
Jetti

5

Короткі, неоднорідні контейнери пропонують ефективність виконання для забезпечення гнучкості. Якщо ви хочете скласти "список речей", не зважаючи на конкретний тип речі, неоднорідність - це шлях. Липи характерно динамічно набираються, і більшість із них все-таки є списком значень у коробці, так що очікується невеликий хіт продуктивності. У світі Lisp продуктивність програміста вважається важливішою, ніж продуктивність виконання.

У динамічно набраній мові однорідні контейнери фактично матимуть невеликі накладні витрати порівняно з гетерогенними, тому що всі додані елементи потрібно перевірити.

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


1
"Однак іноді ви дійсно хочете гетерогенного контейнера, і вам слід дозволити його мати, якщо він вам потрібен." - Чому хоч? Це моє запитання. Навіщо вам коли-небудь потрібно просто розміщувати купу даних у випадковий список?
Jetti

@Jetti: Скажімо, у вас є список введених користувачем параметрів різних типів. Ви можете зробити інтерфейс IUserSettingі реалізувати його кілька разів або зробити загальний UserSetting<T>, але одна з проблем статичного набору тексту полягає в тому, що ви визначаєте інтерфейс, перш ніж точно знати, як він буде використовуватися. Те, що ви робите з цілими налаштуваннями, ймовірно, сильно відрізняється від речей, які ви робите з налаштуваннями рядків, тож які операції має сенс розміщувати в загальному інтерфейсі? Поки ви не знаєте напевно, краще розумно використовувати динамічне введення тексту та зробити його конкретним пізніше.
Джон Перді

Дивіться, там я стикаюся з проблемами. Мені це здається поганим дизайном, коли ви робите щось, перш ніж дізнаєтесь, що це робитиме / використовуватиметься. Також у цьому випадку ви можете зробити Інтерфейс із поверненим значенням об'єкта. Це те ж саме, що і гетерогенний список, але його більш чітко та простіше виправити, коли ви точно знаєте, які типи використовуються в інтерфейсі.
Jetti

@Jetti: Це, по суті, та сама проблема - універсальний базовий клас не повинен існувати в першу чергу, тому що, незалежно від того, які операції він визначає, буде тип, для якого ці операції не мають сенсу. Але якщо C # полегшує використання, objectа не a dynamic, тоді обов'язково використовуйте перший.
Джон Перді

1
@Jetti: Саме про це йде поліморфізм. Список містить ряд "гетерогенних" об'єктів, хоча вони можуть бути підкласами одного суперкласу. З точки зору Java, ви можете правильно визначити визначення класу (або інтерфейсу). Для інших мов (LISP, Python тощо) корисність для отримання всіх декларацій не має ніякої користі, оскільки практичної різниці в застосуванні немає.
S.Lott

2

У функціональних мовах (наприклад, lisp) ви використовуєте відповідність шаблону, щоб визначити, що відбувається з певним елементом у списку. Еквівалент у C # буде ланцюжком, якщо ... elseif висловлювання, які перевіряють тип елемента і виконують на основі цього операцію. Потрібно сказати, що функціональна відповідність шаблону є більш ефективною, ніж перевірка типу виконання.

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

public class ListVisitor
{
  public void DoSomething(IEnumerable<dynamic> list)
  {
    foreach(dynamic obj in list)
    {
       DoSomething(obj);
    }
  }

  public void DoSomething(SomeClass obj)
  {
    //do something with SomeClass
  }

  public void DoSomething(AnotherClass obj)
  {
    //do something with AnotherClass
  }

  public void DoSomething(Object obj)
  {
    //do something with everything els
  }
}

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

Зворотна сторона сповіщає всіх, хто зареєструється для повідомлення (наприклад, шаблон агрегатора подій, який зазвичай використовується для вільного з'єднання ViewModels у шаблоні MVVM). Я використовую наступну конструкцію

IDictionary<Type, List<Object>>

Єдиний спосіб додати до словника - це функція

Register<T>(Action<T> handler)

(а об'єкт насправді є слабким посиланням на передане обробник). Тому тут я маю використовувати List <Object>, оскільки під час компіляції я не знаю, що буде закритим типом. Однак під час виконання програми я можу стверджувати, що саме цей тип є ключовим для словника. Коли я хочу запустити подію, яку я дзвоню

Send<T>(T message)

і знову вирішую список. Немає переваги у використанні List <dynamic>, тому що мені все одно потрібно його передати. Отже, як ви бачите, є достоїнства обох підходів. Якщо ви збираєтесь динамічно відправляти об'єкт, використовуючи метод перевантаження, динамічний спосіб це зробити. Якщо ви змушені брати участь незалежно, ви можете також використовувати Object.


При відповідності шаблонів випадки (як правило - принаймні в ML та Haskell) встановлюються в камені за допомогою оголошення відповідного типу даних. Списки, що містять такі типи, теж не є гетерогенними.

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

@MikeBrown - Це добре, але не охоплює ЧОМУ можна використовувати неоднорідні списки і не завжди працюватиме зі списком <динамічним>
Jetti

4
У C # перевантаження вирішуються під час компіляції . Таким чином, ваш приклад код завжди буде викликати DoSomething(Object)(принаймні, при використанні objectв foreachциклі; dynamicце зовсім інша річ).
Хайнці

@Heinzi, ти маєш рацію ... Я сьогодні сонний: P виправлено
Майкл Браун

0

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

З мого досвіду, працюючи з необробленими байтами через файли, мережеві сокети тощо, ви часто стикаєтеся з такими проблемами.

Щоб навести реальний приклад, розглянемо систему розподілених обчислень з використанням ф'ючерсів . Працівник на окремому вузлі може породити роботу будь-якого серіалізуючого типу, даючи майбутнє цього типу. За лаштунками система відправляє роботу одноліткові, а потім зберігає запис, що асоціює цю одиницю роботи з конкретним майбутнім, яке потрібно заповнити, як тільки відповідь на цю роботу повернеться.

Де можна зберігати ці записи? Інтуїтивно зрозуміло, що ви хочете - це щось на зразок Dictionary<WorkId, Future<TValue>>, але це обмежує вас лише одним типом ф'ючерсів у всій системі. Більш підходящий тип Dictionary<WorkId, Future<dynamic>>, оскільки працівник може прикинути його до відповідного типу, коли це змушує майбутнє.

Примітка . Цей приклад походить із світу Haskell, де у нас немає підтипів. Я не був би здивований, якщо для цього конкретного прикладу в C # є більш ідіоматичне рішення, але, сподіваємось, це все-таки показово.


0

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

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