Кратко въведение в типовете оператори, как да ги използвате и техните недостатъци

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

Какво представляват персонализираните оператори?

Swift ви позволява да дефинирате свои собствени оператори освен предварително дефинирания набор от оператори, като ==, +, - и т.н. Има три различни типа: префикс, постфикс и infix.

Префикс и постфикс

Както подсказват имената им, тези оператори или предшестват, или следват съответните си цели, като операторите за увеличаване/намаляване ++/--. Следователно те са „унарни“ оператори, засягащи една стойност.

За да декларираме префикс или постфикс оператор, трябва да създадем static функция с правилния модификатор, prefix или postfix, в разширение на целевия тип:

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

Поставянето на логиката на оператора в разширение всъщност не е необходимо. Глобално наличен func също би бил добре. Но операторите в разширение за съответния им тип са чист начин да ги групирате логически.

Infix

Като „двоичен“ оператор, третият вид работи между своите цели и има достъп и до двете, като аритметичните оператори +, -, *, / и т.н.

Те се дефинират много подобно на операторите infix/prefix:

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

Деклариране на оператора

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

Редът на операциите, известен също като „предимство на оператори“, определя правилата между различните оператори в изразите. Например изразът 2 + 3 * 4 ще доведе до 14, защото изразът е еквивалентен на 2 + (3 * 4) поради приоритета на оператора.

Асоциативността определя как операторите с еднакъв приоритет трябва да се държат, ако се използват заедно. В зависимост от нашата логика може да има разлика за израза a !! b !! c, когато се оценява (a !! b) !! c в сравнение с a !! (b !! c). Ето защо можем да дефинираме как и дори дали множество оператори могат да бъдат свързани заедно.

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

Групите са сортирани „от най-високо към най-ниско“. Всички имена на групи завършват с Precedence, което е пропуснато за четливост.

| Group              | Assoc | Example operators   
| ------------------ | ----- | --------------------------- 
| BitwiseShift       | none  | <<  >>                   
| Multiplication     | left  | *  /                     
| Addition           | left  | +  -                     
| RangeFormation     | none  | ...  ..<                 
| Casting            | none  | is  as  as?  as!       
| NilCoalescing      | right | ??                        
| Comparison         | none  | !=  ==  <  >  <=  >= 
| LogicalConjunction | left  | &&                        
| LogicalDisjunction | left  | ||                      
| Default            | none  |                             
| Ternary            | right | ?  :                       
| Assignment         | right | =  +=  -=`               

Персонализирана група за приоритет ни позволява да позиционираме нашите оператори между вече съществуващите:

Предшестващата позиция и асоциативността вече бяха споменати в статията. Но какво е свойството assignment?

Както е написано в Предложението Swift SE-0077, assignment: true ще сгъне оператора в опционална верига, позволявайки на foo?.bar += 2 да работи като foo?(.bar += 2) вместо да откаже проверката на типа като (foo?.bar) += 2.

Правилното постигане на тези свойства на оператора може да се нуждае от „проба-грешка“ или дори по-добре, набор от тестове на единица, за да се определи точното поведение, особено ако те са част от споделена рамка.

Претоварване на оператори

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

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

Недостатъците на персонализираните оператори

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

Както при разширенията, ние въвеждаме технически дълг в нашите проекти под формата на нов, нестандартен оператор. Дори по-лошо, може да е познат оператор, който се използва повторно в нов контекст. Разработчиците на борда може да не разберат какво се случва и да трябва да потърсят документация или самата декларация на оператора. Това ще изисква определен умствен капацитет за разбиране на „мини-езика“ върху самия Swift. И не можете да щракнете с CMD върху оператора, за да намерите неговата дефиниция, както можете да го направите с метод за разширение.

Например, създадох библиотека, за да опростя създаването на AutoLayout код с разширения и персонализирани оператори:

Първоначално създадох няколко разширения, за да ме спасят от въвеждането на constraint( или constant: отново и отново. Кодът е по-четлив и кратък и не е необходимо обяснение какво се случва. Цялата необходима информация все още е там.

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

Apple не се опитва наистина много да не разбива Xcode, Swift или iOS SDK с всяка версия... Дори без персонализирани оператори или разширения, нашият код може да се повреди неочаквано. Но чрез въвеждането на такива подобрения, ние увеличаваме възможната повърхност за счупване. Така че освен нашия „нормален“ код, ние също трябва да поддържаме „поддържащия“ код за продължителността на живота на проекта. И връщането към „неразширен“ код може да не е толкова просто като „търсене и замяна“.

Заключение

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

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

Ресурси