Чому динамічно набрані мови не дозволяють розробнику вказати тип?


14

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

Наприклад, JavaScript не забезпечує жодного механізму примусового застосування типів змінних, коли це зручно робити. PHP дозволяє вам вказати деякі типи аргументів методу, але немає способу використовувати нативні типи ( int, stringі т. Д.) Для аргументів, і немає способу застосувати типи для нічого, крім аргументів.

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

Чому існує таке обмеження? Це з технічних / продуктивних причин (я вважаю, що це стосується JavaScript), або лише з політичних причин (що, на мою думку, стосується PHP)? Це стосується інших мов, що динамічно набираються, з якими я не знайомий?


Редагувати: слідкуючи за відповідями та коментарями, ось приклад для уточнення: скажімо, у простому PHP у нас є такий метод:

public function CreateProduct($name, $description, $price, $quantity)
{
    // Check the arguments.
    if (!is_string($name)) throw new Exception('The name argument is expected to be a string.');
    if (!is_string($description)) throw new Exception('The description argument is expected to be a string.');
    if (!is_float($price) || is_double($price)) throw new Exception('The price argument is expected to be a float or a double.');
    if (!is_int($quantity)) throw new Exception('The quantity argument is expected to be an integer.');

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Доклавши певних зусиль, це можна переписати як (також див. Програмування за контрактами в PHP ):

public function CreateProduct($name, $description, $price, $quantity)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'name' => array('value' => $name, 'type' => VTYPE_STRING),
        'description' => array('value' => $description, 'type' => VTYPE_STRING),
        'price' => array('value' => $price, 'type' => VTYPE_FLOAT_OR_DOUBLE),
        'quantity' => array('value' => $quantity, 'type' => VTYPE_INT)
    ));

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Але той самий метод буде записаний так, якби PHP необов'язково приймав нативні типи аргументів:

public function CreateProduct(string $name, string $description, double $price, int $quantity)
{
    // Check the arguments.
    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Який коротший написати? Який легше читати?


1
Ви можете додатково вказати типи на деяких динамічно набраних мовах - наприклад, у Common Lisp.
SK-логіка

Досить кілька динамічно набраних мов використовують касти для примусового введення типу ...
Трезоїд

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

1
Clojure - це приклад мови, яка, як правило, динамічно набирається, але ви можете додатково надати типи змінних за допомогою "підказки типу" (це робиться, як правило, лише там, де потрібно, щоб отримати переваги щодо продуктивності інформації про тип компіляції)
mikera

1
Groovy - це ще один приклад динамічно набраної мови, яка дозволяє задавати тип.
Ерік Вілсон

Відповіді:


17

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

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

Щоб висловити інформацію про тип, вам потрібна частина мови, яка не може бути надмірно простою. Незабаром ви дізнаєтесь, що подібної інформації intнедостатньо; Ви хочете щось подібне List<Pair<Int, String>>, а потім параметричні типи тощо. Це може бути досить заплутано навіть у досить простому випадку Java.

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

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

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


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

1
@Ingo: безумовно. Але динамічні мови чудово підходять для майстерності та швидкого складання прототипів, де ви швидко висловлюєте відносно прості ідеї. Якщо ви хочете, щоб виробничий код був захищений від кулі, ви зможете згодом перейти до статичної мови, коли ви вилучили деякі стабільні основні компоненти.
9000

1
@ 9000, я не сумніваюся, що вони чудові. Хотів лише зазначити, що написання 3 або 4 хромових тестів не є і не може забезпечити тип saftey .
Інго

2
@ 9000, правда, і погана новина полягає в тому, що навіть тоді це практично неможливо. Навіть код Haskell або Agda покладається на припущення, як, наприклад, що бібліотека, яка використовується під час виконання, є правильною. Як було сказано, у проекті, де близько 1000 LOC поширюється на кілька десятків файлів вихідного коду, це так круто, коли ви можете щось змінити, і ви знаєте, що компілятор буде вказувати на кожен рядок, де зміни впливають.
Інго

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

8

У більшості динамічних мов можна принаймні динамічно перевірити тип об'єкта чи значення.

І є статичні умовиводи, шашки та / або виконавці для деяких динамічних мов: наприклад

І Perl 6 підтримуватиме систему додаткового типу зі статичним набором тексту.


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

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


2
Слід зазначити, що перевірка самих типів змушує людей у ​​більшості обставин. Використовуйте поліморфізм (конкретно, «типи качок») при роботі з об'єктами ієрахій, примушуйте до очікуваного типу, якщо це можливо / розумного. Залишається кілька випадків, коли просто не має сенсу дозволити будь-який тип, але в багатьох мовах ви все одно отримуєте виняток у більшості випадків, тому перевірка типу рідко корисна.

4
"люди, як правило, використовують динамічні мови, тому що вони динамічно вводяться" : JavaScript використовується, оскільки це єдина мова, яку підтримує більшість браузерів. PHP використовується тому, що він популярний.
Арсеній Муренко

2

Javascript планував включити деякий необов'язковий статичний набір тексту, і, схоже, багато зрілих динамічних мов прямують саме так -

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

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


Я не знаю про ці плани включати в JavaScript необов'язкове статичне введення тексту; але сподіваємось, вони виявилися не такими жорстокими, як у ActiveScript. найгірше як JavaScript, так і Java.
Хав'єр

Він планувався для JS 4 (або ECMAscript 4), але цю версію було скасовано через суперечки. Я впевнений, що щось подібне з’явиться в майбутньому якоюсь мовою. (У Python ви можете це зробити за допомогою декораторів, btw.)
Макке

1
Декоратори додають динамічну перевірку типу, свого роду твердження. Ви не можете отримати комплексну перевірку статичного типу в Python, як би ви не старалися, через надзвичайну динамічність мови.
9000

@ 9000: Це правильно. Однак я не вважаю, що динамічна перевірка типу погана (але я вважаю за краще порівняння типу качки ala JS4), особливо в поєднанні з тестовими одиницями, а класифікація декораторів може бути кориснішою підтримкою IDE / lint-checkers, якщо вони куди стандартизовані.
Макке

звичайно це непогано! Це не питання моралі. У якийсь момент тип потрібно так чи інакше "перевірити". Якщо ви запишете * ((подвійний *) 0x98765E) в C, процесор це зробить і перевірить, чи 0x98765E дійсно є вказівником на подвійний.
Інго

2

Об'єкти Python зробити мають тип.

Ви визначаєте тип під час створення об'єкта.

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

Власне, ручна перевірка типу Python майже завжди є марною тратою часу та коду.

Це просто погана практика писати код перевірки типу в Python.

Якщо якийсь злісний соціопат використовував невідповідний тип, звичайні методи Python піднімають звичайний виняток, коли тип виявляється невідповідним.

Ви не пишете жодного коду, програма все ще не працює з TypeError.

Є дуже рідкісні випадки, коли потрібно визначити тип під час виконання.

Чому існує таке обмеження?

Оскільки це не "обмеження", питання не є справжнім питанням.


2
"Об'єкти Python мають тип." - Ой справді? Подібно до об'єктів perl, PHP-об'єктів та будь-яких інших даних у світі. Різниця між статичним та динамічним введенням тексту полягає лише в тому випадку, коли тип перевіряється, тобто коли виявляються помилки типу. Якщо вони з'являються як помилки компілятора, це статичне введення тексту, якщо вони відображаються як помилки виконання, це динамічно.
Інго

@Ingo: Дякую за пояснення. Проблема полягає в тому, що об'єкти C ++ та Java можна передавати з одного типу на інший, роблячи тип об'єкта дещо мрячним, а тому роблячи "перевірку типу" у цих компіляторах також трохи мутною. Де перевірка типу Python - навіть якщо вона працює під час виконання - набагато менш каламутна. Крім того, питання наближається до того, щоб сказати, що динамічно набрані мови не мають типів. Хороша новина полягає в тому, що вона не робить такої поширеної помилки.
С.Лотт

1
Ви маєте рацію, вводите касти (на відміну від перетворень типів, тобто ((подвійний) 42)) підривайте статичне введення тексту. Вони потрібні, коли система типу недостатньо потужна. До Java 5 у Java не було парметеризованих типів, тоді ви не могли жити без типів. Сьогодні це набагато краще, але в типовій системі все ще бракує вищих родів типів, не кажучи вже про поліморфізм вищого рангу. Я думаю, що цілком можливо, що динамічно набрані мови користуються такою кількістю послідовників саме тому, що вони звільняють її від занадто вузьких систем.
Інго

2

Більшу частину часу вам не потрібно, принаймні, не на рівні деталізації, яку ви пропонуєте. У PHP оператори, якими ви користуєтесь, чітко дають зрозуміти, якими ви очікуєте аргументів; це трохи недогляд дизайну, хоча PHP передасть ваші значення, якщо це взагалі можливо, навіть коли ви передаєте масив операції, яка очікує рядок, і тому що передача не завжди має значення, іноді ви отримуєте дивні результати ( і це саме те, де перевірка типу є корисною). Крім того, це не має значення , якщо ви додасте цілі числа 1і 5чи рядки "1"і "5"- сам факт , що ви використовуєте+Оператор повідомляє PHP, що ви хочете трактувати аргументи як числа, і PHP буде підкорятися. Цікава ситуація, коли ви отримуєте результати запитів від MySQL: Багато числових значень просто повертаються у вигляді рядків, але ви цього не помітите, оскільки PHP кидає їх вам, коли ви трактуєте їх як числа.

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

І є ще одна причина, щоб зауважити: динамічні мови не мають етапу компіляції. Навіть якщо у вас є обмеження типу, вони можуть запускатись лише під час виконання просто тому, що немає часу на компіляцію . Якщо ваші перевірки все-таки призводять до помилок виконання, то набагато простіше моделювати їх відповідно: як явні перевірки (наприклад, is_XXX()в PHP або typeofjavascript), або викидаючи винятки (як це робить Python). Функціонально ви маєте той самий ефект (помилка сигналізується під час виконання, коли перевірка типу не вдається), але вона краще інтегрується з рештою семантики мови. Просто не має сенсу трактувати помилки типу, принципово відмінні від інших помилок виконання на динамічній мові.


0

Вас може зацікавити Haskell - це система типів виводить типи з коду, і ви також можете вказати типи.


5
Haskell - чудова мова. Це якимось протилежним динамічним мовам: ви витрачаєте багато часу на опис типів, і зазвичай, як тільки ви зрозуміли свої типи, програма працює :)
9000

@ 9000: Дійсно. Після компіляції це зазвичай працює. :)
Макке

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

0

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

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

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


0

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

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

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

Сподіваюся, це допоможе вам.


-1

Просто робити це немає сенсу.

Чому?

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


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

1
SK-логіка: Якщо динамічно набране означає, що кожна функція / метод / операції приймає об'єкти типу "Динамічний", "Будь-який" або будь-який інший і повертає "Динамічний", "Будь-який" будь-що, взагалі немає способу сказати, що певне значення завжди буде цілим числом, наприклад. Тому код виконання повинен так чи інакше перевіряти наявність нецілих чисел, як би тип "Динамічний" в першу чергу. Це саме те, що робить система статичного типу: це дає змогу довести, що певна зміна, поле чи метод повернення завжди будуть певного типу.
Інго

@Ingo, ні, є спосіб. Подивіться, як це реалізується, наприклад, у Common Lisp. Це особливо корисно для локальних змінних - ви можете значно підвищити продуктивність, ввівши всі ці підказки щодо введення тексту.
SK-логіка

@ SK-логіка: Можливо, ви можете сказати мені, коли і як вводити помилки в CL? У будь-якому випадку, @MainMa досить добре підсумував статус-кво у своєму питанні: Це просто те, чого можна було очікувати від "чисто" динамічних мов.
Інго

@Ingo, що змушує вас думати, що типи корисні лише для того, щоб статично довести правильність? Це не вірно навіть для таких мов, як C, де ви маєте неперевірений кастинг типу. Анотації типу в динамічних мовах здебільшого корисні як підказки компілятора, які покращують продуктивність або задають конкретне числове подання. Я погодився б, що в більшості випадків анотації не повинні змінювати семантику коду.
SK-логіка

-1

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

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