Чому "блискавка" ігнорує звисаючий хвіст колекції?


12

C # , Scala, Haskell, Lisp і Python мають однакову zipповедінку: якщо одна колекція довша, хвіст мовчки ігнорується.

Це також може бути винятком, але я не чув жодної мови, використовуючи цей підхід.

Це мене спантеличує. Хтось знає причину, чому так zipстворено? Я думаю, що для нових мов це робиться, тому що інші мови роблять це так. Але в чому була першопричина?

Я задаю тут фактичне, засноване на історичному питанні питання, чи не комусь це подобається, чи це хороший чи поганий підхід.

Оновлення : Якби мене запитали, що робити, я б сказав - викиньте виняток, приблизно подібно до індексації масиву (незважаючи на "старі" мови робив усілякі чари, як обробляти індекс меж, UB, розширювати масив, тощо).


10
Якби він не ігнорував хвіст, який має один функтор, використання нескінченних послідовностей було б більш громіздким. Особливо, якщо отримати довжину нескінченного діапазону було дорого / згорнуто / неможливо.
Дедуплікатор

2
Ви, здається, думаєте, що це несподівано і дивно. Я вважаю це очевидним і, справді, неминучим. Що б ви хотіли статися, коли ви збираєте поштові колекції неоднакової довжини?
Кіліан Фот

@KilianFoth, киньте виняток.
greenoldman

@ Дедуплікатор, приємний. З мовчазним падінням хвоста ви, природно, можете висловити zipWithIndexгенератор природних чисел. Тепер, єдиний недолік інформації - що це було причиною? :-) (btw. Будь ласка, опублікуйте свій коментар як відповідь, дякую).
greenoldman

1
У Python є itertools.izip_lo most, який ефективно автозапускає готові входи з Nones. Я вибираю його на zip часто, коли я фактично використовую zip; Я не можу пригадати більше причин, що стоять за будь-яким вибором. Python вже перерахував () для справи @ greenoldman, якими я часто користуюся.
StarWeaver

Відповіді:


11

Це майже завжди те, що ви хочете, а коли це не так, ви можете зробити заливку самостійно.

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

Це також питання стилю. Імперативні програмісти звикли всюди вручну перевіряти граничні умови. Функціональні програмісти віддають перевагу конструкціям, які не можуть зірватися дизайном. Винятки надзвичайно рідкісні. Якщо функція може повернути розумний дефолт, функціональні програмісти приймуть її. Композиційність - король.


Я запитую про історичні причини, а не про те, що я можу зробити. Другий абзац - ви помиляєтесь, погляньте, як zipзараз реалізується. Виключення викидання - це просто зміна "стоп-урожай" на "кидок". Третій абзац - повернення порожнього елемента для виходу за межі не може зазнати невдачі, але все ж я сумніваюся, що будь-який FP розробник проголосував би це гарний дизайн.
greenoldman

3
Мій другий абзац не стосується всіх реалізацій, лише справді ледачих. Якщо ви zipдві безмежні послідовності разом, ви не знаєте розмір на початку. У третьому абзаці я сказав розумний дефолт. Повернення порожнім у цьому випадку не було б розумним, тоді як скинути хвіст, очевидно, є.
Карл Білефельдт

Ага, я бачу ваш висновок - якщо викинути виняток на ледачій мові, це не технічна заміна, це повністю зміна поведінки, тому що вам потрібно кинути виняток прямо на початку, тоді як ви можете ігнорувати хвіст, коли це зручно.
greenoldman

3
+1 це також чудова відповідь: "Функціональні програмісти віддають перевагу конструкціям, які не можуть зазнати невдач у дизайні". Це настільки красномовно стверджує, що є найбільшим мотиватором, що стоїть за більшістю дизайнерських рішень, які приймають функціональні програмісти. Імперативні програмісти мають правило, яке їм подобається: «Скажи, не питай», FP приймає це на N-й ступінь, зосереджуючись на тому, щоб дозволяти безперервно розповідати інструкції, не вимагаючи перевірки результатів до абсолютного останнього моменту, тому ми намагаємося забезпечити проміжні кроки не може провалитися, тому що композитивність - це король. Дуже добре сказано.
Джиммі Хоффа

12

Тому що немає очевидного способу завершити хвіст. Будь-який вибір того, як це зробити, призведе до не очевидного хвоста.

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

Якщо zip зробив це для вас, ви не могли б знати, які значення він заповнює інтуїтивно. Чи циклізував цей список? Чи повторювалося це порожнє значення? Що таке порожнє значення для вашого типу?

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


Також пам’ятайте, що ви маєте на увазі дуже специфічну добре відому функцію із конкретною добре відомою семантикою. Але це не означає, що ви не можете зробити подібну, але трохи іншу функцію . Тільки тому, що є загальна функція, яка це xробить, не означає, що ви не можете визначитися з вашою заданою метою, яку ви хочете зробити xі y.

Хоча пам’ятайте причину, що ця та багато інших загальних функцій стилю FP є загальними, це тому, що вони прості та узагальнені, щоб ви могли налаштувати свій код, щоб використовувати їх та отримувати потрібну поведінку. Наприклад, у C # ви могли просто

IEnumerable<Tuple<T, U>> ZipDefaults(IEnumerable<T> first, IEnumerable<U> second)
{
    return first.Count() < second.Count()
        ? first.Concat(Enumerable.Repeat(default(T), second.Count() - first.Count())).Zip(second)
        : first.Zip(second.Concat(Enumerable.Repeat(default(U), first.Count() - second.count())))
}

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


Гаразд, але це лише тоді, коли ти змушуєш колекції щось робити, щоб відповідати іншим - порівняй це з індексуванням колекції (масиву). Ви можете почати думати, чи варто мені розширювати та масувати, якщо у мене індекс поза межами? А може, мовчки проігноруйте прохання. Але деякий час існує загальне поняття кидання винятку. Те саме тут - якщо у вас немає збірної збірки, киньте виняток. Чому такого підходу не застосовували?
greenoldman

2
zipможе заповнити нулі, що часто є інтуїтивним рішенням. Розглянемо тип zip :: [a] -> [b] -> [(Maybe a, Maybe b)]. Звичайно, тип результату трохи ^ H ^ H є зовсім непрактичним, але це дозволить легко реалізувати будь-яку іншу поведінку (скорочення, виняток) поверх нього.
амон

1
@amon: Це зовсім не інтуїтивно, це нерозумно. Було б просто необхідна перевірка кожного аргументу.
DeadMG

4
@amon не кожен тип має нуль, це те, що я мав на увазі mempty, об’єкти мають null, щоб заповнити простір, але ви хочете, щоб він мав придумати таку річ для int та інших типів? Звичайно, C # має, default(T)але не всі мови, і навіть для C # це дійсно очевидна поведінка? Я не думаю так
Джиммі Хоффа

1
@amon Можливо, було б корисніше повернути незаповнену частину більш тривалого списку. Ви можете скористатися цим, щоб перевірити, чи були вони однакової довжини за фактом, якщо вам потрібно, і чи зможете повторно застебнути або зробити щось із непотребленим хвостом без повторного переходу списку.
Довал
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.