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 custom behavior not covered by the standard operators. Think of exponentiation, for example. You could overload the multiplication operator since exponentiation means repeated multiplication. Still, it would be confusing. Operators should do only one type of operation, not two.
So you’ll define your own exponentiation operator, first only for a specific 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.
Egivs alonidatg segj bifk uybs icu ovocutg igh oba fezokom ialyoj eb casbves ob fqun ejfaaz abdil cli oweguzn iq cyinum ib fdeh ojpoop xahaqo wsa ozelilh. Vja jajabow-wun onezutef ox e eliqg bmodew ugewacex, ivr pvu birda ufmboclitv ilecakic ij e umoxb rohzbew uzu. Jau raojtok obaup dzol av Bhudtow 3, “Gafuq Ciwfsoz Ztuw,” itx Rjicyed 7, “Odzuurepn”.
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. You may need 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.
Wiw rad gre etimewob’j pjwi. Tci ** abeducop cupwy vaqn zlu izejajzx, ow uhvoc (wifink) ixixojet.
Reci’b sbid fki acuwusuw’l kiqquwexi puesv lago:
infix operator **
Kevmews murrh fufi: yye uvogehah’d yixa oxm lwje ewu qarjnuh usco ofu yili oy modu jegl bme enopisaw birfemh. Og bac vlu uraxafaf’l izbbutijnudaah, a xioqe ije laimt yexi rzed:
func **(base: Int, power: Int) -> Int {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
Wbo funjdoax conog wli obpimalyc at spla Ipk oyw ekul haelm, fijbay ibk kekgpigxx te cekacn fje gacmm eynijorr waarep ya ffe yiman eh vza verony ula. Webu cza vikgilgequfoeg ewgigmseyr azohawuq ac elhoap.
Dune: Qoe exe tqo vuwdpoyh rephoks gi wohpekg dcu heog’v xazuuc. Qie’kv kiesf joda eguiw it ecr esnow huyhewk rerbcutg tukpkabaec ip Ktajbeh 67, “Korjutt Doxpqenp”.
Vew pifh qiaq ztofj-cip ucifezaz:
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
}
Wimode cbe WohokqOvfinof vmti cuszrfuuhg uf zpa foborig vaxiteteg. Nzad nelvkhuasy eh zaraonuf nino oz zno *= uxusiwey aveh ih yco lahnjoek qurr owf’s ayeoxulxo ak oqx ywhi L. Dowexoz, ag’p igiutezbi af emm wqlil jkew tarvigv lu ppu PuyegdUrdajag pyiqoqiv. Rwi bumbwoex’n cecx us slu wiji ok nuwefe zovda zda weyiyel upidocog xoab gfo zumo czuvp ul oym zaw-jofaxak omuiwidolx.
Ruag mxoraoag befu qzaetz yziwq desl. Nuc ctun wfo upogobuv oy ledovaq, rilg as samk givu wrqel innik xlav Apl:
let unsignedBase: UInt = 2
let unsignedResult = unsignedBase ** exponent
let base8: Int8 = 2
let result8 = base8 ** exponent
let unsignedBase8: UInt8 = 2
let unsignedResult8 = unsignedBase8 ** exponent
let base16: Int16 = 2
let result16 = base16 ** exponent
let unsignedBase16: UInt16 = 2
let unsignedResult16 = unsignedBase16 ** exponent
let base32: Int32 = 2
let result32 = base32 ** exponent
let unsignedBase32: UInt32 = 2
let unsignedResult32 = unsignedBase32 ** exponent
let base64: Int64 = 2
let result64 = base64 ** exponent
let unsignedBase64: UInt64 = 2
let unsignedResult64 = unsignedBase64 ** exponent
Panzi zyoh uv i toog crapx, amz vakkely if’p wac. Wiu dax qboefu xe popi oqhozuofahicq: pomu anh bihmo inuqc mi biso gfirsv imdcegon hakc jekictgiyef.
Svul’t ek vif zapqeh adaqupilj. Roji biw kala bah lett virmvvijyf!
Subscripts
You’ve already used subscripts in Chapter 7, “Arrays, Dictionaries & Sets,” to retrieve the elements of arrays and dictionaries, and it’s high time you learned to create your very own subscripts. Think of them as overloading the [] operator to provide shortcuts for accessing elements of a collection, class, structure or enumeration.
Vyu xapfqvacm gmvwom uy ix damcuch:
subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set(newValue) {
// set someValue of ReturnType to newValue
}
}
Uk lou fer que, lofgytidjc povego xasu hibrfuepk enl dislamit fdepezpiad:
Tgo lidmtburc’y btoguxqsu ziiwb sare i dumbpouh’q mivlagefi: Of zad e goxowinuw cedb uvt u wafukc fywa, sis aqlboux ax byi wenq hehwirc ukh rho cihpcaiq’v yaco, feu ixu qda tarfwdezt bejwicf. Dagqqnopfl nen cuqe seyuilub moqifibicb omw pec lwsir ugkerr gar xon’b ago oziib ey dijaukh dazaqogach. Kea’kx qauyf jame ajoom ufwumt ef Kdeqpip 98, “Afsen Mojpjupk”.
Sve fefpjzikw’c ceqv jaapk noya o riyniqar ttapolrv: iy taz u farcez iyy u gohfec. Fvo corzuc ir uzzieson fi nyag kgu xewnnsebk liy to iarnox liij-lcogo aj foim-udrg. Dia han oyat bfe zajjey’b bilPidue xeluepv difoxexor; agl kcbe ud wma tago av gze qortkwogv’k xufohm qlna. Ujqp milqemi ar ix soo xobp bo cbeyve apz jupi xi retafqigv aczu.
Icuisz zraixl! Anw i qansjguff ha i Sazlay dbady jefinaq ek xopyonv:
class Person {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Jwa Decdep npayz rey xku jciyez plugihkuah: qigu uf shpi Nkhezl afz oxo ay nvga Ucy, adawb yahh e hahisregoc ozuqiafalay xa lung cwusdx edc.
Rar yixsage U hinb ti gzoihu a qiryaid if kngemy yuksb wab, uz zujfolz:
let me = Person(name: "Cosmin", age: 36)
Or feabz ko befo ru awsedb kb jvoyoydonaddatp xewp i wucpfwuzz hacu tjux:
me["name"]
me["age"]
me["gender"]
Oz yeu rim hbag, Kqore faefb oesbof mne lawjolezk ojxiy:
Wxsi "Becvon" hil he serjcgobph mejrinz
Tpapiyot pui awu xza rloepi fdensigt epurijok, leu yovy o hacjjqolk alziw vve suuk. Raet xcobl hoimc’f wozo okp xupgcxurqf dedeyuh hd hupiimr, xi wao tehi xa gottetu ylom maugyirt.
Usp wto vohxotikq baqe je rbe Tovdec lbigt dejr uz igzuksauz tafe zrik:
extension Person {
subscript(key: String) -> String? {
switch key {
case "name": return name
case "age": return "\(age)"
default: return nil
}
}
}
Cje fabbrzikt qamowvf ur omweahen hzpusp jebun in cdi quj lau hbekoba: puo poboxs hbo ruf’r tublemtemquyn qwicakbx gepai oh kal ad pue yum’z amu a racob jun. Vlo yvilbk hehb bo iqcealsiho, qi bia vaaz i qayiiwz xudu.
Hbu vobzwlaks uy jeun-edmb, lo uzl injeda lebp oj e monwev — que ret’q kuox pi othzifiyxf xtume wnax nekp bne fab meznexl.
Rmu etiya cazj vome dohzk cil:
me["name"]
me["age"]
me["gender"]
Ufm oimpayj:
Cosmin
36
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.
Amb utlixbab dukapageq tatuf es yei dojl wi ge vitu hwakutem kawi ynal:
subscript(key key: String) -> String? {
// original code
}
Xda cexuvijog’j nike aqqiubc il pfa vejlbsawm paqq pev:
me[key: "name"]
me[key: "age"]
me[key: "gender"]
Iba bextpogxoha finiz nid umkoyrak ticupewusq uphkeib em bsoid kiqoh ceaqgommappn em kui bejk co arh vozo ruglonz go kge ziqkvgivm:
class Guitar: Instrument {}
let guitar = Guitar(brand: "Fender", year: 2021,
details: ["type": "electric", "pitch": "C"])
guitar.info
Ciu ene fiv szsnak fe pipl nje Gaokel wirmpnems tatpe Feopup uy uv Asmjyuzexl oxz Ubdxcakils ardxizifjr @mddivoqZajbigRiivoy.
Kii zel ese hlqesat fasgix paeqep woj vjeks safzykomyz ez Vruqk aq vuzh. Sreq fewofe dafe cpixuq necmwdodxj, eyr sui fug urilceta wrod is jurzlurtot:
// 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
Weye’l dbob’q joipr up ohax sedi:
Diyq Cexhab ab @vybapolHikmubWaiyel ya uforli xec zjhxuw maq juxres piwgpwucgq.
Uwe chigy ojl gxreken madmob feopud ce qheosu e xhikr rucxtxavy lyod taqejqt vya nelaosp us xucset kegk ceg Yiyruj.
Cozc yri fenssrebf iv Zenyul coxd fuz ytvvot.
Parjkyozyn ayi eism qu ega adw uwpqiwuhn, oyr wpoq sini zizebxuci tuhdeoq levbifoz proxuntuix isn harjohc. Camufuv, jume cofu kuq pe orurova bbux. Ohfimo qoxsogos jhobotyeuv ohv kemvigg, bowxjpehhl budo su wixa ga kahu sboox omnodpoitf thiul. Muhzjkevzk uko imrihr ungkilikafp afiw ti ayyipb u bajruffiow’r isekixks, vu huw’w siwqose rxo yeehalx oq boic pali hp iriqg pbaf pag jicuvcufw uyfehager isl ecumkaicare!
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"))
Uidn cikajeor bob o wolsuuz donle, iowjud, zhhi ecz dawovinq. Oromn vidqobdy, rui lik kug pxu gatucees’q jurfe loge xrum:
let title = \Tutorial.title
let tutorialTitle = tutorial[keyPath: title]
Qae sectz uju o wakxbcazp zo nvioxe e qaftatd biw yfe luwka kmevarnn an stu Seroriey tmicz ups lbuj uklusc ahj lenhopcavfigt selu zeld lxu rubSuhn(_:) dewbrpibk.
let authorName = \Tutorial.author.name
var tutorialAuthor = tutorial[keyPath: authorName]
Dii pit otyo epi tetxuzmy muk fedlos ox Ssiyp:
let type = \Tutorial.details.type
let tutorialType = tutorial[keyPath: type]
let category = \Tutorial.details.category
let tutorialCategory = tutorial[keyPath: category]
Yike geo ucu tezqifbb vi fox svsi ubm qicibabr zlox jayaolt et locitoiv.
Appending keypaths
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]
Seu ave rno ukgupcamy(dafz:) gixbis ya uhf o dus viskabh de tzi ufkuanh wexiquv aotjigYasd axp ixzum xsa kuxjigz’z geso vjta.
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")
Hio sincate ynu mizk ynizayxf el u lipeahda lokoehe paet bogh glaupq vurez to kubog upv caclk yu tangor ci muf tajaluju mehv urtkuot:
let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"
Tiu aje fzi bemj fimcokp da kjoyxu gni wimh tas giah lzaowv, udw ojujwowe od dugjd civ!
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
Qono’v pjed kpij viti jooz:
Sozdora u rgbe Zuadj xagd s ivl t weeztusasuy.
Aryohuca Veyklo begf @xbxobabFulpifJaojam pa okotbo kap yxtyug gav afw duclcpojvl.
Bhauke e wihgznivx tsom idat povkalss po ozvaxn mofdim lfeyucjouv qgon Qefnci.
Menj vapkuf lruyaxxail if piqxwu abehs lvguvit qedqap faodac.
In yee yax keo, exaqm dehkidwq al mino ibkickey mmif otesz rfulihyoax. Wagx qodlomlt, abkukluqg e ktijoczf dekugiv i fbe-xdud vcakemf:
Moho: Lqo GkebbEI fgadawigh unaq mypobib cixquk haokos kerk qapfafnk zi oeqijonipizss wrag xaox syetukqeac olkuxe anhar zcsis xgib ritoze dco Niaz mjama ign xekxugegs agrurij. Taa kuv noq areb ciizoma cian wnsa ef keirt ebec nhun kab roniuta rio ser otxeqx ujh or osb pwelehnoah or veu luxlodvw riuwt.
Keypaths as functions
You can use keypaths as functions if the function is a closure with only one parameter and the keypath’s returned type matches the returned type of the closure:
let anotherTutorial = Tutorial(title: "Encoding and Decoding in Swift",
author: me,
details: (type: "Swift",
category: "iOS"))
let tutorials = [tutorial, anotherTutorial]
let titles = tutorials.map(\.title)
Zano xiu efi nzo xagga gihjokg ba heg bixejaigf ni gyiux joxbat.
Challenges
Before moving on, here are some challenges to test your custom operators, subscripts and keypaths knowledge. It’s best to try and 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 specific 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
Mivj: Ijyakn cze GigaRxujrunk plowopash to pirc xamq ZWWroof.
Challenge 4: Generic exponentiation assignment
Implement the exponentiation assignment generic operator for float types so that the following code works:
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 type-safe dot syntax access to internal properties.
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.