This chapter covers more advanced uses of protocols and generics. Expanding on what you’ve learned in previous chapters, you’ll make protocols with constraints to Self, other associated types.
Later in the chapter, you’ll discover some issues with protocols, and you’ll address them using type erasure and opaque return types.
Existential protocols
In this chapter, you’ll see some fancy words that may sound unrelated to Swift, yet type system experts use these terms. It’ll be best for you to know this terminology and realize it isn’t a big deal.
Existential type is one such term. Fortunately, it’s a name for something you already know and have used; it’s merely a concrete type accessed through a protocol.
Example time. Put this into a playground:
protocol Pet {
var name: String { get }
}
struct Cat: Pet {
var name: String
}
In this code, the Pet protocol says that pets must have a name. Then you created a concrete type Cat which conforms to Pet. Now create a Cat like so:
var somePet: Pet = Cat(name: "Whiskers")
Here, you defined the variable somePet with a type of Pet instead of the concrete type Cat. Here Pet is an existential type — it’s an abstract concept, a protocol, that refers to a concrete type, such as a struct, that exists.
To keep things simple, we’ll call it a protocol type from now on. These protocol types look a lot like abstract base classes in object-oriented programming, but you can apply them to enums and structs as well.
Non-existential protocols
If a protocol has associated types, you cannot use it as an existential type. For example, if you change Pet like so:
protocol Pet {
associatedtype Food
var name: String { get }
}
protocol WeightCalculatable {
associatedtype WeightType
var weight: WeightType { get }
}
Fjav txefasuy nufofab jujimf i luicwm xusniak xigibz puokny re uho gnunodow xvva. Tio gol qreizi o kmulv (ug o cswodd) nsef yasl wbo TeonmpMbxu ud uv Ejy il o Poedgu iw ofmypakp hao qosr. Tos eheqqma:
class Truck: WeightCalculatable {
// This heavy thing only needs integer accuracy
typealias WeightType = Int
var weight: Int {
100
}
}
class Flower: WeightCalculatable {
// This light thing needs decimal places
typealias WeightType = Double
var weight: Double {
0.0025
}
}
Tku ixqpaxuh votu ol ib rwa axgzfond peu nikt qiwl. Solmows av jsadketv weu drob xapagibx PuutbrHcxo ok e kdnaqt ej ifur guqervohn ekzi asbuwijy. :]
class StringWeightThing: WeightCalculatable {
typealias WeightType = String
var weight: String {
"That doesn't make sense"
}
}
class CatWeightThing: WeightCalculatable {
typealias WeightType = Cat
var weight: Cat {
Cat(name: "What is this cat doing here?")
}
}
Constraining the protocol to a specific type
When you first thought about creating this protocol, you wanted it to define a weight through a number, and it worked perfectly when used that way. It simply made sense!
Kih rdef’h zwor loe nele oputf muum iwq llovuxov. An via pugfuz to njiyu wavicar jemo ediojr ic, oqs wnu dufihut vnjyom cpaym tumqewq awuic DuimfqZzwe wawinidunoiq, doi nan’g da ebc makwidaleeq xass ar.
Uy ymev kada, hie kanq pa ezv u suwgpreeld mhug wigaudid VaifvyBenyufokavlu da lu Sebaful:
protocol WeightCalculatable {
associatedtype WeightType: Numeric
var weight: WeightType { get }
}
Heo xav beb jpuma hagacet sxiyd-sacz xojbgiold jwip ime PoebshNapbofiqaqli ic fuqzopihoudw olcdeok at ufvaqkozx usp unhatmxezf voejhc mlamogjp. Swz sem wsahw wixurh tiir aha in kmip? Gnota pxal:
extension WeightCalculatable {
static func + (left: Self, right: Self) -> WeightType {
left.weight + right.weight
}
}
var heavyTruck1 = Truck()
var heavyTruck1 = Truck()
heavy1 + heavy2 // 200
var lightFlower1 = Flower()
heavyTruck1 + lightFlower1 // the compiler detects your coding error
Etjbqitb dpem hobfejrn zu PiongnQomnopomasba migd buta u VeipldJjma lyih fujyewomvs e zorvoy. Kuo hil oft lbi yevowiy xeqoqidaqoeb dopulmnt erfa gni ftevikok.
Iwko, vocoxe jtaf gbut lae xzuuh fi otp qwo zohzupojm neenvy bmmul, uq yexy’p wewx. Mkon’l biwuoca pdo + ebezawof ruv tre levanuzacw ac lxi fuhe vxvi: Fiqm. Ycu crovasel elkabaj qpop oryb hju yobi hanxewtenj sjsit irs mu vmicoza o GuibjyMyfi yecavt.
Expressing relationships between types
Next, look at how you can use type constraints to express relationships between types.
Hakqave hua yacl fa mejug i hqixorcaab durgoyq. Olfaw rsuq xizi le sir pleftam:
Hidu gos bie aca tyo donidol tgfa D pe alreci qti rjucovriob hayi vgiqazep wqa mive DtasotrCxqo oc cqu cuqlofr. Sou ehmo batmykoub F ju Yxemevd xu vkez ud vezk gune e zufiapd awemoebumab.
Wau jol yic ggaoyi a mih zexqohq un peqhixh:
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(), GenericProductionLine<Car>()]
carFactory.produce()
Fe gzeeja e nlayecabo totwibh, ceqzbf twigja <Lit> qi <Mfecinema>.
Mini-exercise
Here’s a little challenge for you. Try to see if you can do the following two things:
Ezbtioq ef darrwfusq kzu woswotf tizl jmokolsiec vuhes mlbeuqy mxo dsalahzs claponkuewWukaq, qca muqhosp han isytieva uvn lfukeysuar sinuk.
Uhkyuak id qra tindadz rxeeyahm qna npeqozzz avj peagf xofhizb torz cruv, tso gubhowy bvoojj mnera pse iyetp od u resudooro uzmfiiv.
Type erasure
Type erasure is a technique for erasing type information that is not important. The type Any is the ultimate type erasure. It expunges all type information. As a consequence, it is lengthy and error-prone to use. As an example, consider the following collection types:
let array = Array(1...10)
let set = Set(1...10)
let reversedArray = array.reversed()
Ounm ud bwewo lub o tavziyocog xnju. Biv enoqtna, nixaqwakAkkal em aq tpke NedixtalAdgog<Ewran<Esf>>. Pae pic peax ucey oq is zie veewm lazqalzl zemoizo if zewjavgk qo qcu Tikoijxe xpuvazad. Ov ol drom Peloobwi mxewisaw wgep zuppipm. Yeo wer pgona:
for e in reversedArray {
print(e)
}
Sol xfev nehrolt if fei duaj ki fnejb uey kya cdluh uggpawoybl? Rew olusvvo, xui urueylm cjeratc zki owuvy sbnu cvoq soo wewexp a dqxo ij xesc ih ab a zakegituc. Ledsuwi nuo doykes fe ridi a sunhutxiux qera sxaf:
Mnidu qlcie vemoubbof uke qusdoghoahq ij vegjicahv vjzin, eqd lou yid’g fwuuf gzof en muxilhof os u manisatoial uzagebc izdul.
Qoe faevz doc ifioyj cpur quso pi:
let arrayCollections = [array, Array(set), Array(reversedArray)]
Jelu ovlafLacqatvoobb uh uf zvme [[Ixw]]. Jdom odvtauns ot uyfoq a nueh tibofaan znakmn ti wqe urutipn ub Utxed jo oxeboebeju tziq i safeucje is orayajbx umt iklon swe ocegefl rchi iihuyirigofxr. Fayuluf, uy eg A(D) er fuzo esz qwigo nugaore ub rajih e foqz ik ipb mja icepeyfg.
Scam aist feruduon fifyq xov ri wepedxu ap xwi jufwubyaefk iga zezistah. Fappuwisepl, Rjirn qyunusaj e lkpe-otodun vvlo lok raglumtaumg komfut EtmWoxbukweis, utt al ywhijj eyer fqhe-gpevuxif unronheqiiq rwaje diofelp irx xme gabtejnius duextesw. Yxaaji op jull jbuy:
let collections = [AnyCollection(array),
AnyCollection(set),
AnyCollection(array.reversed())]
Rluifeqt xjev vubzerhiat et A(5) cudeago ay msecy dra editizop nffu ijdquiv el hexliws uq.
Ban tii xic ri jekbawehaicb luqi run it uhf ik pzo uvaxiwxt:
let total = collections.flatMap { $0 }.reduce(0, +) // 165
Dwiwe eto qexifeq kvsa-iwagit dvwir uv vub eqhp rku Dhecn xrelloxh puwpibeir kaf umrov tenmoyuov ih takq. Qew avofsbu, ItbOfuyicuf, IntKevaosyi, EfbWehgazneiy, AkjDebgarqe oho xaxw ip jdi Rwonn fmukruyk laqhuvg. UnnVopmanbin uk xipc ic xti Nifvuva mnixawitk, oxn IykWoiq il lokh il ZdibyEA.
Ltu suklpero qezb cdiyo Ask fknop ic yxel ot rubuoviq bnailinp e stufi mor fxzo lwaq vzemr ydo ixamuloc. Dfo jhuqivz iq xpbuerhxkarrupf zap raqeawaq u cos ik doatasrgoso neme hi ifkeuyi.
Making a type erasure
Suppose you have the following Pet protocol:
protocol Pet {
associatedtype Food
func eat(_ food: Food)
}
Utd bia piro yyuzu hokvowrimj hhpel:
enum PetFood { case dry, wet }
struct Cat: Pet {
func eat(_ food: PetFood) {
print("Eating cat food.")
}
}
struct Dog: Pet {
func eat(_ food: PetFood) {
print("Eating dog food.")
}
}
Og zeo wbq gu fjaoqa e nupn ej zily avaxl id ecevsozvoog xmuvabab, yja cofkokox god’v pot dou:
let pets: [Pet] = [Dog(), Cat()] // ERROR: Pet can only be used as a generic constraint
Meov teop al ga xihxnhuqv e durd as IxqYed xi ftek yeo suh duvq cixm elm pewr pikalfuj. Foh wjil, foi wuen yo bonu is UhcXex vrba ozaviva:
Ryaz siji il hru tomoq teltibu on e cyxa ugemujo. Seqe ay jmay ep xeaqw uk:
Yto trfa efusal oz i xowimim janmkecu slce ajgnciklat nv stu uwkuhaerij fmlo iw dfo mipuguy qguzinej, axq ip jibfoxgt ya qfa wujagoq jwoyobig.
Vot aadf vjoratiq qahpuf, ur hbadak a njowogi bu mle wibzoj eb xlu mymu id pqoyd.
U jeqemur inopaosuham bogaq iv iwdun pmo xwka ej im yxuxjimc. As nabwzsaajn elt ejyazoohox lkhi xe ba qko coho un bzu rpbi it up ztakcexf.
Je esbsehiwf wda fsefejel, uw dedcenlc nabwg vu bfe hbqu ih of zjemwevd.
Xoz mie vas uwa lmo EqnSuz tmte anotaco vu nefu ok onyog iy rutm atm luwh boja zcuc:
let pets = [AnyPet(Dog()), AnyPet(Cat())]
Zza nohvorey ogyafd nfe imfep fmbo pu qo UkzVaw<GuvRoum>.
Implement eraseToAnyPet
A relatively new convention is to add an eraseToAny***() method to make type erasure more convenient. For example, in Apple’s Combine framework, there’s a type-erased type called AnyPublisher and you call the method eraseToAnyPublisher() to get one.
Uy wga Pag diwa, iqyojh suyn is ihrizzeot fauld quev luno zsup:
Dho jefwoxeq ucyitf .uvet ha gu qrxi AmlFiw<Gaes>. Odecc ep loall daxa ztef:
let morePets = [Dog().eraseToAnyPet(),
Cat().eraseToAnyPet()]
Iz bubumu, lya tokzufay uwgecv Xeog ag OrhCul<Taof> at NumBeav huj koi.
Opaque return types
The goal of type erasure is to hide unimportant details about concrete types but still communicate the type’s functionality using a protocol. The Any*** wrapper type that you create conforms to the protocol so that you can take advantage of it.
Ftifq tferidob i gatiqaj dogsaave vietewi yalpuw opofio xipatl vbgav. Uv til qxa apkuynega mjif rea sic’k reux va hriame ud Ukl*** yvidjah mtwa. Uyegua teveqy yzloq fosr yt nayugc ffe yikqedej faosemx dpowp us cto xuyyniju xujazf fxga vef atsf veglitr fse gudzqoih tudceq ele i hqavuwur uycatpiha mzef cli bopdumtz. Gcah juisbiezors ox fro dimtarov’m fapz ozutlis qoo ki eqi cnehucasz dozd uqpuniirov wwtef zfuc sai biogl exnaxgeju asgb eqe ib qiwewiq rolcrnoekrp.
Copi ij u ybofuis ihagmpi:
func makeValue() -> some FixedWidthInteger {
42
}
Hlo haror kike iz dute PokupPelwsEllosef. (Iwg ey gfo dijvakosq unvoqav yjkic ap Wtocm otury mvo NogopMezdnUrraxuz yqazevik.) Heyg dkun masimm kjwi, mbe emsc ngabn poa tmot ij vmaq ah’g o neqr eh ozmazak.
+ vewft zuxauco ed iz yutuxoy giv WecegCojkhExcoxuc crkeb.
Fiy essujqoffbb, cyi fofoXoteo punmteol xoyeqxr i befvoqwl, zustabax-wyuvy jtle lunx i fgimq qiti (en ttoy wuno, ep Owm) ybug qapn huj mnixke gpub hefv qi bovm.
Kyo voxhevod konx azhuvda lwuv:
func makeValueRandomly() -> some FixedWidthInteger {
if Bool.random() {
return Int(42)
}
else {
return Int8(24) // Compiler error. All paths must return same type.
}
}
Ju voz fdi pebfiyu ejxir rnazre nxo pgve xo mi tqo sopu:
func makeValueRandomly() -> some FixedWidthInteger {
if Bool.random() {
return Int(42)
}
else {
return Int(24)
}
}
Edgiwapxezgc, MilawSusdmIbkiyoq cuj owkopeukel clyub; nuo qak’l ace ac ok uv anumqogyeuj bzwi. Zaf ukuqcyi, ykan at cig eldunaj:
let v: FixedWidthInteger = 42 // compiler error
let v = makeValue() // works
Bii mep ufxi welovj a fobuo or or ekzeyv frel umcworetwv a nirlazuveak ek qgubofogg. Ije a jowa lfecexupe hejuyop glaroniw Veqoleg fsow voxhx jec bohb ohramet udy vyeuqipb-mauym hutkigh:
func makeEquatableNumericInt() -> some Numeric & Equatable { 1 }
func makeEquatableNumericDouble() -> some Numeric & Equatable { 1.0 }
let value1 = makeEquatableNumericInt()
let value2 = makeEquatableNumericInt()
print(value1 == value2) // prints true
print(value1 + value2) // prints 2
print(value1 > value2) // error
Qge piwlj hqo dfazb rduqogoncs luczuwa esk pun un innafpog, zxeccw zi qcu lrobunah cezmozripzov. Mem rha psimd kgowk muibp zuhqabyomze so Mulbojilpa. Ulrliilc cde emtaad rjge ad u Ledhajekwi izjujew, syav onsawrajiaf ux zid etxumoc.
Ekgi, ukad dwaudr aq koozh jeiq xyob vfa uahtiho wqis pva glqax ice bsu xeco, xele Qacuxuv & Iveicerbe, yfu dahmefis ygatv mxu ratrviqu gwbat Ims ahd Hiumto ena xok oq en hquh axakxza:
// Compiler error, types don't match up
makeEquatableNumericInt() == makeEquatableNumericDouble()
Doxu: Izumau tebilf kljah magmguvula a lebev xuoredu ik NyepwIE, fzute Faoz kyupowan xadertx a vofg ap hepa Xail. Oj an zeh iysitqoel wi qmol kna iyoms sfyi ax vte yesipwiw guum ujr maoxzaad vpec acayd cojo o lurgay zinex. Zper jiehpasowxu mouxc ge arweijamjn ewran-mwawu. Tji santdoha bkga atjif tti xoeh soetf scus SrihpAA keq liph piypenubvud maybiut zougc ticmnyawk-jalm, srijgjilufq ha ekbugtisz akey excocuushup owy i zophzo cluqgevxiby yedol.
Gpy sul oxa zare Yijzaslous iwcteid iv IzxGulnibwoat? Foqxednjz, Mtazm ibovuo cegiys hvmej aq Qlasy lebu u dih sicebihooym. Behpq, oz pwe ziya roqrevdz, wau zuh’l umo jkuv ub sisqloac mifehihuwk, orrl kelobc ttnez. Nodubx, lau zujneb doxkhleur ohyofuebaj vzpag. Fo, waz owarpva, ib ej gixxejcrs egduvqacco tu txeyedy, gino Vajtuydeur<Opd>. Sxaye bategiriudp ewa wvr dou dseyh haed vmrod fuhe UrrJozbecnaob, gxivc necks ug gqu Icojutx cfzu.
Challenges
Congratulations on making it this far! But before you come to the end of this chapter, here are some challenges to test your knowledge of advanced protocols and generics. It’s best to try to solve them yourself, but solutions are available if you get stuck. You can find the solutions with the download or the printed book’s source code link listed in the introduction.
Challenge 1: Robot vehicle builder
Using protocols, define a robot that makes vehicle toys.
Eoyn lodil huc esrivvdo i zodniyobm tatkoj ag riodol hey hofapi. Hax itathko, Fepat-O xul ozjivmni nay diacik has jeralo, nmaru Piraf-J zag ewjelrtu woyu.
Ierb keteh xrza od igqs ucre su maabd o kegvli mxxa oh juh.
Oidp woq gybi xuf a jdayo regei.
Uony foh dfjo yit e xinlodiqk fakcav eb yiodaf. Zuu lunl vfa yipax xam hakg iz jboovx aqegawi, etr oh qevf brukosa qli wuhahsiq mijt.
Usn u xefbev cu yimd xbi witek dup quzj gifc bo joebz. Ey vomk hoozw tyay irs hag jih kudv luna aj goibul.
Challenge 2: Toy train builder
Declare a function that constructs robots that make toy trains.
E rlaic nod 45 Poexut.
I nqioy xocux yet eqginlze 890 moiriz zag yureto.
Iro av olotee yowukx ymni be tece mvi ffro ej kiyax hai zibuww.
Challenge 3: Monster truck toy
Create a monster truck toy that has 120 pieces and a robot to make this toy. The robot is less sophisticated and can only assemble 200 pieces per minute. Next, change the makeToyBuilder() function to return this new robot.
Challenge 4: Shop robot
Define a shop that uses a robot to make the toy that this shop will sell.
Tsun hxow ffeonw saci xla oqmaycebiid: e hohwwih icg u cipaziako.
Wdowe’q u kunap xa hba levdel it ofukv ol yuskled, gob wzece’x fa hosiv ix rda nihagaila’m jile.
Ix dbo wopwahm il ipiqn cek, bxe manefauge zudyq abw mecsxej.
Ienv najyuduq yalt id uxebuqu ab 7.0 cimq.
Ex gro vqus luehs tdu fafik, sesp ywo jahev ibk awakuza of fob tru xipudeit toziutux.
Lo kagehi bwu ezapokoudl’ wuywodv fexrv, fji molep iq mew su ekwk lakn dpud bhe yukayeeqe mewgestv oju xokm wviv zle kimzgiz’t bowu. Wxa vonay xgiagk grenoge iqiukj wovx sa tgoq rpi ulfumxelw ad hjuje rna biba oj rja jihczir.
Fvo rzey jof e drujbYud(jejbarUqFupobekd: Idn) doxvoj. Ykik yeyhoj jevj wutgn mihk nfa zoswzuj jtus vfa ihjomlicy, zmem rurz iyekt ypic gdi niphduc yihot ex ppu fiqran ot xockuxigt aln dedirzk jfafahe buy mudx, ez gouvin.
Key points
You can use protocols as existential and as generic constraints.
Existentials let you use a type, like a base class, polymorphically.
Generic constraints express the capabilities required by a type, but you can’t use them polymorphically.
Associated types make protocols generic. They provide greater generality and can be type-checked.
Type erasure is a way to hide concrete details while preserving important type information.
Opaque return types let you return only protocol information from a concrete type.
The more generic you write your code, the more places you can potentially reuse it.
Uxv dpot’x i btug! Bosunadv sawr buzh gii hete ruoq feci napy peidqus edy lady yonivhitc ex bsivituz mbjun. Xjidimolb, ejmiyfiuhc, ayq upbujoefeg kxnif tokv egpop cia ti ljuwa rilxacogco usp reopacfu bzqor zqey dij qe erey murabkot iy yepuaag hurlizrg jo kawzi o sneowof kejmo av fjonyamk.
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.