Як статичний набір тексту корисний для великих проектів?


9

Цікавившись на головній сторінці сайту мови програмування сценаріїв, я зіткнувся з цим уривком:

Коли система стає занадто великою, щоб тримати її в голові, ви можете додавати статичні типи.

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

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

Крім того, як задекларовано функції, без того type funcname()..., щоб знати, що typeвам доведеться шукати кожен рядок, у якому функція викликається, тому що ви знаєте лише funcname, тоді як у Python та тому подібному, який ви збираєтесь шукати def funcnameабо function funcnameякий відбувається лише один раз, на декларація.

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

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



2
якщо ви прочитаєте відповіді на інше питання, ви, ймовірно, отримаєте потрібні відповіді на це питання, вони в основному задають те саме з різних точок зору :)
sara

1
Свіфт та ігрові майданчики - це ПОВАДЖЕННЯ статично введеної мови.
daven11

2
Мови не компілюються, їх реалізація є. Спосіб написання відповіді на "компільовану" мову полягає в тому, щоб написати щось, що може інтерпретувати мову, або принаймні компілювати та виконувати його рядок за рядком, зберігаючи необхідний стан навколо. Також Java 9 поставляється з REPL.
Себастьян Редл

2
@ user6245072: Ось як зробити REPL для перекладача: прочитайте код, надішліть його перекладачеві, роздрукуйте результат. Ось як зробити REPL для компілятора: прочитати код, надіслати його компілятору, запустити скомпільований код , роздрукувати результат. Легкий, як пиріг. Саме так роблять FSi (F♯ REPL), GHCi (REH GHC Haskell), Scala REPL та Cling.
Йорг W Міттаг

Відповіді:


21

Більше того, з REPLs тривіально тестувати функцію на тип повернення з різними входами

Це не банально. Це не тривіальне взагалі . Для тривіальних функцій це лише тривіально.

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

getAnswer(v) {
 return v.answer
}

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

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

getAnswer(x, y) {
   if (x + y.answer == 13)
       return 1;
   return "1";
}

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

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

Статично набрані мови містять речі, які називаються "інструменти". Це програми, які допомагають робити справи зі своїм вихідним кодом. У цьому випадку я просто клацну правою кнопкою миші та перейду до визначення, завдяки Resharper. Або скористайтеся комбінацією клавіш. Або просто наведіть курсор миші, і він підкаже, які саме типи стосуються. Мене не хвилює жодне відношення до отримання файлів. Текстовий редактор сам по собі є пафосним інструментом для редагування вихідного коду програми.

З пам'яті def funcnameв Python було б недостатньо, оскільки функцію можна було б перевлаштувати довільно. Або може бути оголошено неодноразово в декількох модулях. Або на заняттях. І т.д.

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

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


2
Якщо чесно, ці "інструменти" були винайдені в динамічних мовах, і динамічні мови мали їх задовго до того, як статичні мови. Перейдіть до визначення, завершення коду, автоматизованого рефакторингу тощо. Існували у графічних IDE Lisp та Smalltalk до того, як статичні мови навіть мали графіку чи IDE, не кажучи вже про графічні IDE.
Йорг W Міттаг

Знаючи тип повернення функцій не завжди говорять вам , що функції DO . Замість того, щоб писати типи, ви могли б писати док-тести зі значеннями вибірки. наприклад, порівняти (слова 'деякі слова oue') => ['деякі', 'слова', 'oeu'] з (рядок слів) -> [рядок], (zip {abc} [1..3]) => [(a, 1), (b, 2), (c, 3)] з його типом.
aoeu256

18

Подумайте про проект із багатьма програмістами, який змінився за ці роки. Ви повинні це підтримувати. Є функція

getAnswer(v) {
 return v.answer
}

Що на землі це робить? Що v? Звідки answerпоходить елемент ?

getAnswer(v : AnswerBot) {
  return v.answer
}

Тепер у нас є ще трохи інформації -; йому потрібен тип AnswerBot.

Якщо ми переходимо до мови на основі класу, ми можемо сказати

class AnswerBot {
  var answer : String
  func getAnswer() -> String {
    return answer
  }
}

Тепер ми можемо мати змінну типу AnswerBotі викликати метод, getAnswerі всі знають, що він робить. Компілятор сприймає будь-які зміни до того, як буде проведено тестування виконання. Є багато інших прикладів, але, можливо, це дає вам ідею?


1
Виглядає вже зрозуміліше - якщо ви не зазначите, що така функція не має жодної причини існувати, але це, звичайно, лише приклад.
user6245072

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

1
Я думаю, що це нерозуміння функціонального програмування. Однак що ви маєте на увазі, кажучи, що вони знаходяться у глобальному просторі імен?
user6245072

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

2
@ daven11 "Я думаю, що тут JavaScript", але інші динамічні мови мають фактичні простори імен / модулів / пакунків і можуть попередити вас про перевизначення. Ви, можливо, трохи узагальнюєте.
coredump

10

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

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

Більшість людей, які працюють зі статично набраними мовами, використовують або IDE для мови, або інтелектуальний редактор (наприклад, vim чи emacs), який інтегрується з певними мовами інструментами. Зазвичай існує швидкий спосіб знайти тип функції в цих таких інструментах. Наприклад, для Eclipse для Java-проекту існує два способи, як правило, знайти тип методу:

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

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

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

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

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

Більше того, з REPLs тривіально тестувати функцію на тип повернення з різними входами

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

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

Швидше за все, навіть якщо вам це потрібно було зробити, вам не доведеться все перекомпілювати . У більшості сучасних статичних мов є інкрементальні компілятори, які збиратимуть лише невелику частину вашого коду, що змінилася, так що ви можете отримати майже миттєвий зворотній зв'язок щодо помилок типу, якщо ви зробите одну. Наприклад, Eclipse / Java підкреслить помилки типу, поки ви їх ще вводити .


4
You seem to have a few misconceptions about working with large static projects that may be clouding your judgement.Ну, мені лише 14 років, і я програмую лише менше, ніж на рік на Android, так що можливо я здогадуюсь.
user6245072

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

6
  1. Тому що статичні перевірки простіші для мов, що набираються статично.
    • Як мінімум, без динамічних особливостей мови, якщо вона компілюється, то під час виконання немає невирішених функцій. Це часто зустрічається в проектах ADA та C на мікроконтролерах. (Програми мікроконтролерів іноді набувають великого розміру ... як сотні великих клоків.)
  2. Статичні контрольні еталонні перевірки - це підмножина функціональних інваріантів, які статичною мовою можна перевірити також під час компіляції.
  3. Статичні мови зазвичай мають більш референтну прозорість. Результат полягає в тому, що новий розробник може зануритися в один файл і зрозуміти, що відбувається, і виправити помилку або додати невелику функцію, не знаючи всіх дивних речей у кодовій базі.

Порівняйте з скажімо, javascript, Ruby або Smalltalk, де розробники переробляють нову функціональну мову основної мови під час виконання. Це ускладнює розуміння великого проекту.

Більші проекти не просто мають більше людей, вони мають більше часу. Достатньо часу, щоб усі забули, або рухатися далі.

Анекдотично, моя знайома має захищене програму "Job For Life" у Ліспі. Ніхто, крім команди, не може зрозуміти кодову базу.


Anecdotally, an acquaintance of mine has a secure "Job For Life" programming in Lisp. Nobody except the team can understand the code-base.це справді так погано? Чи не допомагає персоналізація, яку вони додали, більш продуктивної?
user6245072

@ user6245072 Це може бути перевагою для людей, які зараз там працюють, але це ускладнює набір нових людей. Знадобиться більше часу, щоб знайти когось, хто вже знає непрофільну мову, або навчити їх такому, якого він уже не знає. Це може ускладнити масштабування проекту, коли він стає успішним, або відновлюється від коливань - люди все ж віддаляються, переходять на інші посади ... Через деякий час це може бути і недоліком для самих фахівців - після того, як ви лише десять років писали якусь ніч-мову, можливо, буде важко перейти до чогось нового.
Халк

Хіба ви не можете просто використовувати трекер для створення одиничних тестів із запущеної програми Lisp? Як і в Python, ви можете створити декоратор (прислівник), який називається print_args, який приймає функцію і повертає змінену функцію, яка виводить її аргумент. Потім ви можете застосувати його до всієї програми в sys.modules, хоча простіший спосіб зробити це - використовувати sys.set_trace.
aoeu256

@ aoeu256 Я не знайомий з можливостями середовища Lisp для виконання. Але вони активно використовували макроси, тому жоден звичайний програміст не міг прочитати код; Цілком ймовірно, що намагатися робити "прості" речі під час виконання не вдасться через те, що макроси змінюють усе про Lisp.
Тім Вілліскрофт

@TimWilliscroft Ви можете зачекати, поки всі макроси розгорнуться, перш ніж робити такі речі. У Emacs є багато клавіш швидкого доступу, які дозволяють розширювати макроси вбудованого (можливо, і вбудовані функції).
aoeu256

4

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

Справа не в тому, що ти забудеш тип повернення - це завжди станеться. Йдеться про те, що інструмент може повідомити вам, що ви забули тип повернення.

Окрім того, як функції оголошуються типом, без того funcname()..., щоб знати тип, вам доведеться шукати кожен рядок, у якому функція викликається, тому що ви знаєте лише ви funcname, тоді як у Python тощо, які ви могли просто шукати def funcnameабо function funcnameякі трапляються лише один раз , при декларації.

Це питання синтаксису, який абсолютно не пов'язаний зі статичним набором тексту.

Синтаксис сімейства C справді недружній, коли ви хочете шукати декларацію, не маючи в своєму розпорядженні спеціалізованих інструментів. Інші мови не мають цієї проблеми. Дивіться синтаксис декларації Руста:

fn funcname(a: i32) -> i32

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

Будь-яка мова може бути інтерпретована і будь-яка мова може мати відповідь.


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

Я відповім абстрактно.

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

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

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

Я б хотів класифікувати припущення на два види.

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

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

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

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

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

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


3

Ви пам’ятаєте старе прислів’я «сміття, сміття поза», ну ось що допомагає запобігти статичному введенню. Це не універсальна панацея, але суворість щодо того, які дані приймає та повертає дані, означає, що у вас є певна впевненість, що ви правильно з нею працюєте.

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

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


Вам не доведеться робити статичний тип виводу (pylint), ви можете робити динамічний висновок типу chrislaffra.blogspot.com/2016/12/…, що також робиться компілятором JIT PyPy. Існує також інша версія висновку про динамічний тип, коли комп'ютер випадковим чином розміщує макети об’єктів в аргументах і бачить, що викликає помилку. Проблема зупинки не має значення для 99% випадків, якщо ви забираєте занадто багато часу, просто зупиніть алгоритм (саме так Python обробляє нескінченну рекурсію, у нього є встановлена ​​межа рекурсії, яку можна встановити).
aoeu256
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.