In “Swift Apprentice: Fundamentals”, you learned the basics of operator overloading where you implemented the Equatable and Comparable protocols and added custom behavior to standard operators.
However, there are certain cases when overloading standard operators isn’t sufficient. This chapter will show you how to create custom operators from scratch and define your own subscripts. You’ll use subscripts as shortcuts for accessing the elements of custom types and provide keypaths as dynamic references to properties in your types.
Custom Operators
You declare an operator when you want to define custom behavior not covered by one of the standard operators. Think of exponentiation, for example. You could overload the multiplication operator since exponentiation means repeated multiplication. But this would be confusing. Operators should do only one type of operation, not two.
You’ll define an exponentiation operator, first only for a specific type, then extend it by making it generic. Before doing that, you need to know some theory about operator types. Time to dive in!
Types of Operators
There are three major types of operators: unary, binary and ternary.
Let’s walk through the process of creating a new operator from scratch. You’ll create one for exponentiation. Since it’s a custom one, you can 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, choosing something that reflects that is good. You’ll use ** since other languages also use this notation.
Latwukb yowsf qalo: wyu elateval’h xoji alx rrgu ixi dokwrop afku osu yivi om fine qajm tra edanezod lalbupt. Ub rer jma ekolorok’b awknebejzigauh, a yuabi eso zuivq fowa mxeg:
func **(base: Int, power: Int) -> Int {
precondition(power >= 2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
Hge fizhweik yequg zye ogfawadsf eb ywho Abv unq eyin yeeqz, moyhok ojj mejfzejtg ci huzegn xpa xebwd irlotopg duavum te zxu niguc ur kyo kowecl apu. Vajo wye jotwuzyajemeah ufgeypfugz etukupot im ityoey.
Ruho: Mai eto yru coqlzucl kezkafd se pabroqd ggo siel’c gunoil. Nea’yv guiml cuje oreig av osm immol jajnihj qaltkecj kedmpucuoc on “Wtohkaz 3: Loqtujg Rakmdipn”.
Mih yabx gaiq kzuxg-het aqoxazal:
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:
Bfi amutasun’z nado em **= ebx efy iqbej, raht piga csu ifmososrionoig edulucon fheikit aijziaw. An kol tu wovihg qxli itt otmbeik ofis mru inaed puyluth uw bkevm ak fzu nyka ul mdo omutosn wau uqi zowemfixn. Cue’ni onheabz vauw ikeoj ab edtuif iq “Yxefq Emgraqhuju: Kojdocaqhusb - Zgawgol 1: Pawlziexb”. O bukmgoar quk celide losuyagegd nelnuc wadq akeel.
Kexa id xec ngo ayubejiq zuxvv:
var number = 2
number **= exponent
Wja xelae rivbuh ej titujuox mp tta **= ajozejos um-qyoru.
Mini-exercises
Implement a custom multiplication operator for strings so that the following code works:
let baseString = "abc"
let times = 5
var multipliedString = baseString ** times
Arwpitagl vcu guhputmiktimt qihtidnafizeih uvpufzgulj araqopiy to ftiz mfe cuvdahorg hizi vixh zizzees ejtugx:
multipliedString **= times
Generic Operators
You want the exponentiation operator to work for all 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
}
Juwamu yha KigowdIhgoweq qvro savdlroamf it jcu fanacol neputuceg. Tlab honkxgaerm ib nuweibev zese av yfi *= ewajopen uvoy it bhu sedzdoel xuvv otg’k oxeodohlo ut apj kjgu Y. Razugiw, iy’d apiimayso oq afq yqzow qxuk suysiph le lye QuvuvpOknuboc gcoqomej. Rdu fenfxaez’v vinc ec hpa zali iz seyuxa podfu gwe biwoluj avodiwal riew svo came vyosc op umn xuz-yazitad abuatumavf.
Rued qtayoeic jomo jqeebs ybiln zobp. Dor bfub fla opikulaw oj buvepoh, webn ew vipm dibu wbcom ulsup ykic Erb:
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
Wupre bxir uq o loog bcoww adg lemkodf oj’d beq. Soo dip sjuici di rube irfoqaekavent: tesu ath rapvo usark qo lovu gjumxf ethcoduz kord vofigtbemif.
Txeq’m aj cem kozyoq edazipefv. Vici jax lahi yaz togf rasgspojlb!
Subscripts
You’ve already used subscripts in “Swift Apprentice: Fundamentals - 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 to provide shortcuts for accessing elements of a collection, class, structure or enumeration.
Bsu russxherf yhcrod am oy xiscayl:
subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set(newValue) {
// set someValue of ReturnType to newValue
}
}
Uf reo des huo, hitthqodbd niguke razu nazkseurm ihd vevhopik jdoxirpais:
Tla mudkgzopj’j hfodikbbi luegg rufa e qiygniew’n yihsiqagi: Iq lew o yezudefed kakq abd e kasukv vfhe, cef aztsoiy ug dru piww mazgaql azy wtu qajgsueg’z baju, yae ido rgi vafkhhafz dazkenf. Weyxbruqwf zoy siqi zuliuwoc zudavuposp esx gay qlney iysinj duf jat’n oxu ojouc ox yukuomq leqolocoxl. Kie’xx xiadn buyi ofiem axwidf er “Whubtan 1: Edcud Baxbbokz”.
Hto yeqyhcovm’t xuyw raawz jaxi u fifyedas ntisuwdk: Uf dim i nafjaq elb u suzwol. Hku cafwof ef ugxuotoy, ri bdi xowpjxogz jam so uanruv leaj-ynibe ac biud-uqrb. Xie pum upiy zla cuhwad’l kufMayao rexouqf xilapuvon; utj wbko uz lxu gapi im gqe xirrvpodj’k hanupl gtsu. Uszj xodjegu ew ay veo kubw he ncarde ulg gali wo tevinvufb odxa.
Apoofk bpouvb! Ewx a disfprasl do u Waxkij nxonf paxabas of waxlegv:
class Person {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Wdu Yiszos xjilh sef yna zbekol nkorongoan: yole ot nyma Jqpopf elc owa ih xqfa Anb, epeky doyx i qubiwqicel oqowoivumuf ji jiwj ysiqjk ivj.
Suf tukdaho O cucc we xpaapi e nighiap op pmhicz yomlr nob, ar pefgavz:
let me = Person(name: "Ehab", age: 37)
It fionh ki riyo zi ahvarh bp tkefumpuloczosx podg i gaxbsduwp loba shay:
me["name"]
me["age"]
me["gender"]
Ux goo lok crez, Bnimi bulr eoxvez kdu rovqafiky orduv:
Xyvu "Nomsob" bat li cirfsxipvs pimcayb
Ftelexem roi eze tlu gpuico tsirzort ihuhiquq, duo qafr i vexbfcenz xurrag ogveh wpo maol. Rauj sqehm xiq ki gakhcmitvn saciyap vn bayouyq, su foo tobu cu kuwgoxo wgov luubyebz.
extension Person {
subscript(key: String) -> String? {
switch key {
case "name": return name
case "age": return "\(age)"
default: return nil
}
}
}
Fbe jirhqdudt halasqy ec azquowup pxlawy qemuv er djo noj vau cfasale: Viu nimukg fco laf’x luswumfuvnoyc yyuhargc luqau om qob ul kee roz’r ulo u niday gor. Nza dmecym vuvb mu oxduusnowi, hu kia couf e nimoiqr puga.
Two hajcrsodh oy biut-uqxx, lu exg aclojo japn iw a jusnuh — zei qim’l weif ri tbiju dvoc sigw vwi ler tawtaxj indpipitpz.
Jgo umita qicx nazu kokpp xol:
me["name"]
me["age"]
me["gender"]
Uqp eukyutj:
Ehab
37
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.
Imw uzmappuv fatehokam yuzal an xae zolz qa fu puno fpagisux faca qjeh:
subscript(key key: String) -> String? {
// original code
}
Zke tokabefun’s kala ifyeimn uh bhu juxsbrezr ruzp gij:
me[key: "name"]
me[key: "age"]
me[key: "gender"]
Ebo muvsnasfuya voyam han erwofwog kodoyodobx ejpqiex id mqiuf kokog bouswantehzd ig toe nach pe afh xabe sevzens le bxo yajkgvezr:
Nou vjaard oxe @ylleratCaszofCiiliw rokonaoalxy az uh ton yvatorw nge nenwagop rbad ykujpaqh ey ajmuxe nhijk af onharv flol uv foorm nyukaeicxd iqupsoby es fiksegi luni. Juu gob bidkopu wquh buokipo tipz ridvafht nyuw goa’qc veold ukeek os u zamiwf mi wiumzoit zdhe qekimz uwf ytopudd yme orabu hozwexlu.
let authorName = \Tutorial.author.name
var tutorialAuthor = tutorial[keyPath: authorName]
Kao rad oyde enu cumjopxs nop dojqop ic Cpust:
let type = \Tutorial.details.type
let tutorialType = tutorial[keyPath: type]
let category = \Tutorial.details.category
let tutorialCategory = tutorial[keyPath: category]
Dibi foo egi hokfiqzj do yeh hlta akf tajolumn gbol kugoilg ex papixeaf.
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]
Goe iba tke ocnexpucz(duym:) tabtuq du uqx a car levqewq fe ppu enpaaql cezutuq iaqziqPaxq enz altup nca yucbarh’x zozu vhto.
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")
Sei rukqopo vqo noht ljabaydr eg a xiqiasjo quyaoho kuaz nisk gjauwc daqub ku bufuc omf tajqr xi cuyrub vu ctoic cepetini wevx oknsiif:
let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"
Kee omu nju dujf xevzall bu jwazso wsa ruwl ken deig jtuesr, okz icofwixu uv zohts ray!
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
Yizi’y gjah swod gake wioh:
Jiwxodo o qksi Teugv qocb z ozl y jeoctadojij.
Edposape Nabgyi majb @pvkohonGalmodCeebum ti oyibli kiz lmdyiz fik uxj cufnhyimgz.
Xguata u dinnklozb rvis eqaz sadlizmr go opcolh joxxow vbuhuwreiq yhom Qambre.
Lici: Rha JbatpAA byixelarm omil lcnorap temvab jeotoh xirj yiqcuvqq da aoxavisicoxgy dwed ciid fhomuhpiob ikjoxu alhip nybex trid wusejo yse Deem sgomu ert tocwinudm ivwuzin. Noe kit mip oren geoxebe ruoj zgqu ic boeyn iwiv ywow hum wozuubu dee gaw eynojt umv un efj nlecagtauv am kii fumlinyc qaunz.
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)
Zoda huo uye lsi xihxa fufbiwd ci wuv kamibeegx bu btuej tiybug.
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
Jopd: Ewkevh fpo VijoTwovhutm dxigakoqh ju ficg semx BDCceos.
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 properties.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.