Лікнеп по типізації в мовах програмування

Лікнеп по типізації в мовах програмування

Коротка версія

Мови програмування по типізації прийнято ділити на два великих табори - типізовані і нетипізовані (Безтипові). До першого наприклад відносяться C, Python, Scala, PHP і Lua, а до другого - мова асемблера, Forth і Brainfuck.

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

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

Детальна версія

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

безтипових типізація

У безтипових мовах програмування - все суті вважаються просто послідовностями біт, різної довжини.

Безтипових типізація зазвичай властива низькорівневим (мова асемблера, Forth) і езотеричним (Brainfuck, HQ9, Piet) мов. Однак і у неї, поряд з недоліками, є деякі переваги.

переваги
  • Дозволяє писати на гранично низькому рівні, причому компілятор / інтерпретатор не заважатиме будь-якими перевірками типів. Ви вільні робити будь-які операції над будь-якими видами даних.
  • Одержуваний код зазвичай більш ефективний.
  • Прозорість інструкцій. При знанні мови зазвичай немає сумнівів, що з себе представляє той чи інший код.
недоліки
  • Складність. Часто виникає необхідність в поданні комплексних значень, таких як списки, рядки або структури. З цим можуть виникнути незручності.
  • Відсутність перевірок. Будь-які безглузді дії, наприклад віднімання покажчика на масив з символу будуть вважатися абсолютно нормальними, що загрожує так важко впіймати помилками.
  • Низький рівень абстракції. Робота з будь-яким складним типом даних нічим не відрізняється від роботи з числами, що звичайно буде створювати багато труднощів.
Сильна безтіповая типізація?

Так, таке існує. Наприклад в мові асемблера (для архітектури х86 / х86-64, інших не знаю) не можна ассембліровать програму, якщо ви спробуєте завантажити в регістр cx (16 біт) дані з регістра rax (64 біта).

mov cx, eax; помилка часу ассемблирования

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

Статична і динамічна типізації

Лікнеп по типізації в мовах програмування

Головне, що відрізняє статичну (static) типізацію від динамічної (dynamic) то, що всі перевірки типів виконуються на етапі компіляції, а не етапі виконання.

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

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

Лікнеп по типізації в мовах програмування

узагальнене програмування

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

Як же ми будемо її вирішувати? Вирішимо її на 3-ех різних мовах: одному з динамічною типізацією і двох із статичною.

Алгоритм пошуку я візьму один з найпростіших - перебір. Функція буде отримувати шуканий елемент, сам масив (або список) і повертати індекс елемента, або, якщо елемент не знайдений - (-1).

Динамічне рішення (Python):

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

Статична рішення (Сі):

Ну, кожна функція окремо схожа на версію з Python, але чому їх три? Невже статичну програмування програло?

І так і ні. Є кілька методик програмування, одну з яких ми зараз розглянемо. Вона називається узагальнене програмування і мова C ++ її непогано підтримує. Давайте подивимося на нову версію:

Статична рішення (узагальнене програмування, C ++):

Добре! Це виглядає не сильно складніше ніж версія на Python і при цьому не довелося багато писати. До того ж ми отримали реалізацію для всіх масивів, а не тільки для 3-ох, необхідних для вирішення задачі!

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

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

Статика в динаміці

Також потрібно згадати, що багато статичні мови дозволяють використовувати динамічну типізацію, наприклад:

  • C # підтримує псевдо-тип dynamic.
  • F # підтримує синтаксичний цукор у вигляді оператора. на базі чого може бути реалізована імітація динамічної типізації.
  • Haskell - динамічна типізація забезпечується модулем Data.Dynamic.
  • Delphi - за допомогою спеціального типу Variant.

Також, деякі динамічно типізовані мови дозволяють скористатися перевагами статичної типізації:

  • Common Lisp - декларації типів.
  • Perl - з версії 5.6, досить обмежено.

Отже, йдемо далі?

Сильна і слабка типізації

Лікнеп по типізації в мовах програмування

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

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

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

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

Мова при цьому повинен мати ще й сильну типізацію. І правда, якщо компілятор замість повідомлення про помилку буде просто додавати рядок до числа, або що ще гірше, відніме із одного масиву інший, який нам сенс, що все "перевірки" типів будуть на етапі компіляції? Правильно - слабка статична типізація ще гірше, ніж сильна динамічна! (Ну, це моя думка)

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

Хочете дізнатися які?

Переваги сильною типізації
  • Надійність - Ви отримаєте виключення або помилку компіляції, натомість неправильної поведінки.
  • Швидкість - замість прихованих перетворень, які можуть бути досить витратними, з сильною типізацією необхідно писати їх явно, що змушує програміста як мінімум знати, що ця ділянка коду може бути повільним.
  • Розуміння роботи програми - знову-таки, замість неявного приведення типів, програміст пише все сам, а значить приблизно розуміє, що порівняння рядка і числа відбувається не само-собою і не по-помахом чарівної палички.
  • Визначеність - коли ви пишете перетворення вручну ви точно знаєте, що ви перетворюєте і будь-що. Також ви завжди будете розуміти, що такі перетворення можуть привести до втрати точності і до невірних результатів.
Переваги слабкої типізації
  • Зручність використання змішаних виразів (наприклад з цілих і дійсних чисел).
  • Абстрагування від типізації та зосередження на завданні.
  • Стислість записи.

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

Виявляється є і навіть два.

Неявне приведення типів, в однозначних ситуаціях і без втрат даних

Ух ... Досить довгий пункт. Давайте я буду далі скорочувати його до "обмежене неявне перетворення" Так що ж означає однозначна ситуація і втрати даних?

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

Втрата даних це ще простіше. Якщо ми перетворимо дійсне число 3.5 в ціле - ми втратимо частину даних (насправді ця операція ще й неоднозначна - як буде проводитися округлення? У більшу сторону? В меншу? Відкидання дробової частини?).

Перетворення в неоднозначних ситуаціях і перетворення з втратою даних - це дуже, дуже погано. Нічого гіршого цього в програмуванні немає.

Якщо ви мені не вірите, вивчіть мову PL / I або навіть просто пошукайте його специфікацію. У ньому є правила перетворення між УСІМА типами даних! Це просто пекло!

Гаразд, давайте згадаємо про обмежене неявне перетворення. Чи є такі мови? Так, наприклад в Pascal Ви можете перетворити ціле число в реальне, але не навпаки. Також схожі механізми є в C #, Groovy і Common Lisp.

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

Я поясню його на прикладі чудового мови Haskell.

Поліморфні конструктори з'явилися в результаті спостереження, що найчастіше безпечні неявні перетворення потрібні при використанні числових літералів.

Наприклад в вираженні pi + 1 не хочеться писати pi + 1.0 або pi + float (1). Хочеться написати просто pi + 1.

І це зроблено в Haskell, завдяки тому, що у литерала 1 немає конкретного типу. Це ні ціле, ні речовий, ні комплексне. Це ж просто число!

В результаті при написанні простий функції sum x y. перемножуємо все числа від x до y (з инкрементом в 1), ми отримуємо відразу кілька версій - sum для цілих, sum для речових, sum для раціональних, sum для комплексних чисел і навіть sum для всіх тих числових типів що Ви самі визначили.

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

Гаразд, підемо далі?

Явна і неявна типізації

Лікнеп по типізації в мовах програмування

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

Мова з неявній типизацией, навпаки, пропонує Вам забути про типах і перекласти завдання виведення типів на компілятор або інтерпретатор. Англійськи термін для цього - implicit typing.

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

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

Переваги явною типізації
  • Наявність у кожної функції сигнатури (наприклад int add (int, int)) дозволяє без проблем визначити, що функція робить.
  • Програміст відразу записує, якого типу значення можуть зберігатися в конкретній змінної, що знімає необхідність запам'ятовувати це.
Переваги неявній типізації
  • Скорочення записи - def add (x, y) явно коротше, ніж int add (int x, int y).
  • Стійкість до змін. Наприклад якщо в функції тимчасова змінна була того-ж типу, що і вхідний аргумент, то в явно типизированном мовою при зміні типу вхідного аргументу потрібно буде змінити ще й тип тимчасової змінної.

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

Явна типізація по-вибору

Є мови, з неявній типизацией за замовчуванням і можливістю вказати тип значень при необхідності. Справжній тип вираження транслятор виведе автоматично. Один з таких мов - Haskell, давайте я наведу простий приклад, для наочності:

Примітка: я має намір використовував некаррірованную функцію, а також має намір записав приватну сигнатуру замість більш загальної add. (Num a) -> a -> a -> a. тому хотів показати ідею, без пояснення синтаксису Haskell'а.

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

Однак автоматичний висновок типів досить складна річ, і навіть в такому крутому мовою як Haskell, він іноді не справляється. (Як приклад можна привести обмеження мономорфізму)

Чи є мови з явною типизацией за замовчуванням і неявній по-необхідності? кон
ечно.

Неявна типізація по-вибору

У новому стандарті мови C ++, названому C ++ 11 (раніше називався C ++ 0x), було введено ключове слово auto, завдяки якому можна змусити компілятор вивести тип, виходячи з контексту:

Не погано. Але запис скоротилася не сильно. Давайте подивимося приклад з ітераторами (якщо не розумієте, не бійтеся, головне зауважте, що запис завдяки автоматичному висновку дуже сильно скорочується):

Ух ти! Ось це скорочення. Гаразд, але чи можна зробити що-небудь в дусі Haskell, де тип значення буде залежати від типів аргументів?

І знову відповідь так, завдяки ключовим словом decltype в комбінації з auto:

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

Деякі мови програмування по даній класифікації

висновок

Корисні посилання

Схожі статті