Зашто користити статичке типове у ЈаваСцрипт-у? (4-делни пример о статичном куцању помоћу протока)

Као програмер ЈаваСцрипт-а, можете кодирати по цео дан без наилажења на статичке типове. Па зашто се мучити са учењем о њима?

Па испоставило се да типови учења нису само вежба за ширење ума. Ако сте спремни да уложите мало времена у учење о предностима, недостацима и случајевима коришћења статичких типова, то би вам могло неизмерно помоћи у програмирању.

Заинтересовани? Па, имате среће - о томе говори остатак ове четвороделне серије.

Прво, дефиниција

Најбржи начин за разумевање статичких типова је њихово супротстављање динамичким типовима. Језик са статичким типовима назива се статички уписан језик . С друге стране, језик са динамичким типовима назива се динамички уписаним језиком.

Суштинска разлика је у томе што статички откуцани језици врше проверу типа у време компајлирања , док динамички укуцани језици врше проверу типа током извршавања .

Ово вам оставља још један концепт да се позабавите: шта значи „ провера типова“ ?

Да бисмо објаснили, погледајмо типове у Јави у односу на Јавасцрипт.

„Типови“ се односе на врсту података који се дефинишу.

На пример, у Јави ако дефинишете booleanкао:

boolean result = true;

Ово има исправан тип, јер се booleanнапомена подудара са задатом вредношћу result, за разлику од целог броја или било чега другог.

С друге стране, ако сте покушали да изјавите:

boolean result = 123;

… Ово не би успело да се компајлира јер има нетачан тип. Изричито се означава resultкао а boolean, али га затим дефинише као цео број 123.

ЈаваСцрипт и други динамички куцани језици имају другачији приступ, омогућавајући контексту да утврди који се тип података дефинише:

var result = true;

Укратко: статички откуцани језици захтевају да декларишете типове података својих конструкција пре него што их можете користити. Динамички уписани језици не. ЈаваСцрипт подразумева тип података, док Јава то директно наводи.

Као што видите, типови вам омогућавају да наведете инваријанте програма или логичке тврдње и услове под којима ће се програм извршавати.

Провера типова проверава и намеће да се тип конструкције (константа, логичка вредност, број, променљива, низ, објекат) подудара са инваријантом који сте навели. Можете, на пример, навести да „ова функција увек враћа низ“. Када се програм покрене, можете сигурно претпоставити да ће вратити низ.

Разлике између статичке провере типа и динамичке провере типа су најважније када дође до грешке у типу. У статички откуцаном језику, грешке у типу се јављају током корака компајлирања, односно у време компајлирања. У језицима који се динамички куцају, грешке се јављају само када се програм изврши. Односно, за време извођења .

То значи да програм написан на динамички откуцаном језику (попут ЈаваСцрипт-а или Питхон-а) може да се компајлира чак и ако садржи грешке у типу које би иначе спречавале правилно извршавање скрипте.

С друге стране, ако програм написан на статички откуцаном језику (попут Сцале или Ц ++) садржи грешке у типовима, неће успети да се компајлира док грешке не буду отклоњене.

Нова ера ЈаваСцрипт-а

Будући да је ЈаваСцрипт динамички куцани језик, можете се бавити декларисањем променљивих, функција, објеката и било чега другог без декларирања типа.

Погодно, али не увек идеално. Због тога су недавно ускочили алати попут Флов и ТипеСцрипт који програмерима Јава-а дају * опцију * да користе статичке типове.

Флов је библиотека за проверу статичког типа отвореног кода коју је развио и објавио Фацебоок и омогућава вам поступно додавање типова вашем ЈаваСцрипт коду.

С друге стране, ТипеСцрипт је суперсет који се компајлира до ЈаваСцрипт-а - иако делује готово као нови језик са статичким типом. Упркос томе, изгледа и осећа се врло слично ЈаваСцрипт-у и није га тешко покупити.

У оба случаја, када желите да користите типове, изричито кажете алату о томе које датотеке треба проверити. За ТипеСцрипт то радите писањем датотека са .tsнаставком уместо .js. За Флов укључујете коментар на врху датотеке са@flow

Једном када се изјасните да желите да проверите датотеку, моћи ћете да користите њихову синтаксу за дефинисање типова. Једну разлику између ова два алата треба разликовати у томе што је Флов врста „проверавача“, а не компајлер. С друге стране, ТипеСцрипт је компајлер.

Искрено верујем да алати попут Флов и ТипеСцрипт представљају генерацијски помак и напредак за ЈаваСцрипт.

Лично сам толико тога научио користећи свакодневне типове. Због тога се надам да ћете ми се придружити на овом кратком и слатком путовању у статичке типове.

Остатак овог 4-делног поста покриваће:

Део И. Кратки увод у синтаксу и језик протока

Делови ИИ и ИИИ. Предности и недостаци статичких типова (са детаљним примерима за пролаз)

Део ИВ. Да ли бисте требали користити статичке типове у ЈаваСцрипт-у или не?

Имајте на уму да сам у примерима у овом посту изабрао Флов уместо ТипеСцрипт-а због свог познавања. За своје потребе, истражите и одаберите прави алат за вас. ТипеСцрипт је такође фантастичан.

Без даљег одлагања, почнимо!

1. део: Кратки увод у синтаксу и језик протока

Да бисте разумели предности и недостатке статичких типова, прво бисте требали стећи основно разумевање синтаксе за статичке типове користећи Флов. Ако никада пре нисте радили на статички откуцаном језику, синтакси ће можда требати мало времена да се навикнете.

Почнимо са истраживањем како додати типове ЈаваСцрипт примитивима, као и конструкције попут низова, објеката, функција итд.

боолеан

Ово описује boolean(тачну или нетачну) вредност у ЈаваСцрипт-у.

Приметите да када желите да наведете тип, синтакса коју користите је:

број

Ово описује ИЕЕЕ 754 број са покретном тачком. За разлику од многих других програмских језика, ЈаваСцрипт не дефинише различите врсте бројева (попут целих бројева, кратких, дугих и покретних зареза). Уместо тога, бројеви се увек чувају као бројеви са помичном тачком двоструке прецизности. Стога вам је потребан само један тип броја да бисте дефинисали било који број.

numberукључује Infinityи NaN.

низ

Ово описује низ.

нула

Ово описује nullтип података у ЈаваСцрипт-у.

празнина

Ово описује undefinedтип података у ЈаваСцрипт-у.

Имајте на уму да nullи undefinedсе третирају другачије. Ако сте покушали:

Ток би бацио грешку јер би тип voidтребало да буде типа undefinedкоји није исти као тип null.

Арраи

Описује ЈаваСцрипт низ. Користите синтаксу Array<; Т> да бисте описали низ чији су елементи неког типа Т.

Приметите како сам заменио Tса string, што значи да се декларишем messagesкао низ низова.

Предмет

Ово описује ЈаваСцрипт објекат. Постоји неколико различитих начина за додавање врста објектима.

Можете додати врсте да бисте описали облик предмета:

Објекте бисте могли дефинисати као мапе где мапирате низ у неку вредност:

Такође можете дефинисати објекат као Objectтип:

Овај последњи приступ омогућава нам да поставимо било који кључ и вредност на вашем објекту без ограничења, тако да он заправо не додаје велику вредност што се тиче провере типа.

било који

Ово може представљати буквално било који тип. anyТип је ефикасно неконтролисано, тако да треба да покуша да избегне да га користите уколико није апсолутно неопходно (као када треба да одустану од типа провере или је потребан излаз за случај опасности).

Једна ситуација за коју бисте могли сматрати да је anyкорисна је када користите спољну библиотеку која проширује прототипове другог система (попут Објецт.прототипе).

На пример, ако користите библиотеку која проширује Објецт.прототипе са doSomethingсвојством:

Можете добити грешку:

Да бисте ово заобишли, можете да користите any:

Функције

Најчешћи начин додавања типова функцијама је додавање типова улазним аргументима и (када је релевантно) повратне вредности:

Можете чак и да додате типове функцијама за асинхронизацију (погледајте доле) и генераторима:

Приметите како је наш други параметар getPurchaseLimitозначен као функција која враћа а Promise. И amountExceedsPurchaseLimitбележи се као враћање а Promise.

Укуцајте псеудоним

Алиасинг типова је један од мојих омиљених начина коришћења статичких типова. Омогућавају вам употребу постојећих типова (број, низ итд.) За састављање нових типова:

Изнад, креирао сам нови тип под називом PaymentMethodкоји има својства која се састоје од numberи stringтипова.

Сада, ако желите да користите PaymentMethodтип, можете да урадите:

Такође можете створити псеудониме типа за било који примитив тако што ћете основни тип умотати у други тип. На пример, ако желите да укуцате псеудоним а Nameи EmailAddress:

Радећи ово, указујете на то Nameи Emailна различите ствари, а не само на жице. Будући да име и е-пошта заправо нису заменљиви, ово их спречава да их случајно не помешате.

Генерички

Генерички лек је начин апстраховања над самим типовима. Шта ово значи?

Хајде да погледамо:

Направио сам апстракцију за тип T. Сада можете да користите било који тип који желите да представите T. Јер numberT, Tбио је типа number. У међувремену, за arrayT, Т је био типаArray er>.

Yes, I know. It’s dizzying stuff if this is the first time you’re looking at types. I promise the “gentle” intro is almost over!

Maybe

Maybe type allows us to type annotate a potentially null or undefined value. They have the type T|void|null for some type T, meaning it is either type T or it is undefined or null. To define a maybe type, you put a question mark in front of the type definition:

Here I’m saying that message is either a string, or it’s null or undefined.

You can also use maybe to indicate that an object property will be either of some type T or undefined:

By putting the ? next to the property name for middleInitial, you can indicate that this field is optional.

Disjoint unions

This is another powerful way to model data. Disjoint unions are useful when you have a program that needs to deal with different kinds of data all at once. In other words, the shape of the data can be different based on the situation.

Extending on the PaymentMethod type from our earlier generics example, let’s say that you have an app where users can have one of three types of payment methods. In this case, you can do something like:

Then you can define your PaymentMethod type as a disjoint union with three cases.

Payment method now can only ever be one of these three shapes. The property type is the property that makes the union type “disjoint”.

You’ll see more practical examples of disjoint union types later in part II.

All right, almost done. There are a couple other features of Flow worth mentioning before concluding this intro:

1) Type inference: Flow uses type inference where possible. Type inference kicks in when the type checker can automatically deduce the data type of an expression. This helps avoid excessive annotation.

For example, you can write:

Even though this Class doesn’t have types, Flow can adequately type check it:

Here I’ve tried to define area as a string, but in the Rectangle class definition we defined width and height as numbers. So based on the function definition for area, it must be return a number. Even though I didn’t explicitly define types for the area function, Flow caught the error.

One thing to note is that the Flow maintainers recommend that if you were exporting this class definition, you’d want to add explicit type definitions to make it easier to find the cause of errors when the class is not used in a local context.

2) Dynamic type tests: What this basically means is that Flow has logic to determine what the the type of a value will be at runtime and so is able to use that knowledge when performing static analysis. They become useful in situations like when Flow throws an error but you need to convince flow that what you’re doing is right.

I won’t go into too much detail because it’s more of an advanced feature that I hope to write about separately, but if you want to learn more, it’s worth reading through the docs.

We’re done with syntax

We covered a lot of ground in one section! I hope this high-level overview has been helpful and manageable. If you’re curious to go deeper, I encourage you to dive into the well-written docs and explore.

With syntax out of the way, let’s finally get to the fun part: exploring the advantages and disadvantages of using types!

Next up: Part 2 & 3.