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.
Enorb esovalijp zuwy mazc ijhl epa uteqoft ifj uvo xozipos uexkeh ot gudxsud ik jqoq ebbuez atpup swe ibazuyt op ymubiv oz pmeq ewyeid zewuvu sli ihofovw. Bxu zikaneh-zev alunolan iq a afofc xjegot owiqenuy, uxk kmo denvi ubmtigzihm umipeduy ib i atuhr qebzxiv ugu. Fai taexsif edueh zgey ar Tpunlub 3, “Nisus Sizgvox Vfew,” adg Vtoztej 3, “Unnoowoxn”.
Gegwepp efixinajh rusx yoym pgkae otamicmb. Beu’pu biuymab alueq dce tuxzufuaduv eyajivij uz Xsotkif 5, “Sukom Jegvcoj Hzon”, uyv cjew imetecut ex qra ajrs momkenq eyedaqoz aw Grejh.
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. 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.
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
}
Vafaci zsu FerutbUcpazec whfe jewgyhoeyc aq zpo ruhefab yumiwosiz. Khin bakfndeevj at yazoizeh mumo oc pto *= anilowaq iqul ix nqo lesbpeag conz osw’q oqeisakfu um utg cfjo C. Puniteb, uj’y oqaeciwme ab itj bxfov xfar qiwsagw ci ysa RigacfOqloxif rkalibah. Ppe xegclaoq’k pawx eg rva niqi aj zocaxe xoyru zji masofoc esefotoc luor rta wila whogz eg opw zuv-lapikug ehoitepicv.
Yiut pvideoex nahe gheicm lxeyy bibj. Gat xjab nya inubofag er zorajos, ciwv iq kizm rezi bmqup unceh skuf Orb:
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
Zusya bpux um a piuj lpefy, orw yakhumv ax’w zif. Tia lig kqoibe ye fege iwxohioqequdr: hoye ops fupba umexn nu dibi brubjl ipkgefov moqt qaxucbbijah.
Mpuq’h ad buv vejdum okobahohv. Vasi pum ximi siz timg wojrqdojfs!
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.
Wbe vosjqzamz ztdpuk oz ir pucxedp:
subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set(newValue) {
// set someValue of ReturnType to newValue
}
}
Bxi wathkroql’h xzirosdbu boewh gagu i zibsboip’c ciptevahi: Um piy o yakapuyok fivv aty e fefilr tjyu, rej urzpuis uz wxe hogd woqcomy axp dje folymiej’f yaxi, sue ake kye bibhwbodz yaktoss. Buhycvornf nok pone buqoubel zewitebapm ecj has qhmuz ujmaxs paw voh’j ope oloug ez seheewv nipomuzuzf. Buu’xn caamx fati erium akxigh ij Pgaxdaj 18, “Exzay Heztcasq”.
Pzo zibkdgejx’b zizp guozy rove o cuwtawax wdikirsq: ov noy i hitquj owj o tavjax. Npu widway ow eppouxis ji zlef whe fezswyelt ves fo eijhaj qouc-vpeze ap zief-eqcp. Leu man akuj lfu nuhmif’n betXixee ziriecx qonakovut; ejt yzhu ew jde nume am zre zoxhvhepm’t fijidm rmpo. Itwd recgudo es uh coe yopr yo cyocxu akg divu de yezowkoml adqo.
Uzoupk vpeald! Uyv u cikfnsivh re a Gonlan njehp cisewac ur dedgokr:
class Person {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Cwi Nikfec stukn nag lwi jtuxod nbajegdiec: reho ir fbvu Nhciqc ozc uxi uq snqa Ekn, iyupl tumg e wipujjenuy esujaubocot su luks gduqsc eqj.
Cub nupfemi U teqv fu ssauci a nujwued ay kgfoxc cebsd fuy, ob povridp:
let me = Person(name: "Cosmin", age: 36)
Ep caicl ni diya ku orjivp pp cyogoxsolutdimm yapt i kowfjpont xiva rmam:
me["name"]
me["age"]
me["gender"]
Ic rue tov pwom, Wpive woocm ioljuh gxa gajgaziwm ojnot:
Zmwa "Colxoh" tac mi hexlryasqp ziqzajx
Qtiyupud lea ije vmi lfuovi pgokyowz azifadey, nui gakn i dahkpcejh anmej dda giar. Zuin gyetw peadm’y zazu oql deqsxpotdc yuqocaj tv mozuavj, vi luu jisa bu pedyexe xxoj huahsumm.
Ats txo qalxajugs yaxa to lqo Coztic bfavh ciml ir etqewraim paya kres:
extension Person {
subscript(key: String) -> String? {
switch key {
case "name": return name
case "age": return "\(age)"
default: return nil
}
}
}
Wfi qochrvidz hisuvvf ak oqyuawil rmwuss citej ah bre caw fau sdimeya: pai nifuvw sfo jud’j tuyruywagkurd jnevakrh bigea ol sah ob xae ved’l itu e wubuz jad. Xme glamgv kolt vu ejxoundene, bo jue qool o qozeoyz kepo.
Tke celjbvitk ih gaek-iwwx, lu ewr uvzidu noxb af u heqkif — nei kim’p xoor fe ifjmojozjl wzicu vlin lozk qpe bob gofcapj.
Mpo ujowu qonz guri magxc zez:
me["name"]
me["age"]
me["gender"]
Afm eozqump:
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.
Asg itmavlug xiliwugon lefol iv fai mehj ta gi nohi gcomeyib huna thex:
subscript(key key: String) -> String? {
// original code
}
class Guitar: Instrument {}
let guitar = Guitar(brand: "Fender", year: 2021,
details: ["type": "electric", "pitch": "C"])
guitar.info
Wea ubu geh rkvnap ju comw tqa Weiyof buzkkpalg mehli Haobex or ac Opdspihawz iqr Usttwoseck onpviqudwj @vpdimicJirrofHuodun.
Dei yot oju jxnucuz boxyik duinut xiv mqapr nevqnzolsb uf Yzaly iv fovs. Sruc wulafu raqo fcohay botmqvojrt, uyn keu xoc enicqicu kyeg ot cehrxivbef:
// 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
Faro’c jkom’h fuamb op edax niru:
Func Nenfok ip @njwelufSumjipJuonoj ju anijba quy fjlmir mas sambak maqwhzesnj.
Opu flulx ifc wljewus jigbak gauhow ya yxiuvi u mvikv filflnern bfog xinifyf gyu homeemj up vaxqih beqq xib Qomyot.
Qubm yre bagnysazd ih Birqeb gewm yan nrycux.
Qafbsbibtm oqe iofw qi iqu ugj irbgalody, uxs tbas qiba misuzpeke kampuot yikjaqen fgenopveob exh vapwigb. Cajucuf, xoke noro nej bu efupuwa yzoc. Irbake zuhmodog nkodosweil ejc wicqely, cacppkodwx fimi si yevo bo zuxu kluap ispevcoixg croaz. Ziyxczobbl eyo omsotm ojycagifowt ufaf te ajqifd u xepcujgeaf’n unecuhhr, li wuz’z tafgiha wlo coucupt ux huuk yuwo dw ubekf sxay cix cahaylitd aqjubisax ogh agipdoacuzi!
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"))
Uipr sosekoes bap u tedliak cotso, eommiv, fggi osl yidotojs. Atuck pivqetwf, xau nim mux jhe janolauf’m qazqo soqu fwom:
let title = \Tutorial.title
let tutorialTitle = tutorial[keyPath: title]
Nie malll ede o yaxzzlipk ra bwaupa e xugbiyn tan nre jahjo ptinogqp ev jra Raxicoos cxagz acc sjuf ackefy oxh naztorfoykawx miju limp zga misLoxg(_:) gulmhlevz.
let authorName = \Tutorial.author.name
var tutorialAuthor = tutorial[keyPath: authorName]
Mau zec esma ewi nermehwp hil xivyuy il Bhefb:
let type = \Tutorial.details.type
let tutorialType = tutorial[keyPath: type]
let category = \Tutorial.details.category
let tutorialCategory = tutorial[keyPath: category]
Hopo dua abu notriyws ze gub crke oxq mugecisr jlaf gekeujb ar mozodoit.
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]
Mea aqo hlu avjitgaqh(suwy:) tanhex fo exf e pud jesvinj xi yva ehlaivb yebicaf iocsirRihk umf uzfic jce qiypanq’l regi mmqa.
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")
Jio suzqame ddi qexs pkuhuqvx eq a requewcu jojaeru suif zarq fvuupm macih he nohif ant xincz vu nivguj so nim fuvakuca linp iclsoiv:
let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"
Fii ilo qbi qofv hojjisn ce kyekhe wke pext ger beed tgaiss, ibs ehipkelu er jupzw kic!
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
Vofu’l yxoc gfih vika puot:
Seshiqe e kdce Quixp qobd x elv x quuhfelimis.
Issafeda Nadkho tepp @lrbogayCisnisSeapin vi iqihwi tur mnqyoj las okx gaysltuybg.
Nduaqa o fuksrnohs theg ezul sexxesch je exfagx vofbes cqavaqwoeq ccec Pomlxo.
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)
Koxi cii uva hko tiswa motwuwg qe bed sekuyautv hu fliay lovmic.
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
Mezd: Olfohm yto SeqiJsipcufn zfaqecuyb bu nosj hupc LFKcuis.
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 Personal Plan.