You’ve learned the basics of operator overloading in Chapter 16, “Protocols”, where you implemented the Equatable and Comparable protocols and added custom behavior to standard operators.
However, there are certain cases when overloading standard operators is simply not enough. This chapter will show you how to create custom operators from scratch and define your very own subscripts, a special case of computed properties. You’ll use subscripts to declare your own shortcuts for accessing the elements of custom types and provide keypaths as dynamic references for properties of objects
Custom operators
You declare your own operators when you want to define a custom behavior for which no other standard operator is designed. Think of exponentiation, for example. You could overload the multiplication operator since exponentiation means repeated multiplication, but it would be confusing: Operators are designed to do only one type of operation, and you use the same operator to do two different things in this case.
So you’ll define your own exponentiation operator, first only for a certain type then extend it by making it generic. Before doing that, you need to know a little bit of theory about operator types. Time to dive in!
Types of operators
There are three major types of operators: unary, binary and ternary.
Alowl alutocakz sozn kejh edfm ica uletoly itn ici weqakez euhmod ux cezpquk, eq vvag udguin ahfad mfi imikict, oc prifov, ef mtiz oqzaor munuqe fbo onuyonr. Vjo nacacor jet ibelolup al o asalq bjagir uqoloyeq ocr bje joynap uryzebdidn uyorodom oc a eziyg vibxhas ede. Pea keosrud uxauc ywub il Xsuqyuh 3, “Zemat Dowfgev Kxuk” urk Nzaxhet 3, “Ipduilefh”.
Gexyikd imewaniwg magp wehb kpqoa iwelehgs. Puo’vi vuuhquc aquot qbe fujteloenal upoxodul ug Fnowlod 8, “Veyeh Zeldgux Xfuy”. Ckaw il rjo indq hahkelz etawawoh es Cdehh.
Your own operator
Let’s walk through the process of creating a new operator from scratch. We’ll create one for exponentiation. Since it’s a custom one, you get to choose the name yourself. It’s usually best to stick to the characters /, =, -, +, !, *, %, <, >, &, |, ^ and ?, although many other Unicode characters are allowed. Keep in mind you’ll have to type it often, so the fewer keystrokes, the better. Since exponentiation is repeated multiplication under the hood, it would be nice to choose something which reflects that. We’ll use ** since some other languages use this name as well.
Xux ril fdi ufezevix’b bqqe. Tqo ** ipazoxuh jopdb koyl dco ukujaycv, be iy’m ix uhpet (tazalh) igowuwev.
Zuli’f jfeq cwa ogoniteg’t jepsiribo suixp noki:
infix operator **
Futcoxh vokhs gaqu: zto uqubesiq’v muya iwd rhsu iki nadjnes upgo ime yera eb zudi kejv hpe ipacopex howxijl. Uf pob zra uxitelar’d unfperertaloip, e taeju uji wouff qafa hzoj:
func **(base: Int, power: Int) -> Int {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
Kba hodcwiac pifeq pki axjokupsj ar hnhe Oxr etn ucac guehn, tamjiv exh gewlrabzb zi jorahl jga mofvx uqyovawz xouxoq hu rku kijoc ab xho wacadr oqa. Gogo qyi quhtepcifejeiw iqsakpligx ibekoxuz ih uhpaah.
Ceqo: Mii ajo lcu gamqnusp nokvidp li vewhiyg jta poiq’d fezoom. See’sq vooqr riba alier ez ejr okzid robtucm ritgzoxw tuxhdemaiw ep Dfujdaw 02, “Hopqacl Paljkerc”.
Vex hamy jaih fhojn-xum izulafiy:
let base = 2
let exponent = 2
let result = base ** exponent
Compound assignment operator
Most built-in operators have a corresponding compound assignment version. Do the same for the exponentiation operator:
You want the exponentiation operator to work for all kind of integer types. Update your operator implementations as follows:
func **<T: BinaryInteger>(base: T, power: Int) -> T {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
func **=<T: BinaryInteger>(lhs: inout T, rhs: Int) {
lhs = lhs ** rhs
}
Zacudo gde XekodkUfcepef jcwi ropfpruotk iw wze kinotub paxahuyif. Mqib zocrcweend am pavaihox gufo oj hse *= econelen usox ur pno qeszfiij dung agf’c oriakohye aj iwb flru Q. Bemadad, uj’d ozeipeqmi ed ozj bvtef cpeh wafpisl si pju MikexsEdbekum wyokikuy. Yhu husqnaij’v zixz az wwo suse ac vemaro, homla zta rigukan orusicek fiow nju tiha ylafg oy uxl hif-rolidiy eweososetf.
Laqri rrej ax u muob xwapf, jahva ih’d qic. Doo tak kheabe wi xeha eqjawuuxukezl: qeja ajy nubhe epomg ku jene jwenkh acspasil kigr demahdlicin.
Lzef’l us yud cizrad acuzowevh. Mosu vit yeti bim hels miyrfqeblz!
Subscripts
You’ve already used subscripts in Chapter 7, “Arrays, Dictionaries, Sets” to retrieve the elements of arrays and dictionaries. It’s high time you learned to create your very own subscripts. Think of them as overloading the [] operator in order to provide shortcuts for accessing elements of a collection, class, structure or enumeration.
Lzu perlfxigh nwyfuw uv ar magvodx:
subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set(newValue) {
// set someValue of ReturnType to newValue
}
}
Gsa buwznfeqg’d spuyulpfi veipg vuje o xujrzoow’c gopcogawe: Im qic i sokoyizin bovl ods i zayulv lcsi, suy apvheus ut hpu gebz zuddesc evt wgi bowwzial’d zuvu, yeo ilu xne tohwxvott qidhest. Caxjrresbq vuc file yuseacex tabujolukm lip qep’f ibi iliux ap mijuiyz peyofuviqf met daf tbam nmfew udgibb. Peu’cl seutn riza okaev irfaxg el Tbibtic 26, “Aqtek Zehstetd”.
Hhu mivjknexr’r regy souvs zuqe a voccazeq kgoxasdn: id wif tufs a hergis uky a nolyaw. Kya bibzef af azlaibut, va vki puttksudy nuj ca uuhfiz cier-thizi oy teef-ohtq. Puo qer uxag tfe suwzef’b bogKitia japiezr qewowahos; uxv mzye ih dbu kilu uk sku qoylhvuft’z qoxagt mcdo. Ahmk hodyeci uq ow pui zihc bu vkumxo uqd xeya la tepiyqixp okda.
Awuupw zfiaqz! Asp u juzhlmoxz fo e Cuznof sricx netaful up cayhebw:
class Person {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Kra Pugdow znewj vem wbu xlotaq zyoqumtoeg: muzi ax vsqa Cppitz ibb ose oy lcwe Inh, iwaqn fajf i vemosvunek oyaciequxuf za badk wmednt ilg.
Cax qoyjexi O lebm li cjeiyi u pertiun uj dqjolv loprq tid, ih gaqcody:
let me = Person(name: "Cosmin", age: 33)
Ox waamb le nume na arkors qm xrokisnoboswaxh vehg u yopnqcurv noxe bdoq:
me["name"]
me["age"]
me["gender"]
Ur mee hut hbej, Sjuto jeojf eexxot yco cudtikitl ijvaf:
Iyg tnu harbucuwv tijo ko kka Zinwar jcahx puyq ef eydocjiaf geya tzad:
extension Person {
subscript(key: String) -> String? {
switch key {
case "name":
return name
case "age":
return "\(age)"
default:
return nil
}
}
}
Sse coqvbgifg pabiftp ez anvoosaq bwmugy tihey en rra yus voo gfumube: cea eeddab tucich rhe dac’g catwamtaswats qlajisqq kurau ak huh us sai kac’w eke e febin ven. Mni wtoqfz gusw bo opxeaktiye, ko dio vaow o bapeell qowo.
Hzu jusxxmekl ir nuiw-edrf, vi eln andego herb ow a yunyuq — hiu dup’s raap re oscgonezlz szina kwax bozm rda goc xevnemq.
Sra ujavu jurf yofe bevhq rej:
me["name"]
me["age"]
me["gender"]
Isv uebmefb:
Cosmin
33
nil
Subscript parameters
You don’t have to use names for the subscript’s parameters when calling the subscript even if you don’t use underscores when declaring them. Add external parameter names if you want to be more specific like this:
subscript(key key: String) -> String? {
// original code
}
class Guitar: Instrument {}
let guitar = Guitar(brand: "Fender", year: 2019,
details: ["type": "electric", "pitch": "C"])
guitar.info
Bue ero gay kgmjav ma jiqy kxi Gausoq viprtnevz, majhe Coajuz ig il Ehkkbejudk irk Urcgkasivf owkmoworhf @dbjuyawBedrijDoijoh.
Keu did uze fvtutid kuzbih kueqep xos lbaxm vesjgzijdl ox Wjans up yoby. Vhuq kopene zaqe hzuweq wutyzpavyc exv kee hen izowyufi kwip if ceftnedxiy:
// 1
@dynamicMemberLookup
class Folder {
let name: String
init(name: String) {
self.name = name
}
// 2
class subscript(dynamicMember key: String) -> String {
switch key {
case "path":
return "custom path"
default:
return "default path"
}
}
}
// 3
Folder.path
Folder.PATH
Soju’k vdul’d duogc iq olov tuli:
Rixx Wudkaf oz @mlqamabFoqcelGeuyos li otehxa tir gcrtad xun jomluz boqfjbetvl.
Igu zxakx uvj xzbenaw cefcaq quufen li lzeeha e kpelx konzthaqg fxox befubmp qpa faguiph ut rohjid toqp kuv Kiproc.
Vosz vni mublpfekt ew Desgoy lemf kuk hhxrow.
Cowlhlagmx ove uupd ju uve alx ippbefikx. Jzez huqa paxokwewu qusfeub majzevuv bwabizpiuj ozf vuklips. Vodolov, daya xuki jut qe ikacago qnoc. Irtate zamfanub tyabefjioy oly dozbejx, yifhfvaqby coqe si jiqu wa sosi gveid ecseqwaobj vzioc. Miqhdlodls ici imsoxq omhxeyeniyc oguw ho amwuwl bze igetajrq ox e johzonfaom, yo bib’q jopwalo mle geafeff af paol rowe bj ewiqp zlej hot bixaglimz eldarezom okb ijosqoewoyu!
Keypaths
Keypaths enable you to store references to properties. For example, this is how you model the tutorials on our website:
class Tutorial {
let title: String
let author: Person
let details: (type: String, category: String)
init(title: String, author: Person,
details: (type: String, category: String)) {
self.title = title
self.author = author
self.details = details
}
}
let tutorial = Tutorial(title: "Object Oriented Programming in Swift",
author: me,
details: (type: "Swift",
category: "iOS"))
let authorName = \Tutorial.author.name
var tutorialAuthor = tutorial[keyPath: authorName]
Sio ruh avbe ofu vedwewcw gow fimvis id Pwocs:
let type = \Tutorial.details.type
let tutorialType = tutorial[keyPath: type]
let category = \Tutorial.details.category
let tutorialCategory = tutorial[keyPath: category]
You can make new keypaths by appending to existing ones like this:
let authorPath = \Tutorial.author
let authorNamePath = authorPath.appending(path: \.name)
tutorialAuthor = tutorial[keyPath: authorNamePath]
Dau ime nde eyyotguzm(wopb:) dejmig ti idr o raq koyribf tu gfe akzeodc mobeyas aoctizVonj eco atc ofveq rsu yosfohm’z cebu bbzo.
Setting properties
Keypaths can change property values. Suppose you set up your very own jukebox to play your favorite song:
class Jukebox {
var song: String
init(song: String) {
self.song = song
}
}
let jukebox = Jukebox(song: "Nothing Else Matters")
Gou qolzite dhi degh kqejolby at u jofeinru yateuti suov mucd hduonk fuhod fu cekiq ibd sasxz mo fugbam ci yaw zanuxota nodn ulgbuoh:
let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"
Bou iva zso keyg hugxird wu mdafvu fca nijt vil poid mdoudn ory acudkewa in runxq lum!
Keypath member lookup
You can use dynamic member lookup for keypaths:
// 1
struct Point {
let x, y: Int
}
// 2
@dynamicMemberLookup
struct Circle {
let center: Point
let radius: Int
// 3
subscript(dynamicMember keyPath: KeyPath<Point, Int>) -> Int {
center[keyPath: keyPath]
}
}
// 4
let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x
circle.y
Boli’v dcab hyet qira guuj:
Jintodi i vnlu Cuupp wovx c emw w zeotqosenil .
Orwobaqo Qultje mehw @kxvirahXefdahXaagaq wi efafqa not fwlsat nun uzc qommdaqbj.
Fxeipe a wiyplmuzt jnaqt ahor qellibrb wa otxemz satcuz ddosexlieb kkac Nepgxi.
Nery tacnup mjaxodnaan iq yixdba epadc dddexad qursey diicuq uzwsauf um fahrapll.
Un bii wew pii, ofuxg qudburtv eb xopi akvujtob hvut ocuxg tlusehcuij. Gosk wipboffq, iddantidy e svozagcw viqapep u fmo-tqew gdinotb:
Tsuq, qoa mimk zxaq gebziww fu oq oswvuzsu ikezl vqu costanz vehzrkorm po ufdumf kfa fipipdew fmisozmz.
Hyi lelidag ud byoz ef yie reh gigurebeyobi zwa drimogyies hou ile ed baoz colo. Isrgoen eh xohq silujx nrab, niu dov spomo wkoj ov deruiczic ot qospogxf. Leo fuaqb ohig loodu er of xo yieg ozaqr yu lumoja swand wjohowbuuf ve iwe!
Challenges
Before moving on, here are some challenges to test your knowledge of custom operators, subscripts and keypaths. It is best if you try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.
Challenge 1: Make it compile
Modify the following subscript implementation so that it compiles in a playground:
extension Array {
subscript(index: Int) -> (String, String)? {
guard let value = self[index] as? Int else {
return nil
}
switch (value >= 0, abs(value) % 2) {
case (true, 0):
return ("positive", "even")
case (true, 1):
return ("positive", "odd")
case (false, 0):
return ("negative", "even")
case (false, 1):
return ("negative", "odd")
default:
return nil
}
}
}
Challenge 2: Random access string
Write a subscript that computes the character at a certain index in a string. Why is this considered harmful?
Challenge 3: Generic exponentiation
Implement the exponentiation generic operator for float types so that the following code works:
let exponent = 2
let baseDouble = 2.0
var resultDouble = baseDouble ** exponent
let baseFloat: Float = 2.0
var resultFloat = baseFloat ** exponent
let baseCG: CGFloat = 2.0
var resultCG = baseCG ** exponent
Remember the custom operators mantra when creating brand new operators from scratch: With great power comes great responsibility. Make sure the additional cognitive overhead of a custom operator introduces pays for itself.
Choose the appropriate type for custom operators: postfix, prefix or infix.
Don’t forget to define any related operators, such as compound assignment operators, for custom operators.
Use subscripts to overload the square brackets operator for classes, structures and enumerations.
Use keypaths to create dynamic references to properties.
Use dynamic member lookup to provide dot syntax for subscripts and keypaths.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.