Динамично и силно писане. Типизация на езиците за програмиране

Въпреки че са възможни междинни варианти, ето два основни подхода:

  • Динамично писане: изчакайте, докато всяко обаждане приключи, и след това вземете решение.
  • Статично писане: Като се има предвид набор от правила, определете от изходния текст дали са възможни нарушения на типа по време на изпълнение. Системата се изпълнява, ако правилата гарантират, че няма да има грешки.

Тези термини са лесни за обяснение: кога динамично писанепроверката на типа се извършва, докато системата работи (динамично) и когато статично въвежданепроверката се извършва върху текста статично (преди изпълнение).

Статично писаневключва автоматична проверка, обикновено присвоена на компилатора. В резултат на това имаме проста дефиниция:

Определение: статично типизиран език

Един обектно-ориентиран език е статично типизиран, ако идва с набор от последователни, проверени от компилатора правила, които гарантират, че изпълнението на системата не води до нарушения на типа.

Терминът " силенписане" ( силен). Това съответства на ултимативния характер на дефиницията, изискващ пълно отсъствие на нарушение на типа. Също така е възможно слаб (слаб) форми статично въвеждане, при които правилата отстраняват определени нарушения, без да ги отстраняват изцяло. В този смисъл някои обектно-ориентирани езици са статично слабо типизирани. Ще се борим за най-силна типизация.

B динамично въведени езици, известни като нетипизирани, нямат декларации за тип и могат да имат всякакви стойности, прикачени към обектите по време на изпълнение. В тях не е възможна статична проверка на типа.

Правила за писане

Нашата OO нотация е статично въведена. Правилата за неговия тип бяха въведени в предишни лекции и се свеждат до три прости изисквания.

  • При деклариране на всеки обект или функция трябва да се уточни неговият тип, напр. acc: АКАУНТ. Всяка рутина има 0 или повече формални аргумента, чийто тип трябва да бъде посочен, например: put (x: G; i: INTEGER) .
  • Във всяко присвояване x:= y и във всяко извикване на подпрограма, в което y е действителният аргумент на формален аргумент x, типът на източника y трябва да е съвместим с типа на целта x. Дефиницията за съвместимост се основава на наследяването: B е съвместимо с A, ако е негов наследник - допълнено от правила за общи параметри (вижте "Въведение в наследяването").
  • Извикването на x.f(arg) изисква f да бъде компонент на базов клас за целевия тип x и f трябва да бъде експортирано в класа, в който се появява извикването (вижте 14.3).

Реализъм

Въпреки че дефиницията на статично типизиран език е дадена доста точно, тя не е достатъчна - необходими са неформални критерии при създаване на правила за въвеждане. Нека разгледаме два крайни случая.

  • Абсолютно правилен език, в който всяка синтактично правилна система е и тип правилен. Правилата за деклариране на тип не са необходими. Такива езици съществуват (представете си полската нотация на израз със събиране и изваждане на цели числа). За съжаление нито един истински универсален език не отговаря на този критерий.
  • Напълно неправилен език, който е лесен за създаване, като вземете всеки съществуващ език и добавите правило за въвеждане, което прави всякаквисистемата е неправилна. По дефиниция езикът е типизиран: тъй като няма системи, които следват правилата, нито една система няма да причини нарушение на типа.

Можем да кажем, че езиците от първия тип подходящ, Но безполезен, последното може да е полезно, но не е подходящо.

На практика се нуждаем от система за типове, която е едновременно използваема и полезна: достатъчно мощна, за да отговори на нуждите на изчисленията, и достатъчно удобна, за да не ни принуждава да правим усложнения, за да удовлетворим правилата за писане.

Да кажем този език реалистичен, ако е използваем и полезен на практика. За разлика от определението статично въвеждане, давайки категоричен отговор на въпроса: „ Дали езикът X е статично типизиран?", определението за реализъм е отчасти субективно.

В тази лекция ще се уверим, че нотацията, която предлагаме, е реалистична.

Песимизъм

Статично писанепо своята същност води до „песимистична” политика. Опит да се гарантира това всички изчисления не водят до неуспехи, отхвърля изчисления, които могат да завършат без грешки.

Помислете за нормален, необектен, подобен на Pascal език с различни типове REAL и INTEGER. При описание на n: INTEGER; r: Реалният оператор n:= r ще бъде отхвърлен като нарушаващ правилата. По този начин компилаторът ще отхвърли всички следните твърдения:

n:= 0,0 [A] n:= 1,0 [B] n:= -3,67 [C] n:= 3,67 - 3,67 [D]

Ако позволим тяхното изпълнение, ще видим, че [A] винаги ще работи, тъй като всяка бройна система има точно представяне на реалното число 0,0, което недвусмислено се превежда в 0 цели числа. [B] почти сигурно също ще работи. Резултатът от действие [C] не е очевиден (искаме ли да получим резултата чрез закръгляване или изхвърляне на дробната част?). [D] ще свърши работата, точно като оператора:

ако n^2< 0 then n:= 3.67 end [E]

което включва недостижимото присвояване (n^2 е квадрат на n). След замяна на n^2 с n, само серия от изпълнения ще даде правилния резултат. Присвояването на n на голяма реална стойност, която не може да бъде представена като цяло число, ще доведе до неуспех.

IN въведени езицивсички тези примери (работещи, неработещи, понякога работещи) се третират безмилостно като нарушения на правилата за описание на типа и се отхвърлят от всеки компилатор.

Въпросът не е ние щедали сме песимисти, но факт е, колкоможем да си позволим да бъдем песимисти. Нека се върнем към изискването за реализъм: ако правилата за типа са толкова песимистични, че пречат на лесното писане на изчисления, ние ще ги отхвърлим. Но ако постигането на безопасност на типа идва с малката цена на изразителна сила, ние ще ги приемем. Например, в среда за разработка, която предоставя функциите за заобикаляне и съкращаване, операторът n:= r се счита за неправилен, защото ви принуждава изрично да напишете преобразуването от реално към цяло число, вместо да използвате двусмислените преобразувания по подразбиране.

Статично писане: как и защо

Въпреки че ползите статично въвежданеса очевидни, добре е да поговорим за тях отново.

Предимства

Причини за употреба статично въвежданев обектната технология, която изброихме в началото на лекцията. Това са надеждност, лекота на разбиране и ефективност.

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

Ранното откриване на грешки също е важно, защото колкото по-дълго чакаме да ги открием, толкова повече ще се увеличат разходите за коригиране. Това свойство, интуитивно разбираемо за всички професионални програмисти, се потвърждава количествено от добре известните произведения на Бем. Зависимостта на разходите за корекция от времето за намиране на грешки е показана на графика, базирана на данни от редица големи индустриални проекти и експерименти, проведени с малък контролиран проект:


ориз. 17.1.

Четивностили Лесен за разбиране(четимост) има своите предимства. Във всички примери в тази книга появата на тип върху обект дава на читателя информация за неговата цел. Четливостта е изключително важна по време на фазата на поддръжка.

накрая ефективностможе да определи успеха или провала на обектната технология на практика. При липса статично въвежданеЗавършването на x.f(arg) може да отнеме произволно време. Причината за това е, че по време на изпълнение, ако f не бъде намерено в базовия клас на target x, търсенето ще продължи в неговите наследници, което е рецепта за неефективност. Можете да облекчите проблема, като подобрите търсенето на компонент в йерархията. Авторите на Self са положили много работа, опитвайки се да генерират най-добрия код за динамично въведен език. Но точно статично въвежданепозволява на такъв обектно-ориентиран продукт да се доближи или да се изравни по ефективност с традиционния софтуер.

Ключът към статично въвежданеидеята вече е заявена, че компилаторът, генериращ код за конструкцията x.f (arg), знае типа на x. Поради полиморфизма няма начин да се определи еднозначно подходящата версия на компонента f. Но декларацията стеснява набора от възможни типове, позволявайки на компилатора да конструира таблица, която осигурява достъп до правилното f с минимални разходи - с ограничена константатруден достъп. Извършени са допълнителни оптимизации статично обвързванеИ вграждане- също стана по-лесно благодарение на статично въвеждане, напълно елиминирайки разходите там, където се прилагат.

Случаят за динамично писане

Въпреки всичко това, динамично писанене губи своите привърженици, особено сред програмистите на Smalltalk. Техните аргументи се основават предимно на реализма, обсъден по-горе. Те са сигурни, че статично въвежданеги ограничава твърде много, като им пречи да изразяват свободно творческите си идеи, понякога го наричат ​​„колан на целомъдрието“.

Човек може да се съгласи с подобно разсъждение, но само за статично въведени езици, които не поддържат редица функции. Струва си да се отбележи, че всички понятия, свързани с понятието тип и въведени в предишни лекции, са необходими - отхвърлянето на което и да е от тях е изпълнено със сериозни ограничения, а въвеждането им, напротив, дава на нашите действия гъвкавост и ни дава възможността да се насладите напълно на практичността статично въвеждане.

Машинопис: компоненти на успеха

Какви са механизмите на реалистичното статично въвеждане? Всички те бяха представени в предишни лекции и затова можем само накратко да ги припомним. Изброяването им заедно показва последователността и силата на тяхната асоциация.

Нашата типова система е изцяло базирана на концепцията клас. Дори основни типове като INTEGER са класове, което означава, че не се нуждаем от специални правила за описание на предварително дефинирани типове. (Това е мястото, където нашата нотация се различава от „хибридните“ езици като Object Pascal, Java и C++, които комбинират системите от типове на по-старите езици с базирана на клас технология за обекти.)

Разширени типовени дават повече гъвкавост, като позволяват типове, чиито стойности обозначават обекти, както и типове, чиито стойности обозначават препратки.

Решаващата дума при създаването на гъвкава типова система принадлежи на наследствои свързаната концепция съвместимост. Това преодолява основното ограничение на класическите типизирани езици, като Pascal и Ada, в които операторът x:= y изисква типът на x и y да е един и същ. Това правило е твърде строго: то забранява използването на обекти, които могат да представляват обекти от свързани типове (SAVINGS_ACCOUNT и CHECKING_ACCOUNT). При наследяване изискваме само типова съвместимост y с тип x, например x е от тип ACCOUNT, y е SAVINGS_ACCOUNT и вторият клас е наследник на първия.

На практика един статично въведен език се нуждае от поддръжка множествено наследяване. Основните обвинения са известни статично въвежданее, че не позволява обектите да бъдат интерпретирани по различен начин. По този начин обект DOCUMENT може да се предава по мрежа и следователно изисква компоненти, свързани с типа MESSAGE. Но тази критика е вярна само за ограничените езици единично наследство.


ориз. 17.2.

Универсалностнеобходими, например, за описание на гъвкави, но сигурни контейнерни структури от данни (напр клас СПИСЪК [G] ...). Без този механизъм, статично въвежданеще изисква деклариране на различни класове за списъци, които се различават по типа на елементите.

В някои случаи е необходима гъвкавост лимит, което ви позволява да използвате операции, които се прилагат само към обекти от общ тип. Ако общият клас SORTABLE_LIST поддържа сортиране, той изисква обекти от тип G, където G е общият параметър, да имат оператор за сравнение. Това се постига чрез свързване с G на клас, който определя общо ограничение - COMPARABLE:

клас SORTABLE_LIST ...

Всеки действителен общ параметър SORTABLE_LIST трябва да бъде потомък на класа COMPARABLE, който има необходимия компонент.

Друг задължителен механизъм е опит за възлагане- организира достъп до онези обекти, чийто тип софтуерът не контролира. Ако y е обект на база данни или обект, извлечен по мрежата, тогава операторът x ?= y ще присвои на x стойността на y, ако y е от съвместим тип, или, ако не е, ще даде на x стойността на Void .

Изявлениясвързани, като част от идеята за проектиране чрез договор, с класове и техните компоненти под формата на предпоставки, постусловия и инварианти на класове, позволяват да се опишат семантични ограничения, които не са обхванати спецификация на типа. Езици като Pascal и Ada имат типове диапазони, които могат да ограничат стойностите на обекта до между 10 и 20, например, но няма да можете да гарантирате, че i е отрицателно, като винаги е два пъти стойността на j. На помощ идват инвариантите на класа, предназначени да отразяват точно наложените ограничения, колкото и сложни да са те.

Закачени рекламиса необходими, за да се избегне лавинообразно дублиране на кодове на практика. Обявяване y: като x, вие получавате гаранция, че y ще се промени след всякакви повтарящи се декларации от тип x в наследника. Без този механизъм разработчиците постоянно биха предекларирали в опит да поддържат последователност между различните типове.

Залепващите декларации са специален случай на последния езиков механизъм, който изискваме - ковариация, които ще разгледаме подробно по-късно.

При разработването на софтуерни системи всъщност е необходимо още едно свойство, присъщо на самата среда за разработка - бързо инкрементално повторно компилиране. Когато пишете или модифицирате система, искате да видите ефекта от промените възможно най-бързо. При статично въвежданетрябва да дадете време на компилатора да провери отново типовете. Традиционните процедури за компилиране изискват повторен превод на цялата система (и неговите възли), и този процес може да бъде болезнено дълъг, особено когато преминете към по-мащабни системи. Това явление се превърна в аргумент в полза тълкувателносистеми, като ранните среди на Lisp или Smalltalk, управляваха системата с малко или никаква обработка и без проверка на типа. Този аргумент вече е забравен. Един добър модерен компилатор определя как кодът се е променил след последната компилация и обработва само промените, които открива.

„Бебето написано ли е“?

Нашата цел е строг статично въвеждане. Ето защо трябва да избягваме всякакви вратички в нашата „игра по правилата“, поне точно да ги идентифицираме, ако съществуват.

Най-често срещаната вратичка в статичното въведени езицие наличието на трансформации, които променят типа на обекта. В C и неговите производни езици те се наричат ​​"преобразуване на типове" или кастинг. Нотацията (OTHER_TYPE) x указва, че стойността x се третира от компилатора като имаща тип OTHER_TYPE, предмет на определени ограничения за възможни типове.

Такива механизми заобикалят ограниченията на проверката на типа. Преобразуването е често срещано в програмирането на C, включително диалекта ANSI C. Дори в C++, преобразуването на типове, макар и по-рядко, остава често срещано и може би необходимо.

Придържайте се към правилата статично въвежданене е толкова просто, ако могат да бъдат заобиколени по всяко време чрез кастинг.

Въвеждане и свързване

Въпреки че като читател на тази книга вероятно ще разграничите статичното писане от статичното подвързване, има хора, които не могат да направят това. Това може да се дължи отчасти на влиянието на езика Smalltalk, който се застъпва динамичен подходкъм двата проблема и може да формира погрешното схващане, че имат едно и също решение. (В нашата книга ние твърдим, че за създаване на надеждни и гъвкави програми е желателно да се комбинират статично въвеждане и динамично обвързване.)

Както въвеждането, така и обвързването се занимават със семантиката на основната конструкция x.f (arg), но отговарят на два различни въпроса:

Въвеждане и свързване

  • Въвеждане на въпрос: кога трябва да знаем със сигурност, че по време на изпълнение ще има операция, съответстваща на f, приложена към обекта, прикачен към обект x (с параметър arg )?
  • Въпрос за свързване: Кога трябва да знаем каква операция инициира дадено повикване?

Въвеждането отговаря на въпроса за наличността поне единоперации, обвързването отговаря за избора необходимо.

В рамките на обектния подход:

  • проблемът, който възниква при писане, е свързан с полиморфизъм: от х по време на изпълнениеможе да означава обекти от няколко различни типа, трябва да сме сигурни, че операцията, представяща f, е наличнивъв всеки от тези случаи;
  • причинен проблем със свързването повтарящи се съобщения: Тъй като един клас може да променя наследени компоненти, може да има две или повече операции, които претендират, че представляват f в дадено извикване.

И двата проблема могат да бъдат решени както динамично, така и статично. Съществуващите езици предоставят и четирите решения.

И динамичното обвързване е въплътено в нотацията, предложена в тази книга.

Нека отбележим уникалността на езика C++, който поддържа статично типизиране, въпреки че не е строго поради наличието на преобразуване на типове, статично свързване(по подразбиране), динамично обвързване при изрично указване на виртуален ( виртуален) реклами.

Причина за избора статично въвежданеи динамичното свързване е очевидно. Първият въпрос е: "Кога ще разберем, че компонентите съществуват?" - предлага статичен отговор: " Колкото по-рано, толкова по-добре", което означава: по време на компилиране. Вторият въпрос: "Кой компонент трябва да използвам?" изисква динамичен отговор: " този, от който се нуждаете“, - съответстващ динамичен типобект, определен по време на изпълнение. Това е единственото приемливо решение, ако статичното и динамичното обвързване дават различни резултати.

При статично въвежданекомпилаторът няма да отхвърли извикването, ако може да се гарантира, че когато програмата се изпълнява, обектът my_aircraft ще има обект, прикачен към него, който идва с компонент, съответстващ на lower_landing_gear. Основната техника за получаване на гаранции е проста: задължителната декларация на my_aircraft изисква базовият клас на неговия тип да включва такъв компонент. Следователно my_aircraft не може да бъде деклариран като AIRCRAFT, тъй като последният няма по-нисък_шапия на това ниво; хеликоптерите, поне в нашия пример, не знаят как да свалят колесника си. Ако декларираме обекта като САМОЛЕТ, - клас, съдържащ необходимия компонент - всичко ще бъде наред.

Динамично писанев стил Smalltalk изисква да изчакате повикването и в момента на изпълнението му да проверите за наличието на необходимия компонент. Това поведение е възможно за прототипи и експериментални разработки, но е недопустимо за индустриални системи - в момента на полета е твърде късно да попитате дали имате колесник.

Тази статия съдържа необходимия минимум от онези неща, които просто трябва да знаете за писането, за да не наричате динамичното писане зло, Lisp език без тип, а C език със строго въведен тип.

Пълната версия съдържа подробно описание на всички видове писане, подправено с примери на кодове, връзки към популярни езици за програмиране и илюстративни снимки.

Препоръчвам първо да прочетете кратката версия на статията, а след това и пълната, ако желаете.

Съкратена версия

Въз основа на типизирането езиците за програмиране обикновено се разделят на два големи лагера - типизирани и нетипизирани (безтипови). Първият включва например C, Python, Scala, PHP и Lua, а вторият включва асемблер, Forth и Brainfuck.

Тъй като „безтиповото писане“ по своята същност е просто като щепсел, то не се разделя допълнително на други типове. Но въведените езици са разделени на още няколко припокриващи се категории:

  • Статично/динамично писане. Статичността се определя от факта, че крайните типове променливи и функции се задават по време на компилиране. Тези. компилаторът вече е 100% сигурен кой тип къде е. При динамичното въвеждане всички типове се откриват по време на изпълнение на програмата.

    Примери:
    Статични: C, Java, C#;
    Динамични: Python, JavaScript, Ruby.

  • Силно/слабо писане (наричано понякога силно/слабо). Силното въвеждане се отличава с факта, че езикът не позволява смесване на различни типове в изрази и не извършва автоматични неявни преобразувания, например не можете да извадите набор от низ. Слабо въведените езици извършват много неявни преобразувания автоматично, въпреки че може да възникне загуба на точност или преобразуването е двусмислено.

    Примери:
    Силен: Java, Python, Haskell, Lisp;
    Слаб: C, JavaScript, Visual Basic, PHP.

  • Изрично/имплицитно въвеждане. Явно въведените езици се различават по това, че типът на новите променливи/функции/техните аргументи трябва да бъде посочен изрично. Съответно, езиците с имплицитно въвеждане прехвърлят тази задача на компилатора/интерпретатора.

    Примери:
    Изрично: C++, D, C#
    Подразбира се: PHP, Lua, JavaScript

Трябва също така да се отбележи, че всички тези категории се припокриват, например езикът C има статично слабо явно въвеждане, а езикът Python има динамично силно имплицитно въвеждане.

Въпреки това, няма езици със статично и динамично писане едновременно. Въпреки че, гледайки напред, ще кажа, че лъжа тук - те наистина съществуват, но повече за това по-късно.

Подробна версия

Ако кратката версия не ви е достатъчна, това е добре. Не напразно съм написал подробно? Основното е, че беше просто невъзможно да се побере цялата полезна и интересна информация в кратка версия, а подробната вероятно би била твърде дълга, за да може всеки да чете без напрежение.

Безтипово писане

В езиците за програмиране без тип всички обекти се считат просто за последователности от битове с различна дължина.

Типизирането без тип обикновено е присъщо на ниско ниво (език за сглобяване, Forth) и езотерични (Brainfuck, HQ9, Piet) езици. Въпреки това, наред с недостатъците, той има и някои предимства.

Предимства
  • Позволява ви да пишете на изключително ниско ниво и компилаторът/интерпретаторът няма да пречи на никакви проверки на типа. Вие сте свободни да извършвате всякакви операции с всякакъв тип данни.
  • Полученият код обикновено е по-ефективен.
  • Прозрачност на инструкциите. Ако знаете езика, обикновено няма съмнение какъв е този или онзи код.
недостатъци
  • Сложност. Често има нужда да се представят сложни стойности като списъци, низове или структури. Това може да причини неудобство.
  • Липса на проверки. Всякакви безсмислени действия, като изваждане на указател към масив от символ, ще се считат за напълно нормални, което е изпълнено с фини грешки.
  • Ниско ниво на абстракция. Работата с всеки сложен тип данни не се различава от работата с числа, което разбира се ще създаде много трудности.
Силно безтипово въвеждане?

Да, това съществува. Например, в асемблерния език (за архитектурата x86/x86-64, не знам други) не можете да асемблирате програма, ако се опитате да заредите данни от регистъра rax (64 бита) в регистъра cx (16 бита) .

mov cx, eax; грешка във времето на сглобяване

Значи се оказва, че асемблерът все още има писане? Смятам, че тези проверки не са достатъчни. И вашето мнение, разбира се, зависи само от вас.

Статично и динамично писане

Основното нещо, което отличава статичното писане от динамичното е, че всички проверки на типове се извършват по време на компилиране, а не по време на изпълнение.

Някои хора може да смятат, че статичното въвеждане е твърде ограничаващо (всъщност е така, но това отдавна е елиминирано с помощта на някои техники). Някои хора казват, че динамично въведените езици си играят с огъня, но какви характеристики ги отличават? И двата вида наистина ли имат шанс да съществуват? Ако не, тогава защо има толкова много езици, които са статично и динамично въведени?

Нека да го разберем.

Предимства на статичното писане
  • Проверката на типа се извършва само веднъж - на етапа на компилация. Това означава, че няма да се налага непрекъснато да разбираме дали се опитваме да разделим число на низ (и или да извеждаме грешка, или да извършваме преобразуване).
  • Скорост на изпълнение. От предишната точка става ясно, че статично въведените езици са почти винаги по-бързи от динамично въведените.
  • При някои допълнителни условия ви позволява да откриете потенциални грешки още на етапа на компилация.
Предимства на динамичното писане
  • Лесно създаване на универсални колекции - купища всичко и всеки (рядко възниква такава нужда, но когато възникне динамично писане, това ще помогне).
  • Удобство при описване на обобщени алгоритми (например сортиране на масиви, което ще работи не само върху списък от цели числа, но и върху списък с реални числа и дори върху списък с низове).
  • Лесни за научаване - Динамично въведените езици обикновено са много добри за започване на програмиране.

Обобщено програмиране

Добре, най-важният аргумент за динамичното писане е удобството да се описват общи алгоритми. Нека си представим проблем - имаме нужда от функция за търсене в няколко масива (или списъка) - масив от цели числа, масив от реални числа и масив от знаци.

Как ще го решим? Нека го решим на 3 различни езика: един с динамично писане и два със статично писане.

Ще използвам един от най-простите алгоритми за търсене - brute force. Функцията ще получи елемента, който се търси, самия масив (или списък) и ще върне индекса на елемента или ако елементът не е намерен - (-1).

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

Def find(required_element, list): for (index, element) in enumerate(list): if element == required_element: return index return (-1)

Както можете да видите, всичко е просто и няма проблеми с факта, че списъкът може да съдържа числа, списъци или други масиви. много добре Нека отидем по-нататък - решете същата задача в C!

Статично решение (C):

Unsigned int find_int(int required_element, int array, unsigned int size) ( for (unsigned int i = 0; i< size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_float(float required_element, float array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_char(char required_element, char array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); }

Е, всяка функция поотделно е подобна на версията на Python, но защо са три от тях? Статичното програмиране наистина ли е загубило?

Да и не. Има няколко техники за програмиране, една от които ще разгледаме сега. Нарича се общо програмиране и езикът C++ го поддържа доста добре. Нека да разгледаме новата версия:

Статично решение (генерично програмиране, C++):

Шаблон unsigned int find(T required_element, std::vector масив) ( for (unsigned int i = 0; i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

Добре! Не изглежда много по-сложно от версията на Python и не изисква много писане. В допълнение, ние получихме имплементация за всички масиви, а не само за 3-те, необходими за решаване на проблема!

Тази версия изглежда е точно това, от което се нуждаем - получаваме както предимствата на статичното писане, така и някои от предимствата на динамичното писане.

Чудесно е, че това изобщо е възможно, но може да е още по-добре. Първо, обобщеното програмиране може да бъде по-удобно и красиво (например на езика Haskell). Второ, в допълнение към генерализираното програмиране, можете също да използвате полиморфизъм (резултатът ще бъде по-лош), претоварване на функции (по подобен начин) или макроси.

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

Трябва също да се спомене, че много статични езици позволяват динамично въвеждане, например:

  • C# поддържа динамичния псевдотип.
  • F# поддържа синтактична захар под формата на оператора ?, въз основа на който може да се реализира имитация на динамично въвеждане.
  • Haskell - динамичното въвеждане се осигурява от модула Data.Dynamic.
  • Delphi - чрез специален тип Variant.

Освен това някои динамично въведени езици ви позволяват да се възползвате от статичното въвеждане:

  • Common Lisp - типове декларации.
  • Perl - от версия 5.6, доста ограничен.

Силно и слабо писане

Строго типизираните езици не позволяват обекти от различни типове да се смесват в изрази и не извършват автоматични преобразувания. Те се наричат ​​още "строго типизирани езици". Английският термин за това е силно типизиране.

Слабо типизираните езици, напротив, насърчават програмиста да смесва различни типове в един израз, а самият компилатор ще намали всичко до един тип. Те се наричат ​​още "свободно въведени езици". Английският термин за това е слаб тип.

Слабото писане често се бърка с динамично писане, което е напълно погрешно. Един динамично типизиран език може да бъде или слабо, или силно типизиран.

Въпреки това, малко хора придават значение на стриктността при писане. Често се казва, че ако един език е статично въведен, тогава можете да уловите много потенциални грешки при компилиране. Те ви лъжат!

Езикът също трябва да има силна типизация. Наистина, ако компилаторът, вместо да съобщи за грешка, просто добави низ към число или, дори по-лошо, извади друг от един масив, каква полза за нас, че всички „проверки“ на типовете ще бъдат при компилацията етап? Точно така - слабото статично въвеждане е дори по-лошо от силното динамично въвеждане! (Е, това е моето мнение)

Така че слабото въвеждане няма ли никакви предимства? Може да изглежда така, но въпреки факта, че съм горещ привърженик на силното въвеждане, трябва да се съглася, че слабото въвеждане също има предимства.

Искате ли да знаете кои?

Предимства на силното въвеждане
  • Надеждност - Ще получите изключение или грешка при компилация вместо неправилно поведение.
  • Скорост - Вместо скрити преобразувания, които могат да бъдат доста скъпи, със силно въвеждане трябва да ги напишете изрично, което принуждава програмиста поне да знае, че тази част от кода може да е бавна.
  • Разбиране как работи програмата - отново, вместо имплицитно преобразуване на типове, програмистът пише всичко сам, което означава, че той приблизително разбира, че сравняването на низ и число не се случва само по себе си, а не чрез магия.
  • Сигурност - когато пишете трансформации на ръка, вие знаете точно какво конвертирате и към какво. Също така винаги ще сте наясно, че подобни преобразувания могат да доведат до загуба на точност и неправилни резултати.
Предимства на слабото писане
  • Удобство при използване на смесени изрази (например от цели и реални числа).
  • Абстрахиране от писане и фокусиране върху задачата.
  • Краткост на записа.

Добре, разбрахме, оказва се, че слабото писане има и предимства! Има ли начини да прехвърлите предимствата на слабото въвеждане към силното въвеждане?

Оказва се, че са дори две.

Неявно преобразуване на типове, в недвусмислени ситуации и без загуба на данни

Уау... Доста дълга точка. Нека го съкратя допълнително до „ограничено имплицитно преобразуване“. И така, какво означава недвусмислената ситуация и загубата на данни?

Недвусмислената ситуация е трансформация или операция, в която същността е ясна веднага. Например добавянето на две числа е недвусмислена ситуация. Но преобразуването на число в масив не е (може би ще бъде създаден масив от един елемент, може би масив с такава дължина, запълнен с елементи по подразбиране, и може би числото ще бъде преобразувано в низ и след това в масив от знаци).

Загубата на данни е още по-лесна. Ако преобразуваме реалното число 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), получаваме няколко версии наведнъж - сума за цели числа, сума за реални числа, сума за рационални числа, сума за комплексни числа и дори сума за всички онези числови типове, които сами сте дефинирали.

Разбира се, тази техника спестява само когато се използват смесени изрази с числови литерали и това е само върхът на айсберга.

Така можем да кажем, че най-доброто решение е балансирането на границата между силното и слабото писане. Но нито един език все още не постига перфектния баланс, така че аз клоня повече към силно типизирани езици (като Haskell, Java, C#, Python), а не към слабо типизирани (като C, JavaScript, Lua, PHP).

Явно и имплицитно въвеждане

Изрично въведеният език изисква от програмиста да посочи типовете на всички променливи и функции, които декларира. Английският термин за това е изрично въвеждане.

От друга страна, имплицитно типизираният език ви насърчава да забравите за типовете и да оставите задачата за извеждане на типове на компилатора или интерпретатора. Английският термин за това е имплицитно въвеждане.

Първоначално може да си помислите, че имплицитното въвеждане е еквивалентно на динамично, а явното въвеждане е еквивалентно на статично, но по-късно ще видим, че това не е така.

Има ли предимства за всеки тип и отново, има ли комбинации от тях и има ли езици, които поддържат и двата метода?

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

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

Явно въвеждане по избор

Има езици с имплицитно въвеждане по подразбиране и възможност за указване на типа стойности, ако е необходимо. Преводачът ще изведе реалния тип израз автоматично. Един от тези езици е Haskell, позволете ми да дам прост пример за яснота:

Без изрична спецификация на тип add (x, y) = x + y -- Изрична спецификация на тип add:: (Integer, Integer) -> Integer add (x, y) = x + y

Забележка: Умишлено използвах неизползвана функция и също умишлено написах частен подпис вместо по-общото add:: (Num a) -> a -> a -> a , защото Исках да покажа идеята, без да обяснявам синтаксиса на Haskell.

хм Както виждаме, много е хубаво и кратко. Писането на функция отнема само 18 знака на един ред, включително интервалите!

Автоматичното извеждане на типове обаче е доста сложно нещо и дори в такъв готин език като Haskell понякога се проваля. (пример е ограничението за мономорфизъм)

Има ли езици с явно въвеждане по подразбиране и имплицитно въвеждане, ако е необходимо? Con
разбира се.

Неявно въвеждане по избор

Новият езиков стандарт C++, наречен C++11 (преди наричан C++0x), въведе ключовата дума auto, която позволява на компилатора да изведе типа въз основа на контекста:

Нека сравним: // Ръчно указване на типа unsigned int a = 5; unsigned int b = a + 3; // Автоматичен изход от тип unsigned int a = 5; автоматично b = a + 3;

не е лошо Но записът не е намалял много. Нека да разгледаме пример с итератори (ако не разбирате, не се страхувайте, основното нещо, което трябва да отбележите е, че записът е значително намален благодарение на автоматичния изход):

// Ръчно указване на типа std::vector vec = случаенВектор(30); for (std::vector::const_iterator it = vec.cbegin(); ...) ( ... ) // Автоматично извеждане на типа auto vec = randomVector (30); за (auto it = vec.cbegin(); ...) ( ... )

Уау! Това е съкращението. Добре, но възможно ли е да се направи нещо като Haskell, където връщаният тип зависи от типовете на аргументите?

И отново отговорът е да, благодарение на ключовата дума decltype в комбинация с auto:

// Ръчен тип int divide(int x, int y) ( ... ) // Автоматичен тип извеждане auto divide(int x, int y) -> decltype(x / y) ( ... )

Тази форма на нотиране може да не изглежда много добра, но когато се комбинира с генерично програмиране (шаблони/генерични), имплицитното въвеждане или автоматичното извеждане на типа върши чудеса.

Някои езици за програмиране според тази класификация

Ще дам малък списък с популярни езици и ще напиша как те са разделени във всяка категория „набиране“.

JavaScript - Динамичен / Слаб / Неявен Ruby - Динамичен / Силен / Неявен Python - Динамичен / Силен / Неявен Java - Статичен / Силен / Явен PHP - Динамичен / Слаб / Неявен C - Статичен / Слаб / Явен C++ - Статичен / Полусилен / Явен Perl - Динамичен / Слаб / Неявен Objective-C - Статичен / Слаб / Явен C# - Статичен / Силен / Явен Haskell - Статичен / Силен / Неявен Common Lisp - Динамичен / Силен / Неявен

Може би съм направил грешка някъде, особено с CL, PHP и Obj-C, ако имате различно мнение за някой език, пишете в коментарите.

Заключение

добре Скоро ще светне и чувствам, че няма какво повече да кажа за писането. О, как? Бездънна ли е темата? Има ли много недоизказано? Моля, споделете полезна информация в коментарите.

Тази статия съдържа необходимия минимум от онези неща, които просто трябва да знаете за писането, за да не наричате динамичното писане зло, Lisp език без тип, а C език със строго въведен тип.

Пълната версия съдържа подробно описание на всички видове писане, подправено с примери на кодове, връзки към популярни езици за програмиране и илюстративни снимки.

Препоръчвам първо да прочетете кратката версия на статията, а след това и пълната, ако желаете.

Съкратена версия

Въз основа на типизирането езиците за програмиране обикновено се разделят на два големи лагера - типизирани и нетипизирани (безтипови). Първият включва например C, Python, Scala, PHP и Lua, а вторият включва асемблер, Forth и Brainfuck.

Тъй като „безтиповото писане“ по своята същност е просто като щепсел, то не се разделя допълнително на други типове. Но въведените езици са разделени на още няколко припокриващи се категории:

  • Статично/динамично писане. Статичността се определя от факта, че крайните типове променливи и функции се задават по време на компилиране. Тези. компилаторът вече е 100% сигурен кой тип къде е. При динамичното въвеждане всички типове се откриват по време на изпълнение на програмата.

    Примери:
    Статични: C, Java, C#;
    Динамични: Python, JavaScript, Ruby.

  • Силно/слабо писане (наричано понякога силно/слабо). Силното въвеждане се отличава с факта, че езикът не позволява смесване на различни типове в изрази и не извършва автоматични неявни преобразувания, например не можете да извадите набор от низ. Слабо въведените езици извършват много неявни преобразувания автоматично, въпреки че може да възникне загуба на точност или преобразуването е двусмислено.

    Примери:
    Силен: Java, Python, Haskell, Lisp;
    Слаб: C, JavaScript, Visual Basic, PHP.

  • Изрично/имплицитно въвеждане. Явно въведените езици се различават по това, че типът на новите променливи/функции/техните аргументи трябва да бъде посочен изрично. Съответно, езиците с имплицитно въвеждане прехвърлят тази задача на компилатора/интерпретатора.

    Примери:
    Изрично: C++, D, C#
    Подразбира се: PHP, Lua, JavaScript

Трябва също така да се отбележи, че всички тези категории се припокриват, например езикът C има статично слабо явно въвеждане, а езикът Python има динамично силно имплицитно въвеждане.

Въпреки това, няма езици със статично и динамично писане едновременно. Въпреки че, гледайки напред, ще кажа, че лъжа тук - те наистина съществуват, но повече за това по-късно.

Подробна версия

Ако кратката версия не ви е достатъчна, това е добре. Не напразно съм написал подробно? Основното е, че беше просто невъзможно да се побере цялата полезна и интересна информация в кратка версия, а подробната вероятно би била твърде дълга, за да може всеки да чете без напрежение.

Безтипово писане

В езиците за програмиране без тип всички обекти се считат просто за последователности от битове с различна дължина.

Типизирането без тип обикновено е присъщо на ниско ниво (език за сглобяване, Forth) и езотерични (Brainfuck, HQ9, Piet) езици. Въпреки това, наред с недостатъците, той има и някои предимства.

Предимства
  • Позволява ви да пишете на изключително ниско ниво и компилаторът/интерпретаторът няма да пречи на никакви проверки на типа. Вие сте свободни да извършвате всякакви операции с всякакъв тип данни.
  • Полученият код обикновено е по-ефективен.
  • Прозрачност на инструкциите. Ако знаете езика, обикновено няма съмнение какъв е този или онзи код.
недостатъци
  • Сложност. Често има нужда да се представят сложни стойности като списъци, низове или структури. Това може да причини неудобство.
  • Липса на проверки. Всякакви безсмислени действия, като изваждане на указател към масив от символ, ще се считат за напълно нормални, което е изпълнено с фини грешки.
  • Ниско ниво на абстракция. Работата с всеки сложен тип данни не се различава от работата с числа, което разбира се ще създаде много трудности.
Силно безтипово въвеждане?

Да, това съществува. Например, в асемблерния език (за архитектурата x86/x86-64, не знам други) не можете да асемблирате програма, ако се опитате да заредите данни от регистъра rax (64 бита) в регистъра cx (16 бита) .

mov cx, eax; грешка във времето на сглобяване

Значи се оказва, че асемблерът все още има писане? Смятам, че тези проверки не са достатъчни. И вашето мнение, разбира се, зависи само от вас.

Статично и динамично писане

Основното нещо, което отличава статичното писане от динамичното е, че всички проверки на типове се извършват по време на компилиране, а не по време на изпълнение.

Някои хора може да смятат, че статичното въвеждане е твърде ограничаващо (всъщност е така, но това отдавна е елиминирано с помощта на някои техники). Някои хора казват, че динамично въведените езици си играят с огъня, но какви характеристики ги отличават? И двата вида наистина ли имат шанс да съществуват? Ако не, тогава защо има толкова много езици, които са статично и динамично въведени?

Нека да го разберем.

Предимства на статичното писане
  • Проверката на типа се извършва само веднъж - на етапа на компилация. Това означава, че няма да се налага непрекъснато да разбираме дали се опитваме да разделим число на низ (и или да извеждаме грешка, или да извършваме преобразуване).
  • Скорост на изпълнение. От предишната точка става ясно, че статично въведените езици са почти винаги по-бързи от динамично въведените.
  • При някои допълнителни условия ви позволява да откриете потенциални грешки още на етапа на компилация.
Предимства на динамичното писане
  • Лесно създаване на универсални колекции - купища всичко и всеки (рядко възниква такава нужда, но когато възникне динамично писане, това ще помогне).
  • Удобство при описване на обобщени алгоритми (например сортиране на масиви, което ще работи не само върху списък от цели числа, но и върху списък с реални числа и дори върху списък с низове).
  • Лесни за научаване - Динамично въведените езици обикновено са много добри за започване на програмиране.

Обобщено програмиране

Добре, най-важният аргумент за динамичното писане е удобството да се описват общи алгоритми. Нека си представим проблем - имаме нужда от функция за търсене в няколко масива (или списъка) - масив от цели числа, масив от реални числа и масив от знаци.

Как ще го решим? Нека го решим на 3 различни езика: един с динамично писане и два със статично писане.

Ще използвам един от най-простите алгоритми за търсене - brute force. Функцията ще получи елемента, който се търси, самия масив (или списък) и ще върне индекса на елемента или ако елементът не е намерен - (-1).

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

Def find(required_element, list): for (index, element) in enumerate(list): if element == required_element: return index return (-1)

Както можете да видите, всичко е просто и няма проблеми с факта, че списъкът може да съдържа числа, списъци или други масиви. много добре Нека отидем по-нататък - решете същата задача в C!

Статично решение (C):

Unsigned int find_int(int required_element, int array, unsigned int size) ( for (unsigned int i = 0; i< size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_float(float required_element, float array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_char(char required_element, char array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); }

Е, всяка функция поотделно е подобна на версията на Python, но защо са три от тях? Статичното програмиране наистина ли е загубило?

Да и не. Има няколко техники за програмиране, една от които ще разгледаме сега. Нарича се общо програмиране и езикът C++ го поддържа доста добре. Нека да разгледаме новата версия:

Статично решение (генерично програмиране, C++):

Шаблон unsigned int find(T required_element, std::vector масив) ( for (unsigned int i = 0; i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

Добре! Не изглежда много по-сложно от версията на Python и не изисква много писане. В допълнение, ние получихме имплементация за всички масиви, а не само за 3-те, необходими за решаване на проблема!

Тази версия изглежда е точно това, от което се нуждаем - получаваме както предимствата на статичното писане, така и някои от предимствата на динамичното писане.

Чудесно е, че това изобщо е възможно, но може да е още по-добре. Първо, обобщеното програмиране може да бъде по-удобно и красиво (например на езика Haskell). Второ, в допълнение към генерализираното програмиране, можете също да използвате полиморфизъм (резултатът ще бъде по-лош), претоварване на функции (по подобен начин) или макроси.

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

Трябва също да се спомене, че много статични езици позволяват динамично въвеждане, например:

  • C# поддържа динамичния псевдотип.
  • F# поддържа синтактична захар под формата на оператора ?, въз основа на който може да се реализира имитация на динамично въвеждане.
  • Haskell - динамичното въвеждане се осигурява от модула Data.Dynamic.
  • Delphi - чрез специален тип Variant.

Освен това някои динамично въведени езици ви позволяват да се възползвате от статичното въвеждане:

  • Common Lisp - типове декларации.
  • Perl - от версия 5.6, доста ограничен.

Силно и слабо писане

Строго типизираните езици не позволяват обекти от различни типове да се смесват в изрази и не извършват автоматични преобразувания. Те се наричат ​​още "строго типизирани езици". Английският термин за това е силно типизиране.

Слабо типизираните езици, напротив, насърчават програмиста да смесва различни типове в един израз, а самият компилатор ще намали всичко до един тип. Те се наричат ​​още "свободно въведени езици". Английският термин за това е слаб тип.

Слабото писане често се бърка с динамично писане, което е напълно погрешно. Един динамично типизиран език може да бъде или слабо, или силно типизиран.

Въпреки това, малко хора придават значение на стриктността при писане. Често се казва, че ако един език е статично въведен, тогава можете да уловите много потенциални грешки при компилиране. Те ви лъжат!

Езикът също трябва да има силна типизация. Наистина, ако компилаторът, вместо да съобщи за грешка, просто добави низ към число или, дори по-лошо, извади друг от един масив, каква полза за нас, че всички „проверки“ на типовете ще бъдат при компилацията етап? Точно така - слабото статично въвеждане е дори по-лошо от силното динамично въвеждане! (Е, това е моето мнение)

Така че слабото въвеждане няма ли никакви предимства? Може да изглежда така, но въпреки факта, че съм горещ привърженик на силното въвеждане, трябва да се съглася, че слабото въвеждане също има предимства.

Искате ли да знаете кои?

Предимства на силното въвеждане
  • Надеждност - Ще получите изключение или грешка при компилация вместо неправилно поведение.
  • Скорост - Вместо скрити преобразувания, които могат да бъдат доста скъпи, със силно въвеждане трябва да ги напишете изрично, което принуждава програмиста поне да знае, че тази част от кода може да е бавна.
  • Разбиране как работи програмата - отново, вместо имплицитно преобразуване на типове, програмистът пише всичко сам, което означава, че той приблизително разбира, че сравняването на низ и число не се случва само по себе си, а не чрез магия.
  • Сигурност - когато пишете трансформации на ръка, вие знаете точно какво конвертирате и към какво. Също така винаги ще сте наясно, че подобни преобразувания могат да доведат до загуба на точност и неправилни резултати.
Предимства на слабото писане
  • Удобство при използване на смесени изрази (например от цели и реални числа).
  • Абстрахиране от писане и фокусиране върху задачата.
  • Краткост на записа.

Добре, разбрахме, оказва се, че слабото писане има и предимства! Има ли начини да прехвърлите предимствата на слабото въвеждане към силното въвеждане?

Оказва се, че са дори две.

Неявно преобразуване на типове, в недвусмислени ситуации и без загуба на данни

Уау... Доста дълга точка. Нека го съкратя допълнително до „ограничено имплицитно преобразуване“. И така, какво означава недвусмислената ситуация и загубата на данни?

Недвусмислената ситуация е трансформация или операция, в която същността е ясна веднага. Например добавянето на две числа е недвусмислена ситуация. Но преобразуването на число в масив не е (може би ще бъде създаден масив от един елемент, може би масив с такава дължина, запълнен с елементи по подразбиране, и може би числото ще бъде преобразувано в низ и след това в масив от знаци).

Загубата на данни е още по-лесна. Ако преобразуваме реалното число 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), получаваме няколко версии наведнъж - сума за цели числа, сума за реални числа, сума за рационални числа, сума за комплексни числа и дори сума за всички онези числови типове, които сами сте дефинирали.

Разбира се, тази техника спестява само когато се използват смесени изрази с числови литерали и това е само върхът на айсберга.

Така можем да кажем, че най-доброто решение е балансирането на границата между силното и слабото писане. Но нито един език все още не постига перфектния баланс, така че аз клоня повече към силно типизирани езици (като Haskell, Java, C#, Python), а не към слабо типизирани (като C, JavaScript, Lua, PHP).

Явно и имплицитно въвеждане

Изрично въведеният език изисква от програмиста да посочи типовете на всички променливи и функции, които декларира. Английският термин за това е изрично въвеждане.

От друга страна, имплицитно типизираният език ви насърчава да забравите за типовете и да оставите задачата за извеждане на типове на компилатора или интерпретатора. Английският термин за това е имплицитно въвеждане.

Първоначално може да си помислите, че имплицитното въвеждане е еквивалентно на динамично, а явното въвеждане е еквивалентно на статично, но по-късно ще видим, че това не е така.

Има ли предимства за всеки тип и отново, има ли комбинации от тях и има ли езици, които поддържат и двата метода?

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

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

Явно въвеждане по избор

Има езици с имплицитно въвеждане по подразбиране и възможност за указване на типа стойности, ако е необходимо. Преводачът ще изведе реалния тип израз автоматично. Един от тези езици е Haskell, позволете ми да дам прост пример за яснота:

Без изрична спецификация на тип add (x, y) = x + y -- Изрична спецификация на тип add:: (Integer, Integer) -> Integer add (x, y) = x + y

Забележка: Умишлено използвах неизползвана функция и също умишлено написах частен подпис вместо по-общото add:: (Num a) -> a -> a -> a , защото Исках да покажа идеята, без да обяснявам синтаксиса на Haskell.

хм Както виждаме, много е хубаво и кратко. Писането на функция отнема само 18 знака на един ред, включително интервалите!

Автоматичното извеждане на типове обаче е доста сложно нещо и дори в такъв готин език като Haskell понякога се проваля. (пример е ограничението за мономорфизъм)

Има ли езици с явно въвеждане по подразбиране и имплицитно въвеждане, ако е необходимо? Con
разбира се.

Неявно въвеждане по избор

Новият езиков стандарт C++, наречен C++11 (преди наричан C++0x), въведе ключовата дума auto, която позволява на компилатора да изведе типа въз основа на контекста:

Нека сравним: // Ръчно указване на типа unsigned int a = 5; unsigned int b = a + 3; // Автоматичен изход от тип unsigned int a = 5; автоматично b = a + 3;

не е лошо Но записът не е намалял много. Нека да разгледаме пример с итератори (ако не разбирате, не се страхувайте, основното нещо, което трябва да отбележите е, че записът е значително намален благодарение на автоматичния изход):

// Ръчно указване на типа std::vector vec = случаенВектор(30); for (std::vector::const_iterator it = vec.cbegin(); ...) ( ... ) // Автоматично извеждане на типа auto vec = randomVector (30); за (auto it = vec.cbegin(); ...) ( ... )

Уау! Това е съкращението. Добре, но възможно ли е да се направи нещо като Haskell, където връщаният тип зависи от типовете на аргументите?

И отново отговорът е да, благодарение на ключовата дума decltype в комбинация с auto:

// Ръчен тип int divide(int x, int y) ( ... ) // Автоматичен тип извеждане auto divide(int x, int y) -> decltype(x / y) ( ... )

Тази форма на нотиране може да не изглежда много добра, но когато се комбинира с генерично програмиране (шаблони/генерични), имплицитното въвеждане или автоматичното извеждане на типа върши чудеса.

Някои езици за програмиране според тази класификация

Ще дам малък списък с популярни езици и ще напиша как те са разделени във всяка категория „набиране“.

JavaScript - Динамичен / Слаб / Неявен Ruby - Динамичен / Силен / Неявен Python - Динамичен / Силен / Неявен Java - Статичен / Силен / Явен PHP - Динамичен / Слаб / Неявен C - Статичен / Слаб / Явен C++ - Статичен / Полусилен / Явен Perl - Динамичен / Слаб / Неявен Objective-C - Статичен / Слаб / Явен C# - Статичен / Силен / Явен Haskell - Статичен / Силен / Неявен Common Lisp - Динамичен / Силен / Неявен

Може би съм направил грешка някъде, особено с CL, PHP и Obj-C, ако имате различно мнение за някой език, пишете в коментарите.

Силно писане- една от възможностите за работа с типове данни, която се използва в езиците за програмиране.

Строгата типизация предполага изпълнението на следните задължителни условия:

  1. Всеки обект с данни (променлива, константа, израз) в езика винаги има строго определен тип, който е фиксиран по време на компилация на програмата (статично типизиране) или определен по време на изпълнение (динамично типизиране).
  2. Позволено е да се присвои на променлива само стойност, която има точно същия тип данни като променливата; същите ограничения се прилагат за предаване на параметри и връщане на резултати от функции.
  3. Всяка операция изисква параметри от строго определени типове.
  4. Неявното преобразуване на тип не е разрешено (т.е. транслаторът третира всеки опит за използване на стойност от тип, различен от декларирания за променливата, параметъра, функцията или операцията като синтактична грешка).

Ако се спазват стриктно стриктни изисквания за въвеждане, дори типове данни, които са идентични в състава на стойностите и допустимите операции, са несъвместими. Ако една програма трябва да присвои стойността на един тип данни на променлива от друг тип, това може да стане, но само чрез изрично прилагане на специална операция за преобразуване на типа, която в такива случаи обикновено е част от езика за програмиране (въпреки че може формално не е един, а по-скоро предоставен от стандартни библиотеки).

Единственият строго типизиран език за програмиране в практическа употреба е Ada. Доста голям брой общи езици за програмиране използват слабо статично писане. Такива езици включват например Pascal, Modula-2, Java. Те изискват описание на типовете променливи, параметри и функции, но имплицитното преобразуване на типове е разрешено - ако стойност от един тип е присвоена на променлива от друг, компилаторът автоматично генерира код за преобразуване на стойността в желания тип, освен ако такова преобразуване води до загуба на данни. Например, цяло число може да бъде присвоено на променлива, декларирана като число с плаваща запетая, но обратното присвояване без изрично преобразуване на типа е забранено, тъй като най-вероятно ще доведе до грешка. Някои езици, които формално имат концепция за тип данни, всъщност могат да се считат за нетипизирани. Такива езици включват класически C, в който, въпреки че се изискват декларации на типове, в действителност всички типове данни са съвместими с присвояване (модерните C компилатори ограничават тази свобода и издават поне предупреждения за опасни преобразувания на типове).

В теорията на програмирането стриктното типизиране е незаменим елемент за осигуряване на надеждността на разработения софтуер. Когато се използва правилно (което означава, че програмата декларира и използва отделни типове данни за логически несъвместими стойности), тя защитава програмиста от прости, но трудни за откриване грешки, свързани със споделянето на логически несъвместими стойности, понякога възникващи просто от елементарна печатна грешка. Такива грешки се откриват дори на етапа на компилация на програмата, докато когато е възможно имплицитно да се преобразуват почти всякакви типове един в друг (както например в класическия език C), тези грешки се откриват само по време на тестване, а не всички от тях, а не наведнъж. От друга страна, много професионални програмисти не харесват стриктното писане поради неговото неудобство - увеличава размера на програмата и времето, необходимо за написването й, и изисква по-внимателно разработване на кода, което мнозина смятат за ненужно.

Предпоставки

Строгата типизация предполага изпълнението на следните задължителни условия:

  1. Всеки обект с данни (променлива, константа, израз) в езика винаги има строго определен тип, който е фиксиран по време на компилация на програмата (статично типизиране) или определен по време на изпълнение (динамично типизиране).
  2. Позволено е да се присвои на променлива само стойност, която има точно същия тип данни като променливата; същите ограничения се прилагат за предаване на параметри и връщане на резултати от функции.
  3. Всяка операция изисква параметри от строго определени типове.
  4. Неявното преобразуване на тип не е разрешено (т.е. транслаторът третира всеки опит за използване на стойност от тип, различен от декларирания за променливата, параметъра, функцията или операцията като синтактична грешка).

Ако се спазват стриктно стриктни изисквания за въвеждане, дори типове данни, които са идентични в състава на стойностите и допустимите операции, са несъвместими. Ако една програма трябва да присвои стойността на един тип данни на променлива от друг тип, това може да стане, но само чрез изрично прилагане на специална операция за преобразуване на типа, която в такива случаи обикновено е част от езика за програмиране (въпреки че може формално не е един, а по-скоро предоставен от стандартни библиотеки).

Въвеждане на езици за програмиране

Връзки

Вижте също


Фондация Уикимедия.

2010 г.

    Вижте какво е „Силно въвеждане“ в други речници:

    Типът данни е фундаментална концепция в теорията на програмирането. Типът данни дефинира набор от стойности, набор от операции, които могат да бъдат приложени към тези стойности, и евентуално начин за прилагане на съхраняване на стойности и извършване на операции. Всяка... ... Уикипедия

    Въвеждане на данни Безопасност на типа Извод за тип Динамично типизиране Статично типизиране Силно типизиране Меко въвеждане Зависими типове Патешко въвеждане Основна статия: Силно въвеждане Динамичното въвеждане е широко разпространена техника... ... Wikipedia

    Типиране на данни Безопасност на типа Извод за тип Динамично типизиране Статично типизиране Силно типизиране Меко типизиране Зависими типове Патешко въвеждане Основна статия: Силно типизиране Статичното типизиране е широко разпространена техника... ... Wikipedia

    Динамичното типизиране е техника, широко използвана в езиците за програмиране и спецификационните езици, при които променливата се свързва с тип в момента на присвояване на стойност, а не в момента на деклариране на променливата. Така в различни области ... Wikipedia

    Въвеждане на данни Безопасност на типа Извод за тип Динамично типизиране Статично типизиране Силно типизиране Меко типизиране Зависими типове Патешко въвеждане Зависим тип, в компютърните науки и логиката, тип, който зависи от стойност. Зависими... ... Уикипедия

    - (терминът тип данни също се среща) е фундаментална концепция на теорията на програмирането. Типът данни дефинира набор от стойности, набор от операции, които могат да бъдат приложени към такива стойности и евентуално начин за реализиране на съхранение на стойностите и... ... Wikipedia

    Тип данни Съдържание 1 История 2 Определение 3 Необходимостта от използване на типове данни ... Wikipedia

    Този термин има други значения, вижте ML (значения). Семантика на ML: мулти-парадигма: функционална, императивна, модулна Появява се през: 1973 г. Автор(и): Робин Милнър и др. Университет на Единбург ... Уикипедия