Чи зрозумілі невеликі обсяги функціонального програмування людям, які не є ПП? [зачинено]


43

Випадок : я працюю в компанії, пишу заявку в Python, яка обробляє безліч даних у масивах. Я є єдиним розробником цієї програми на даний момент, але вона, ймовірно, буде використана / модифікована / розширена в майбутньому (1-3 роки) деяким іншим програмістом, на даний момент невідомим мені. Я, мабуть, не буду там безпосередньо, щоб допомогти, але, можливо, надам деяку підтримку електронною поштою, якщо у мене буде час на це.

Отже, як розробник, який вивчив функціональне програмування (Haskell), я схильний вирішувати, наприклад, фільтрацію так:

filtered = filter(lambda item: included(item.time, dur), measures)

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

Питання : Чи добре сьогодні писати такий код?

  • Як розробник, який не написав / не вивчив FP, реагує на такий код?
  • Чи читається?
  • Модифікується?
  • Чи слід писати документацію, як пояснити дитині, що робить рядок?

     # Filter out the items from measures for which included(item.time, dur) != True

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

Яка ваша думка з цього приводу? Як програміст, який не є FP, як ви реагуєте на код? Чи є код "googable", щоб ви могли зрозуміти, що він робить? Я хотів би отримати відгук про це.


14
На мою думку, багато сучасних мов дозволяють певний ступінь функціонального програмування і використання цього стає все більш поширеним. Особисто я б сказав, що займіться цим (принаймні, щодо відносно простих завдань з фільтрації / картографування).
Йоахім Зауер

Я не можу дати тобі незахищену відповідь, тому що мені подобається FP. Було б непогано додати коментар, якщо це все-таки не просто.
Бруно Шеппер

3
Ваш коментар може бути чіткішим; Фільтр включає елементи, для яких тест є правдивим, щоб ваш коментар міг прочитати: # Select the item's from measures for which included(item.time, dur) == Trueуникнення подвійного негативу завжди покращує розуміння.
Martijn Pieters

6
Чи розглядали ви замість цього розуміння списку? Їх часто вважають більш "пітонічними", ніж карта / фільтр.
phant0m

2
@MatjazMuhic Ну, це прекрасно ...;)
kd35a

Відповіді:


50

Чи читається?

Для мене: Так, але я зрозумів, що спільнота Python часто, здається, вважає розуміння списку більш чистим рішенням, ніж використання map()/ filter().

Насправді GvR навіть розглядав можливість взагалі відмовитися від цих функцій.

Врахуйте це:

filtered = [item for item in measures if included(item.time, dur)]

Крім того, це має перевагу в тому, що розуміння списку завжди повертає список. map()а filter()з іншого боку поверне ітератор у Python 3.

Примітка. Якщо ви хочете мати ітератор замість нього, це так само просто, як замінити []на ():

filtered = (item for item in measures if included(item.time, dur))

Якщо чесно, я не бачу причин для використання map()або filter()в Python.

Чи можна змінити?

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

def is_included(item):
    return included(item.time, dur)
filtered = filter(is_included, measures)
filtered = [item for item in measures if is_included(item)]

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

Як розробник, який не написав / не вивчив FP, реагує на такий код?

Він шукає документацію на Python і знає, як вона працює через п'ять хвилин. Інакше він не повинен програмувати в Python.

map()і filter()є надзвичайно просто. Це не так, як ти просиш їх зрозуміти монадів. Тому я не думаю, що потрібно писати такі коментарі. Використовуйте хороші назви змінних та функцій, тоді код майже не пояснює себе. Ви не можете передбачити, які мовні особливості розробник не знає. Наступний розробник може не знати, що таке словник.

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


5
Можна також згадати генераторні вирази , які працюють так само, як розуміння списку, але повертають генератори замість списків, як mapі filterв python 3.
James

@James: Чудова ідея, я додав їх у свій пост. Дякую!
phant0m

2
Зазвичай я reduceпропоную як протилежний приклад до ви завжди можете використовувати аргумент розуміння списку , оскільки його порівняно важко замінити reduceгенератором вбудованого чи списку.
kojiro

4
+1 для відзначення кращої ідіоми відповідної мови. Послідовність IMHO має величезну цінність і використання мови таким чином, що значна частина "динаміків" може забезпечити більше ремонтопридатності, ніж навіть коментарі часом.
Джошуа Дрейк

4
+1 для "Він googles для документації на Python і знає, як це працює через п'ять хвилин. Інакше він не повинен програмувати в Python."
Bjarke Freund-Hansen

25

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

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

Код перед рефакторингом:

var categorizedProducts = new Dictionary<string, List<Product>>();

// Get only enabled products, filtering the disabled ones, and group them by categories.
foreach (var product in this.Data.Products)
{
    if (product.IsEnabled)
    {
        if (!categorizedProducts.ContainsKey(product.Category))
        {
            // The category is missing. Create one.
            categorizedProducts.Add(product.Category, new List<Product>());
        }

        categorizedProducts[product.Category].Add(product);
    }
}

// Walk through the categories.
foreach (var productsInCategory in categorizedProducts)
{
    var minimumPrice = double.MaxValue;
    var maximumPrice = double.MinValue;

    // Walk through the products in a category to search for the maximum and minimum prices.
    foreach (var product in productsInCategory.Value)
    {
        if (product.Price < minimumPrice)
        {
            minimumPrice = product.Price;
        }

        if (product.Price > maximumPrice)
        {
            maximumPrice = product.Price;
        }
    }

    yield return new PricesPerCategory(category: productsInCategory.Key, minimum: minimumPrice, maximum: maximumPrice);
}

Той самий код після рефакторингу за допомогою FP:

return this.Data.Products
    .Where(product => product.IsEnabled)
    .GroupBy(product => product.Category)
    .Select(productsInCategory => new PricesPerCategory(
              category: productsInCategory.Key, 
              minimum:  productsInCategory.Value.Min(product => product.Price), 
              maximum:  productsInCategory.Value.Max(product => product.Price))
    );

Це змушує мене думати, що:

  • Ви не повинні турбуватися, якщо знаєте, що наступний розробник, який підтримуватиме Ваш код, матиме достатній загальний досвід та деякі знання функціонального програмування, але:

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


8
+1, якщо FP не зробить ваш код більш читабельним для когось , ви робите це неправильно.
György Andrasek

7
Я можу бачити, як ізольовано хтось, хто раніше не бачив FP, міг би вважати перший фрагмент більш читабельним над другим. Але що робити, якщо ми помножимо це на 100? Читання 100 коротких стисненого майже на англійському , як пропозиції описує , що проти тисяч ліній водопроводу , як код. Чистий обсяг коду, як би не був знайомий, повинен бути настільки непосильним, що знайомство вже не є таким великим виграшем. У якийсь момент комусь буде легше спочатку розслабитися з елементами FP, а потім прочитати жменю рядків проти читання тисяч рядків знайомого коду.
Есаїлія

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

2
@MainMa Ваша думка цілком справедлива, я повинен був бути більш конкретним. Я маю на увазі те, що від розробника на будь-якій мові слід очікувати ефективного використання функцій цих мов. Наприклад, використання "функціонального стилю" в C # (використання фільтра / карти / зменшення стилю програмування або передача / повернення функцій) не є новим, і тому я вважаю, що будь-які розробники C #, які не здатні читати такі заяви, повинні оновити свої навички ... напевно, дуже швидко. Я, звичайно, думаю, кожен розробник повинен навчитися трохи Haskell, але тільки з академічних причин. Це різна річ.
Sprague

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

20

Я програміст, який не є FP, і нещодавно мені довелося змінити код свого колеги в JavaScript. Був Http-запит із зворотним дзвоном, який дуже нагадував додану вами заяву. Треба сказати, що мені знадобилося певний час (як півгодини), щоб все це зрозуміти (код загалом був не дуже великий).

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

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

Крім того, як сказав Йоахім Зауер у своєму коментарі, часто є фрагменти ПП на багатьох мовах, як, наприклад, C # (indexOf, наприклад). Тож багато програмістів, що не належать до ПП, займаються цим досить часто, і фрагмент коду, який ви включили, не є чимось жахливим або незрозумілим.


1
Дякуємо за Ваш коментар! Це дало мені більше інформації про іншу перспективу. Стати незрячим удома, і тоді ви не знаєте, як це виглядало, коли ви не знали FP :)
kd35a

1
@ kd35a, ласкаво просимо. На щастя, я можу бути об’єктивним тут))
superM

1
+1 за вказівку, що зараз багато мов містять елементи ПС.
Джошуа Дрейк

LINQ - це основний приклад FP в C #
Конрад Моравський

3

Я б точно сказав, що так!

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

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

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

Я також часто уникаю мутуючих значень та змінних - інша концепція, запозичена у FP.

Обидві ці методи чудово працюють у Python та інших мовах, які зазвичай не класифікуються як функціональні. Їх часто навіть підтримує сама мова (тобто finalзмінні на Java). Таким чином, майбутні програмісти з технічного обслуговування не зіткнуться з глибоким бар'єром для розуміння коду.


2

У нас була така сама дискусія про компанію, в якій я працював минулого року.

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

Ми ніколи не прийшли до цього хорошого висновку, більше того, що люди в основному думають, що код, який виглядає незнайомим, є "магічним" або важко читати. Тож чи гарна ідея уникати коду, який здається незнайомим іншим користувачам? Я думаю, що це залежить від того, де він використовується. Я б утримався від використання виразів у стилі fp (динамічне включення файлів тощо) в основному методі (або важливих центральних частинах додатків), де дані повинні бути тунельними чітким та легким для читання, інтуїтивним способом. З іншого боку, я не думаю, що варто побоюватися натиснути на конверт, інші розробники, ймовірно, швидко вивчать FP, якщо вони зіткнуться з кодом FP ​​і, можливо, матимуть хороший власний ресурс для консультацій з питань.

TL; DR: уникайте центральної частини додатків високого рівня (які потрібно прочитати для огляду функціональності програми). В іншому випадку використовуйте його.


Хороший момент уникнути використання його у коді вищого рівня!
kd35a

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

2

Нещодавно спільнота C ++ отримала і лямбда, і я вважаю, що вони мають приблизно те саме питання. Однак відповідь може бути не однаковою. Еквівалент C ++ був би:

std::copy_if(measures.begin(), measures.end(), inserter(filter),
  [dur](Item i) { return included(i, dur) } );

Зараз std::copyне ново, і _ifваріанти теж не нові, але лямбда є. І все-таки це визначено досить чітко в контексті: durзахоплюється і, отже, є постійним, Item iзмінюється в циклі, і одне returnтвердження виконує всю роботу.

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


Цікавий момент, що відповіді можуть бути різними, залежно від того, на якій мові мова перебуває. Напевно, це стосується публікації @Christopher Käck про те, як у PHP-кодерів було більше проблем з подібними матеріалами, ніж у Python-кодерів.
kd35a

0

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

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

Чи міг ваш колега бути спритним і не зрозуміти чогось, що має бути очевидним? Так, але ви завжди повинні програмувати відповідно до KISS.

Можливо, ваш код є більш ефективним / гарним виглядом / елегантним, ніж більш прямий ідіотівський підхід? Тоді вам потрібно запитати себе: чи потрібно це робити? Знову ж таки, якщо відповідь - ні, тоді не робіть цього!

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


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