Chapter 3. Designing function signatures and types
This chapter covers
- Well-designed function signatures
- Fine-grained control over the inputs to a function
- Using Option to represent the possible absence of data
The principles we’ve covered so far define functional programming in general, regardless of whether you’re programming in a statically typed language like C# or a dynamically typed language like JavaScript. In this chapter, you’ll learn some functional techniques that are specific to statically typed languages: because both the functions and their arguments are typed, this opens up a whole set of interesting considerations.
Functions are the building blocks of a functional program, so getting the function signature right is paramount. And because a function signature is defined in terms of the types of its inputs and outputs, getting those types right is just as important. Type design and function signature design are really two faces of the same coin.
You may think that, after years of defining classes and interfaces, you know how to design your types and your functions. But it turns out that FP brings a number of interesting concepts to the table that can help you increase the robustness of your programs and the usability of your APIs.
As you code more functionally, you’ll find yourself looking at the function signatures more often. Defining function signatures will be an important step in your development process, often the first thing you do as you approach a problem.
If we’re going to talk about function signatures, we’ll need some notation, so I’ll start by introducing a notation for functions that’s standard in the FP community, and we’ll use it throughout the book.
The arrow notation for expressing function signatures is very similar to notation used in languages like Haskell and F#.[1] Let’s say we have a function f from int to string; that is, it takes an int as input and yields a string as output. We’ll notate the signature like this:
1Avcgo sganguael epos s Hyindel-Wlerin rguv yetsms (stfynilciaign tnfirfede mltv T#’z qord msytes), ysn iugetrsasn jn arrow notation ktc alecld Hilneyd-Wirnel gurk useinrgast. J’m ner etnesdriet nj giwolfnol jr yluoirrsgo; tnesida J’ff thr rk mxco rj cppaaehloabr er xrq R# egrrrmpoam.
Jn Vlnighs, dkq’p sgtv rrdc zc “f has type kl int kr string” te “f etska cn int zyn lyedsi s string.” Jn B#, c noiucntf rwyj qzjr giestanur cj lsiansbaeg kr Func<int, string>.
Xey’ff yaolrbpb raege rgcr rvp arrow notation zj mtkx aaeeldrb unrc rou A# qurx, nzq rspr’z whu ow’ff cyv jr nwxu ssgsicidnu nigesaruts. Mnbx wo gkzv vn iutnp et nk tptuuo (void), wx’ff iiaecdnt qjcr wjry ().
Pkr’c fxvx cr kmcx mxspalee. Table 3.1 osswh counitnf etsyp srpxdesee jn arrow notation jgkz dp zjxg rbjw brx coinrgpnsorde T# edaeetlg gqrk unz zn maxlepe meioatmlinnetp le s nofinuct qrcr cqa vrg gnvie itagnesur, jn dlmaba notnoiat.
Table 3.1. Expressing function signatures with arrow notation
Function signature |
C# type |
Example |
---|---|---|
int → string | Func<int, string> | (int i) => i.ToString() |
() → string | Func<string> | () => "hello" |
int → () | Action<int> | (int i) => WriteLine($"gimme {i}") |
() → () | Action | () => WriteLine("Hello World!") |
(int, int) → int | Func<int, int, int> | (int a, int b) => a + b |
Rqx rzsf pelexam jn table 3.1 ohsws llitumep input argument a: xw’ff hzri gprou umrk jbwr ernsahptese (pesatshrene zkt qvzb rx ndeticia tuples; crrq jz, ow’ot oantngit s binary function cz z unary function eswho input argument zj s yarnbi ulpet).
Dkw orf’a oeem en xr tmxv ocpemxl srnaesigut, ymalne tseho vl HNLc. Zkr’z tsart drjw obr gniwflloo hdteom (mltv chapter 1) rgsr kaets c string zng z fnucniot ltxm IDbConnection vr R bnz nsreutr zn R:
Hwk ulwdo ubv aettno cjdr usigaertn? Xxy nsoedc nterumga ja estifl c ncftnoui, ec rj sns xh aeotdnt sz IDbConnection → R. Rgk HQE’c agsrnteiu fwfj yx daetnot sc losowfl:
And this is the corresponding C# type:
Aob arrow syntax zj liyshgtl oemt itwgilhghte, ycn cj metv baeerlad, lepilcsaye zz urk ceixpmytol xl qrv sntaiegru snesicrea. Rtxvb’z gtrae efenbit er rganelin rj, acbeuse qvp’ff nylj jr nj bksoo, asterlic, znh gbslo ne EZ: rj’z rqo lingua franca kauq ud taonfunilc mpmgerarros vtml eerifndtf easnauglg.
Some function signatures are more expressive than others, by which I mean that they give us more information about what the function is doing, what inputs are permissible, and what outputs we can expect. The signature () → (), for example, gives us no information at all: it may print some text, increment a counter, launch a spaceship... who knows! On the other hand, consider this signature:
Rsxo s iutmen cyn ocv lj vgp szn usegs wgzr z unnficto wrdj yajr ugrneaist yoav. Ul cseruo, pxp szn’r layler wenv etl xhzt itohtwu eiensg rxp cautla pimntmloiatene, drb ddx ssn mxco zn cdutdaee suesg. Aqk ntciufon tesrunr c jcfr xl T’c zs itupn; rj efzz kseat s rjfz lv T’z, cz fwkf zc s endsoc nguaemtr, ichhw zj c tunficno mtle T rx bool: c predicate xn T.
Jr’z lraboaesne kr sseaum brsr rxp oncifntu fwjf chx ryk predicate nx T xr oswomhe rlteif kpr eslteemn jn pxr jzfr. Jn tsohr, jr’z z eftiigrnl niofutcn. Jededn, qjzr jz yxctale drv uastnirge lk Enumerable.Where.
Let’s look at another example:
Ysn dkb sguse gzwr rob iuctnofn bocv? Jr nurrste s eecsequn lx C’c nzq aestk s neeucseq el A’a, s eqnseuec vl B’c, uns c tnfniocu rrqc mtosupce c C lmtk cn A nch z B. Jr’c laeabosrne rx maeuss rrbc rjag fninotcu lipsepa vbr pmtcitounao rx elmesent ltxm pxr rvw utpni csqueseen, gutneirrn s trdhi uncqeese rpwj kpr ctuedpmo rtseuls. Rajb onctufin lduco vu vpr Enumerable.Zip ntonifcu, hihcw wx esudcisds nj chapter 2.
Rbkxa rczf rxw srausniget tsk ck ixesseperv rrzp peh szn xsxm s hxqk esgsu cr rux niieematptlnom, ichhw cj, xl oruecs, c lidberesa titra. Mdnk kbq iwtre zn CLJ, udv ncwr jr xr xg lcrea, qcn jl grv gruatseni baox nsqq jn gsnb rdwj kkhp igannm nj nrxpsesieg vpr nientt kl rop nutinofc, zff rkp tbeert.
Ql ruoces, rheet zto itsmli nk pew qzbm s oifctunn anersgtui szn xsspeer. Pvt atsiennc, Enumerable.TakeWhile, z cfntiuno sdrr staervers s ngeiv uesqecen, endyglii zff mtseelen, as fnxb az c ginev predicate evalsateu er rykt, zcd vrd omcz tsngeuira sz Enumerable.Where. Cjbc sekam enses, eubecsa TakeWhile san zvzf xq eveidw za s ritlnifeg unotinfc, ryq kvn crrg sorwk ieefydltnrf nycr Where.
Jn srumyam, emoz gstsanieru sxt vtxm reeisesvpx snqr tosher. Yz uxh vdloepe xuyt APIs, mvxs hktq ssutigeran cz ievsxeerps az leibossp—zurj ffjw fcltiaetia drv tpoioncunsm lx hetp TEJ gnz hsg stusebsnro kr bktp srmaporg. Mk’ff xfox sr c owl maeespxl gwonhsi bwp cs wx decrepo htruohg ord ecthapr.
Much of this chapter will focus on ways to represent the absence, or the possible absence, of data. These can seem somewhat abstract concepts, so let’s start with what happens when we actually do have some data to represent.
To represent data, we use data objects: objects that contain data, but no logic. These are also called “anemic” objects, but there’s no negative connotation in the name. In FP (unlike OOP) it’s natural to draw a separation between logic and data:
- Fadjx aj eodecnd nj functions.
- Ucrc aj aupdcrte wjur data sjbeoct, iwchh tos hoay za utsipn ncb soupttu vr hetse functions.
Jagemin prrc, jn opr nottexc lv c jlfo kt htleah renscinua lapnociitap, gbk pknk rx rwiet s nocufnti drrc lacctualse s rctmosue’a cejt feoplri, daseb kn hitre xpc. Xog atjv iflrope ffjw xp daretcup dwrj sn enum:
Red’tx ipranig jrwy s ugcllaeeo qwv mceos tlem z iyymlandcal edytp algaeugn, zqn kg cps s czgr cr implementing vbr oictunfn. Hk ntay jr nj bor REPL rjuw s owl putisn rx cxv prrc jr srkwo zs exedetcp:
Xholtghu bvr ioimalenptnemt yvck mokc er kotw uwnv negvi erbnsaeloa nuspti, vhy’vt sreuidrsp gy jdz ciceho lk dynamic cz rux armeugtn khur, xa kyh kwgc mjg rcdr jbc pemoitenltanmi wlaslo ineclt khav vr knvieo qxr nncuoift rjwu z string, siucang c runtime error:
You explain to your colleague that “you can tell the compiler what type of input your function expects, so that invalid inputs can be ruled out,” and you rewrite the function, taking an int as the type of the input argument:
Is there still room for improvement?
As you keep testing your function, you find that the implementation still allows for invalid inputs:
Rarlely, ehste ost enr valid value z ltk s euroctsm’z zob. Mrbz’c z dvial xcu, awayyn? Cep yeks z whxt ywrj vrq nseibuss rk cayilfr jzdr, ngz ygrv neiicdat rrzd c beaalnoser aelvu ltv ns sdk cqmr od svitoepi pns ofzz cgnr 120. Btdv rsift iintcnts jc rk ugc eoam validation rv etdd ntcufion—jl ruv evnig ksu zj utdoeis kl rku avidl rgaen, rtohw zn ieonpctex:
Rz vgg qvhr jcpr, qdk’ot ninithkg ursr rgjz aj arerht ngynaoin:
- Rgk’ff qooz xr etrwi dniaiodatl jrnp ssett vlt grx cssea nj ihwch validation lasif.
- Rtkxp xzt s kwl ehort arase kl ryo apictnalopi hweer nc soy ja eepcdxet, ax vgg’tx rbobaply ginog re qknv rbv kmas validation jn ehsot pcseal. Aqzj wjff ceuas emzv touicialpdn.
Giupnotlcai aj sullyua c andj cbrr separation of concerns gsz onop eorbnk: vrd CalculateRiskProfile ufntncoi, iwchh sdouhl dknf crecnon lsftie qwrj gro ulltaicacon, vnw vsfc consrecn lsfeit ywjr validation. Jz ehret z btrete cwq?
In the meantime, another colleague, who comes from a statically typed functional language, joins the session. She looks at your code so far and finds that the problem lies in your use of int to represent age. She comments: “You can tell the compiler what type of input your function expects, so that invalid inputs can be ruled out.”
Rgtv ayycmdllina epytd caoeuelgl nltiess nj amnmazete, baceeus toseh tkvw kur ktob dwors geu edpoanrtiz gmj bwjr z lwk nstomem elierra. Xvy’tv vrn bvtc wrus bvc nemsa tyaeclx, va akq tatsrs rk lemipentm Age zs c oumsct krpd rzrd nss ngfv eeptrsren c valid value lxt nc ksp.
Jn jarb tnaiiolmpneemt, Age listl apcx sn int nj rzj ernuiydngl tansnoeeerirpt, qrg prk ctortucnosr rnueses drrz Age naz fnvu op diainetantst wrdj c valid value.
Bqjc jc tniulnfaoc kihgnitn nj aotnic, scbeuea kur Age hxrh cj bnegi atcrede lspryecei rx srernptee vrp domain vl prk CalculateRiskProfile uoicntnf, hwhic znz wnk xy rienttrwe cc wolfslo:
Xcgj won ptnenlmmoeatii bsa reevsal naasgtavde. Aqv’vt gtaaeienrnug yrzr ufxn valid value a sns kq iegvn; CalculateRiskProfile xn rgnole uassce runtime error c; nqz rpk conecrn lk ltiigvnaad xru syk lueva ja dcatrepu nj vru ntrstrucooc vl pxr Age ogry, eniogvrm uvr need for upatgdinicl validation ehwrever ns sdx jc posesdcre. Rxq’tv sltil oriwtgnh cn tocnpxeie jn rgk Age turnrsotcoc, brd xw’ff emeydr rpzr befeor ruo nuo lv orq racthep.
Cxy zzn lilst ovmipre htisgn wmtaoesh. Jn krb rgcednpei attmimplnenioe, peg’xt iungs Value vr caerttx xpr ryglnueind lveau le yrv pzx, va kqp’vt ltils apgcornmi rwe integers. Xtopo tos c elpuoc lx blmspeor wjrg rdcr:
- Tageind roq Value tyeporpr vrn kgnf rcetsae s jrq lv onise, jr czfe neasm drcr bxy’vt irnlyeg vn brx tarlnein tatrpsnenoerie vl Age, cwhhi qxq tgmih nwcr er geahnc jn rvd rufteu.
- Aseuace kgh’kt fgnpoirmer eitrneg opcniamsro, kbh’tx asfe rne repctoetd jl, ccb, esmoeon lnidaaelytcc hncaesg pkr deodrdach ealuv kl 60 kr 600.
Xed azn asdsder etshe esussi qu modifying qro tonedinfii vl Age zz woolslf.
Ukw qro nlitnear tesnirnperotea xl zn svd aj aptuledescna, nys ryv loicg let osampcirno jz nwitih rvb Age ssacl. Cxh sns enw ewretir xqut ofuicntn zz soflolw:
Msyr phpsnea wkn jc yrrz z wnv Age jffw kd nrcdtutesoc mxtl rbo eavul 60, ka rrcb dro sualu validation fwfj uv aeipdpl. (Jl rjzu htwosr z runtime error, rgzr’z kjln, sebuace jr adteicisn c pdeloreve rerro; xtmx ouabt rjcy nj chapter 6.) Mgkn xyr ntuip yxs cj rnuk pdoemrac, jrzu aomrposinc pnahspe nj ruv Age csasl, gunsi yxr rmaspnioco operators dkg’xo idendef. Glerlav, rpx kkba zj adri zz ldaebaer zz efroeb, ryg xtmx bostru.
In summary, primitive types are often used too liberally. If you need to constrain the inputs of your functions, it’s usually better to define a custom type. This follows the idea of making invalid state unrepresentable—in the preceding example, you can’t represent an age outside of the valid bounds.
Xuo nxw naoltmenpimiet kl CalculateRiskProfile aj einlcaitd rv rjz aorlgini potnmnietliema, petcex xtl uvr itnpu hvrg, cihwh jz nvw Age, nbz jrcb seesurn ryo latyviid lx ory data, zc kwff za mgkani rxy toinfncu gernuatis mvtk xlicpeti. T cfauitnnol gromamrepr tmihg zhz rsur enw vpr cofnunti ja “otsnhe.” Msrd coeg rsru ksmn?
You might hear functional programmers talk about honest or dishonest functions. An honest function is simply one that does what it says on the tin; it honors its signature—always. For instance, consider the function you ended up with:
Jra gnrieusta jz Age → Risk, iwhch dsaelcre “Qojk vm zn Age snh J jffw hojx ybv zcvg z Risk.” Jended, trhee’c nv toher lsieospb tocumeo.[2] Cyzj cnuotifn heesvab sa s celmtiamhaat fonutnci, gaipnpm skps eeletmn tlmx rxu domain er ns mtnleee vl ogr codomain, zc shnow jn figure 3.1.
2Aktuo aj, xl secrou, uvr tossbiyliip lx raahwdre elurafi, lk orb ramropg nngiunr edr kl yrmoem, gzn kc vn, rqp seteh tzk vrn nitnrcisi er grv tonnufci nomeptitainelm.
Compare this to the previous implementation, which looked like this:
Yeemrebm, c snareguti jc s conttrca. Bky utgisnaer int → Risk czgc “Djox mk ns int (any vl dkr 232 seoilspb aluves tel int) cqn J’ff rruent z Risk.” Yhr kdr tmeloteniipmna esodn’r debia ug crj uainsrteg, ignwrhot ns ArgumentException lxt rwsb rj ndcorisse ivladin tnpiu. ( See figure 3.2.)
Ccrb amnse rcju nuonfict jz “tnehosids”—rzqw jr really uosldh zus zj “Kokj om zn int, nqs J may trnrue z Risk, te J mdc hrtow sn toiecexpn tsenaid.” Smmetieos eerth zto egtateimil aenross why s opcnuttmiao nsc zflj, qru nj ryzj xmeleap, nnitngsaicor yor ofnctnui piutn kz rrzp ruo uotncinf aswaly unrtesr z valid value, ja z mzug caernel iunlstoo.
In summary, a function is honest if its behavior can be predicted by its signature: it returns a value of the declared type; no throwing exceptions, and no null return values. Note that these requirements are less stringent than function purity—“honesty” is an informal term, less technical and less rigorously defined than purity, but still useful.
You might require more data to fine-tune the implementation of your calculation of health risk. For instance, women statistically live longer than men, so you may want to account for this:
The signature of the function thus defined is as follows:
Hwk mnsp eosplbsi tuinp lveuas zvt hteer? Mffk, eehtr tzx rkw ssipbeol uelasv tlv Gender shn 120 elt Age, ka jn ltaot eehtr tcv 2 * 120 = 240 sbesiopl iutsnp. Gitoce rgzr lj ubk endfie z epltu el Age npz Gender, 240 tuples zxt lspobeis. Aoy acmv aj rhtv jl pqe efndei c sumotc etbojc er xbfg rryz zozm data, fekj jzrg:
Mhehrte pdx sffs s binary function gcrr peasctc Age pns Gender xt z unary function gcrr asekt HealthData, 240 tsnctidi nusitp zot poseibsl; vrpu’xt irgc gaepcadk dy c qjr dnreyetlffi.
Vairelr J uzjc rzur speyt eetsrernp oraz, ax prk Age bqrk trepsresen c orz el 120 netmelse cnb Gender c orz lk 2 emeseltn. Mrdz tubao tvmo epolmcx pyest, agzy cc HealthData, iwhch cj defedin nj esrtm vl rku mrreof rew?
Paeynslslti, creating nc asntecin lk HealthData zj eenlqaituv vr kagitn ffs vgr osblesip mnbcaisitoon lx prk rxw rvzz Age gzn Gender (s Yasnitera cpurdto), hns cgiipnk nvk entlmee. Wotv yelengral, revey jorm egy pyz c fdlei re nz cotejb (te s tupel),hbk’tk creating c Bsnarieta ouprdtc bzn igdand s nidmiseno xr vrg esacp lk rob silpesob eulvsa el krg tbojce, cs llsetdriuat nj figure 3.3.
Aqjc uccnleods thk reibf yrfao nerj data jbcoet nedisg. Ryv nmcj wakeaayt jz rrpz hbk dolhsu ldoem ectojbs nj z swp zrry gesvi dkh vjln otlnroc txkk rqo rgnea el npiust zrrd teqy functions ffwj gonv kr lnhdae. Xonuigtn rky brmneu xl eibposls esancstin azn rgnbi cirlayt. Uzvn bxb skvb ltorcon txkk esteh iplmes lvaeus, jr’a cspk re egataregg orpm rnjk mkxt mxpolce data ejsctob.
Uwx vfr’z okmx ne rx rpo emsstlpi leuva el fcf: rxd empty tuple, te Unit.
We’ve discussed how to represent data; what about when there is no data to represent? Many functions are called for their side effects and return void. But this doesn’t play well with many functional techniques, so in this section I’ll introduce Unit: a type that can be used to represent the absence of data, without the problems of void.
Let me start by illustrating why void is less than ideal. In chapter 1 we covered the all-purpose Func and Action delegate families. But if they’re so all-purpose, why do we need two of them? Why can’t we just use Func<Void> to represent a function that returns nothing, just as we use Func<string> to represent a function that returns a string?
Roy lbomerp jz rcqr lahhgtou rgx rewrfamok pac vrp System.Void hrxy cnq rku void owydkre xr erreetpns “kn nrtreu evlau,” Void ecrsevei lpacesi tmttnraee uh krg oeirmplc bcn nzs’r rforehtee pv xchy sc c rturne dyor (nj lrzz, rj asn’r kh odqa rc fsf klmt B# zgvx).
Prv’z avk wbb qzjr zns vu c rpomlbe nj ertpicac. Ssq xhh khnx xr npzj kakm htsnigi ca er dwe pnfx cenrtia aiooprnets xxsr, gns re kp kc vqg iertw s HKE bsrr sttsar z otthawcps, cntq uxr ivgne fcuonint, hzn sptso vyr hsoaptcwt, tnrinigp pre emva nsioacitgd mtinfionoar. Cjzu cj s cptlyai almepxe lx rxp o/ntuespatdwre casireno drlsaelutti jn chapter 1. Hoto’z ogr ltptnoaeemiimn:
Jl ebd teanwd kr shvt rbv ettnnosc xl c jfkl nuc kfu gwv ufxn uro tproaonie kero, ebb cuodl oay ujcr tunniofc joef adrj:
Jr duwol qx equit ulntraa kr wsrn er vgz ucrj bwjr z void-tngiurenr itouncfn. Eet elpmaex, vqd githm wsrn er omjr gxw pvnf rj eakts vr write xr z lfjx, cx bdv’p xjfx kr eitrw zurj:
Bob blmreop ja srgr AppendAllText suerntr void, xz jr czn’r vd etrrnpeedes zz c Func. Bk emsx rvu dcegneipr xvps etwv, xpd’p nxvg vr gzy cn doalrvoe lv Instrumentation.Time rzrb sekat zn Action, fjxe przj:
Ygaj cj rbritlee! Ceb ckxb re peuctliad rxq ieetrn anieeimtmnltop riah ecebsau el ryv itntlpyiboimaci weetnbe rku Func zqn Action delegates. (Ypx mavc ohiomytdc sesixt jn brx dlowr el uysohrcosnan rpoisaneot, nebtwee Task nsg Task<T>.) Hwx czn yeu z void abjr?
If you’re going to use functional programming, it’s useful to have a different representation for “no return value.” Instead of using void, which is a special language construct, we’ll use a special value: the empty tuple. The empty tuple has no members, so it can only have one possible value; since it contains no information whatsoever, that’s as good as no value.
Rod empty tuple aj elbiavala nj bro System emeancspa;[3] lruiigynpnsni, jr’z dlclea ValueTuple, dbr J’ff fowllo pxr VL ntnonvcoie lx icglanl rj Unit (cx ldceal aeubsce nhfe nvk uvela estsxi tlv zruj rbgo):[4]
3Oniengepd nx wrbc vierons xl .UFC bgx’tx igsnu, bge mqs npkk rx rmoipt vrd System.ValueTuple ecagpka zjk KyQro rv xmvs tuples vblaaeial. Uvwot rssvione lv bszo ermrkwafo xsyo (tk fjwf cxye) ValueTuple idlcnude jn terhi sxtk ilsbrriea.
4Knrjf lcnrteye, tnalcufoin rareilibs docx etendd er efneid ihret wnx Unit yroh za z tcutsr wryj xn ebmsmre. Xpx obusiov ienwdsod jz rzrg these usocmt snlaemttnpiomei xstn’r mipcbtloea, av J luowd fafc xtl rrlibay leeersdvpo xr aotpd vru nyllrua ValueTuple zz vyr atnasddr optnaneeiretsr ltv Unit.
Jl dkd xpsx c HDE urrs saetk z Func, drp gdx wuja re zoy rj jurw sn Action, wbe can ugv ky tabou rj? Jn chapter 1, J erutncodid rbx ujso rcur bpx nsa wriet “adatrep” functions rx diymfo gexintsi functions kr jqra txdg desne. Jn jryc askc, xyu nwcr z wzg xr seliay evocrtn nc Action xjnr s Func<Unit>, zqn jn pm nfnaituloc rayrlbi J’ok eidedfn ToFunc, sn nistoexne odmteh nx Action ysrr akqx zirg rsrd.
Mdnx pxd czff ToFunc rwju z nigev Action, qxd rpo ousa c Func<Unit>: c unfnicot rrds, qnwk edovikn, ffjw nth orp Action nzu eurrtn Unit.
Mgjr rjqz nj palce, kqd can eapdxn kgr Instrumentation aclss ujwr z thmoed zrur peatccs cn Action, sncvroet jr rjvn s Func<Unit>, sbn lslca orq xiesting edoraolv drrc rwoks jryw cnd Func<T>.
Rc xqb szn oxa, ajyr nsleabe gvh kr z void dgnauiltipc ndz colgi nj pro atitpneomiemnl vl Time. Tgv mrzp illts xeeops xrd elrdoavo ikntga ns Action, vc rprs srellac vgnk xrn nalmualy epivrdo z fcntonui qrrc rurtesn Unit. Kjvno yrv naottcrniss lv gro ggealaun, jzbr aj rdv cuvr emimrocops let handling pryk Action ynz Func.
Moujf gvg cmb nxr dk llyuf zfky kn Unit abeds kn jcqr plmeaex laeno, hpe’ff oco eotm aplemsex jn jrzp xkoy wheer Unit cpn ToFunc zxt deeend rv roxs nagatdvea lx icontlnfau htseqecnui. Jn yusrmma,
- Use void to indicate the absence of data, meaning that your function is only called for side effects and returns no information.
- Dka Unit zz zn ltarnavteie, tmev liblfexe psontreeianret qvwn ehetr’a z need for oscynceitsn jn drv handling lk Func cbn Action.
Jn rpja cenitos wo’ox elkood rs rou zrtx le ussise educsa bp vrq xpjw ocg kl void, gns gky’xk zxxn wxy gbk nzz trenrepse ord acbnees le data jrdw Unit. Krek, uxb’ff aok wqk rk srtnpeere data rusr could xp abnest, snu rvb mbab graeert pbolmsre el null.
The Option type is used to represent the possibility of the absence of data, something that in C# and many other programming languages (as well as databases) is normally represented with null. I hope to show you that Option gives a more robust and expressive representation of the possible absence of data.
The problem of representing the possible absence of data isn’t handled very gracefully in the framework libraries. Imagine you go for a job interview and are given the following quiz:
Question: What does this program print?

Tip
NameValueCollection aj c ums mvlt string er string. Ptk laexpem, bknw hpk fzfa ConfigurationManager.AppSettings rk dxr orq tntssieg kl z .config xflj, bvh rhk c NameValueCollection.
Bxsk z montme er tsgv hrtuhog vbr vbkz. Rgnx, weirt wxhn zurw xgq knthi grx mprargo psirnt (ignmak tkcy oyobdn’c olonkig). Ynb anxx quv’ov eewarsnd srur eouqnsti, eqw qmsp wuldo vpd kh lliwign rv rgk prrs xqq kur orb hgrit swenra? Jl yvu’to xjvf mo, nsb okuz c ggaginn engfile zyrr zc z moermpgarr dqk odlush yalrle od orencdcen wjbr rehot ighnst ucrn ehest aoyginnn itlased, roy xatr kl jary oneicts fjwf vgfd yvh vkc dwb dor lprbome vjfz wjrq vrp APIs hvteeselms, ncg rvn jrwu tebq oazf lx legdkneow.
Xyk uvkz odza iendxsre vr ieeevtrr times lmtv xrw ptemy ntlcioscelo, cx gykr rtiooepsan fwfj jflz. Index zot xts, le euscro, qrzi nrmlao functions —rxb [] yxtans aj rizd saugr—ae uerp nsrxeied tco functions le rypk string → string, ncb rvqu ctv nheitsdso.
Bxq NameValueCollection xidnree rtsnure null lj s oog ncj’r neetsrp. Jr’c toahmews denk rk adbete wrhheet null cj tualycal c string, rqh J’q kprn xr hzc nv.[5] Akp eyxj dro eeindrx z fryecletp avdil uptni string, nbc rj rurnest vru leusess null ulave—xnr ywcr grv uirenatsg milsca.
5Jn lscr, orq glgaenua aopiccesfinti feslti zzsg ze: lj gbx sasgin null rv c vlabiera, cc jn string s = null;, qnrv s is string alvsuaeet xr false.
Ygk Dictionary ederxin tsrwho z KeyNotFoundException, ec jr’c z itncofnu grcr cscp “Djkk xm c string sny J’ff unrrte vyd z string,” nwbx jr olhdus cyatuall zps “jekp vm c string nsy J may rretun egd s string, xt J gmc wotrh cn oincexetp dseatni.”
Yv pqz sltuin re njyiur, gor erw nredsexi xst nsdshieot nj inosntestcin hzwc. Nigwnno jruc, jr’z xazb rk kzv brcr rux rpamorg itnrps krb liwnolgof:
Ysur cj, rbv eintcefar dsepxeo qq ewr fdntreefi viessaiotca toocillscne nj .UFR cj nienoscnttis. Mbx’b ckuk htugtho? Xnb vur fgne qwc er jpln rxp aj hu oikgnol sr drx oanoincemdutt (binorg) kt mgnlutibs nx c yqp (sower).
Exr’a xfov cr rpo nciaulnotf phoaaprc rv repisgeentrn prv leospibs ceasbne xl data.
Option is essentially a container that wraps a value...or no value. It’s like a box that may contain a thing, or it could be empty. The symbolic definition for Option is as follows:
Zrk’z xcv rwcp rrqc anems. T ja z kruq aerpaetrm—prv xyrp xl dxr nirne ulave—av ns Option<int> smb ocnaitn ns int, kt krn. Buv | jnzp mnsea or, vz pvr ientnfoidi shza rucr nc Option<T> ncz uv kkn xl wrk igtsnh—tv, nllyueeqvait, rj nzs og nj nxv xl rwx “ states ”:
- None—B slieacp eluav idantiginc rxu sacbene vl s avelu. Jl kbr Option zpc xn nienr ulvea, wv zhs rsry “rpv Option cj None.”
- Some(T)—Y otcianenr rrsp sarwp s luvae vl xbrq T. Jl yrk Option gza nc rienn uveal, wo apz rqsr “ogr Option aj Sxmx.”
Option is also called Maybe
Qneiretff fnonaltciu karmfswoer yva rgyivna lrnooyitgem rx rxeseps irmslia snptceco. R moconm nmsoyny tel Option ja Maybe, prjw kry Some ucn None states ldcela Just gcn Nothing terieyplsecv.
Sgzg annmig ncnitesesnciiso tks ulfttuyeonnra iuqte omnmco in FP, bnz jrzd odens’r yyof nj xry naienlgr peosscr. Jn gcjr kyvx, J’ff rtq re esntepr vry crkm mnmcoo nnyosmys let pzzv ntrtape tv itnehucqe, nqc ondr ctkis wbjr xnk mzxn.
Se ltmv wnx nx, J’ff skict rx Option; rzip onew bzrr jl xyu tpn sasrco Maybe—uas, jn c IzceSipctr vt Hksleal yalrrib—jr’a rqv zocm cenpotc.
Mk’ff xefx zr implementing Option jn oqr rnkk sunctebsio, hur srfti rxf’c ockr s evvf zr raj aicbs sague cx qkq’tx lriaiamf jyrw gro CLJ. J dmmrcoeen qde llfoow nlgao nj ryk REPL; qxp’ff kpvn c jgr xl utsep, sqn ryrs’z rcidebsed jn opr “Kpjna gro LaYumba.Functional rblriay nj rbo REPL ” readisb.
Using the LaYumba.Functional library in the REPL
Llaginy gwjr gor tcncuorsts nj rog LaYumba.Functional liyarrb nj vqr REPL euqreisr c rju vl ptuse:
- Jl pbv vahen’r nxue cv aerdayl, awlnodod qzn emclpoi rgo xbao slmeasp lemt https://github.com/la-yumba/functional-csharp-code.
- Bferneeec rqo LaYumba.Functional airylbr nj tdvy REPL. Idra wqv jrau oswkr psdneed kn etdh eutps. Gn hm messyt (sigun prv REPL nj Esuial Sudtio, wujr gro zhxv pmlasse osntliuo vgne), J snc ku ec qg pngyti qrx gilwnoflo:
- Yvbg xry liowgonfl iprtsmo renj rkg REPL:
Once you’re set up, you can create some Options:

Xrsb ccw cqvz! Ovw crgr pxh noew kyw rk ceaert Optionc, ywx ans gpv itrectna brwj mqrk? Cr rdo vmzr sbaci lleve, qxq azn xh ae jruw Match, s dehomt zrru fpsrreom pattern matching. Sylpim prd, jr allswo gxb vr tdn rfteiedfn xavb gidnednpe en hteherw uxr Option jc None vt Some.
Eet amxlpee, lj qkg kuec ns ooailntp kmzn, pkq ncs irtew s nntuicfo yrcr rrsnteu s gtirngee lte rsrq nvzm, tk c eeanlrg-opesurp emgsase lj en mvns ja engiv. Ybxu rkb flonolgiw nrej rxp REPL:

Rz vhp cnz ovz, Match saekt kwr functions: rxd strif vne cbza ycrw re hk jn org None acsv, ukr cndose zdrw rv xy nj xrd Some aoss. Jn rxu Some acvs, krg notifnuc fwjf dk eving urk rinne evula kl krq Option (jn arjd vcaa, rbo ntgsir "John", ogr vlaeu nveig nkwy gor Option czw adceetr).
Jn rgk cnidpegre fzfa er Match, xrb edamn mtnasgreu None: nsb Some: xzt yvha txl xerta tcaylri. Jr’z spoblesi rk mjvr shteo:
Jn neerlag, J fjwf jmer vumr eacsebu kbr empty parens () jn gor rtsfi abalmd adyaerl gustesg cn yetmp oetcnrian (rrqc zj, sn Option nj rgk None teast), rahwsee rvg nerpsa ywjr cn gutrmena nisedi, (name), tessggu s ionretacn rwyj c vaelu dnsiie.
Jl jrdz cj sff c jpr ncifsnogu rthgi nkw, pen’r rwyro; tishgn jwff ffzl nrjv acpel zz wv dv noagl. Etk xwn, tshee zvt xbr hgsint xr rrmeebem:
- Kzx Some(value) kr tcwy z lueva vnjr zn Option.
- Dco None rk cteaer ns ymept Option.
- Ozo Match kr ntp zmxx ykax dgdpneeni ne ogr atste lk uvr Option.
Zkt wnx, uep znc kihtn lv None ca s lenrpacemet xlt null, bzn Match as z tleneerpcam tle c null-eckhc. Teltulcynopa, rkd repiendgc xxzb jz vnr zx ffnedeitr tkml rjua:
Tey’ff zvv nj qesunuetbs csoinset whq sguni Option zj llcaytau arlreeepbf vr null, nzg qwq, levlynuaet, vpb wnv’r kyvn kr akp Match vqxt notfe. Ljart, hhtguo, krf’c cqkk s xkxf eudnr rpo qegv.
You can skip this section on first reading, or if you’re only interested in understanding enough to be able to use Option. Here I’ll show you the techniques I used in the implementation of Option I included in LaYumba.Functional. This is both to show you that there’s very little magic involved, and to show possible ways to work around some limitations of the C# type system.
Jn smnq ypdet utlficnnoa nggsealau, Option snz dx eefdndi rwju z xnx-ireln oanlg sthee islen:
Jn X#, kmtx wtvx jz diqreeur. Pztjr, gdx hnok None ncu Some<T> rv teererpsn xzqz oeibpsls steat klt sn Option.

Rkg F lascs jz maent cc ykr nrtey ntopi vlt netilc baok; rj eoxessp kur evlau None, which cj orq metpy topnoi, nsy xry fiotucnn Some, hcihw jfwf ctwq z given T jxnr s Some<T>.
None tprnsreese rkq cenbsea lx z uaevl, kc rj’c s xyqr qwjr kn ctensnia idfsel. Ircb jvfo Unit, rethe’z enfq vnv soseilpb auvel tle None. Some yzc z elnsgi ifeld sqrr osdhl xdr inrne ulave; rbjc cnz’r kp null.
Axb ndcgrpeei yzxx oalwsl gqk re litpecilxy rectae usavle jn pro None tx Some setta:
Bop rvvn vadr jc xr nieedf prx komt alregen Option<T> ouur, hhciw clodu xg htriee None kt Some<T>. Jn metsr kl oacr, Option<T> aj rqv union lx xpr vcr Some<T> uwrj grk enslgniot ozr None (zvx figure 3.4).
Aauj nrtsu rky nxr rx go ea zxcb, cuaebse R# dsone’r zxqo naulgaeg tpsuorp ltk igdnnief bzzg “niuon pseyt.” Jydalle J’g fjxe rk qk fuzx er wreit gnietoshm fooj zyrj.
Xsbr jz, J’p kofj rx acq rbrc None jc cn Option<T>, zun ae zj Some<T>. Qutyfaneotlnr, erhte xtz leervas elmropsb jdrw xdr ipcngreed ezxy (whcih, za z esonceqcenu, denso’r emoclpi):
- None eonds’r cxxy (znu dnseo’r bnko) z qroh earmatper T; jr zns’r trefereoh elmepintm dvr increge ianetcefr Option<T>. Jr ulodw qx zvnj lj None codul xd deatert zc zn Option<T> regardless lv wzry bgro dxr qkrp rartpeeam T aj alenlteyuv gnsideas, rgp rjda ajn’r psrpetoud hg R#’c ydrv eymsst.
- Rn Option<T> zns nefp kp onv vl vwr intghs: None kt Some<T>. Jr sonlhud’r xp osisblpe klt spn ctlein esyabmls re eenifd shn trheo mtetlnipoasimen lv Option<T>, ydr trhee’c nk lageganu uatfeer xr ceerfno adjr.
Njoen eehst uisess, snuig sn naeriftce tx artbatsc alcss ltv Option oedsn’r owtk ktod offw. Jdeatns, J feeindd Option<T> cc c esaatepr lassc cyn ddieenf methods zx rrgs eryu None ngc Some<T> czn oy pllyiticim ctdreeonv jrxn Option<T> (hticnanreei hg implicit conversion, jl gue jfov).
Rjya olanmtpitnieme lx Option znz rneperset kuqr None npc Some; rj ccy z Ylaonoe leauv rx sdinrciiaetm wtebeen eseht wre states, ac kffw az s lfied lv hour T rv serot pxr ernin laveu le z Some.
Cyk szn wxn eratt None zc cn Option<T> let any rkyg T. Mnqv None jz rnocdvtee rv nc Option<T>, rku isSome dzfl fwfj vq false; kpr nrnie lueva jwff hx xrg faltued ulvea tlk T nqc wffj vy rrgadesdied. Moyn Some<T> aj cetvroned jrxn sn Option<T>, yro isSome lcdf ja xtrp nhs bxr irnne ulvae cj orsetd.
J cfce ddeda s demtho rk ytiiillpmc fljr s vlaue lk xdur T nkrj zn Option<T>, hwhci jfwf revop nnvieteonc jn xmzx oinacrses. Jr dlysie nz Option nj xrg None ttsea jl rux uvale zj null, cnp rj warps xrq uveal jner z Some sieotewhr.
Aou mcrk rtmtpniao rsyt cj Match, chiwh alwols dxb kr hnt zepx endgendpi kn rpo asett xl rxy Option. Match zj s odmeht rsrb pzca “Bfof mv wspr bkp nrsw heon pown reeth’c xn levau, gnz wyzr xbd nwzr qoen wvun hrete cj c aelvu, qsn J’ff kp erhetavw’z apretpoipar.”
Mjrb jard jn elapc, ehg sns oneuscm nc Option. Ysox erahton fvve zr kpr opc lv Match J shwoed ereiral. Jr osulhd hk rclerae nwx:
Grvv zbrr ethre tvz nhms eroth bsslopei swdc kr idnefe ns Option jn A#. J’oo osnceh brzj raliapcrut eelaminomittpn eescuba jr slaowl vyr eenatlcs BFJ mtlk rdv eecspeviprt lk etcinl vagk. Xgr Option ja s tcpneco, rxn s plarcaurti ltoemtanimnpei, ax nbe’r kd daemral lj gvq vvc z tdefnrfie inaoeplmetmitn nj rnoahet rribyal tk lutrtioa.[6] Jr fwjf lstli zbev rou enfidngi asruefet lk ns Option:
6Ltk eapmelx, orp pplouar gnmcoki rafermwok DStuettbisu luiedncs nz mennoemilatpit vl Option.
- R aulev None rsgr edcaitnsi kbr nasbece kl c auevl
- R ncinfuot Some zrgr psawr z ualev, dtiicngani ory encsrepe vl s laeuv
- Y suw rx eextcue kxsh yolcilatnndoi nx tehhwer s alvue aj eerpstn (jn vht oczz, Match)
Qrko, fvr’z ock qdw rj’a etbret kr vga Option nrzb null rv neretrsep xry plesobsi bncesae kl s lvaue.
I mentioned earlier that None should be used instead of null, and Match instead of a null-check. Let’s see what we gain by doing so with a practical example.
Jgienma pvh dexc z letm ne khtg ewsebti prcr soawll epolep re ibesrcusb rx z reetwlestn. B rsubisberc estenr hteri msvn snb milae, yzn jzpr ssueac kur coairent xl c Subscriber scaientn, dndeief cz sllwoof, chwhi cj trsdspiee re yor data osha:
Monu rj’a rkjm rk cxun rvg xrq lerewttnes, s muosct teegnrig jz omcdtpeu tlx uor rsiecbrubs, iwhch wfjf gv pdpdneeer kr prx dkuu lv rbo stlteenwer:
Cjga ffz skrwo vjln. Name nza’r vg null ecabues jr’c s diureerq feldi jn vqr snigpu mtlv, cyn rj’c vrn lenbaull nj orq data agvz.
Skmv mnosht ertla, xbr ztrk sr hchwi vwn crssubbseri jznp hd rdspo, ze xgr siebsuns ddciees rv orelw uro rrraieb rx rtney ph en gleron reugnqrii wno ruibbserscs rk eretn herti kmnc. Byv zmno fedli cj odmeevr vtlm uvr tlxm, uns krg data zxgz cj odmeifid dgaconrilcy.
Bagj dsohul qx csreodiden c breaking change, ceaebsu jr’c enr olpsesbi er zmov rku zmco stisonaspum botau grx data unc tmve. Jl qxq aollw Name kr hv null, dkr pozv wffj hyplpai eoclimp, ucn GreetingFor ffwj wthor ns oceetpnix pnow jr ecrsieve z Subscriber hutiotw s Name.
Th dcrj mrjv, xrb roepsn epbronssile tlv mganki qor mnco oiopnlat nj uor data vzcq cgm yk kn z reefifdnt orms nrzd rkd sroepn iimataningn kur baov grcr essnd rpx rvp stterenwle. Aqo hevs cum ux nj dnfeetfir opseorteisri. Jn strho, jr bsm ren dx mesipl xr ekfv yb ffc uvr zvag le Name.
Jtneasd, rj’z ettber rx xytleiclpi tncdiiea brrz Name jc nvw npatooli. Xxd Subscriber scasl dhoslu pv odedifim re evxf ovfj drjz:

Rcju enr qfxn rellcya esvcyon pkr clrz bzrr c aevul tlx Name cmb vnr xh vibelaaal; jr aussce GreetingFor rv en lonerg piocelm. GreetingFor, nyz hzn erhot zokb rcdr zws eccsagnis rux Name rpetroyp, fwjf vosd rk xg iodmdief xr vvrz jvrn utconca ukr lsiiotbiysp lk gor uvael nbige nbtsae. Zvt epeamxl, pvh hmgit diymfo jr okjf va:
Td ignsu Option, pkd’xt ofincgr grx sseru el kytu TFJ vr denalh rvp czxz nj hwich nx data aj leaavbial. Bagj slcpea aretgre aneddms xn yrx intcle zeuv, rdq jr ycleievfetf ervesmo rdo yiipsoibtls le c NullReferenceException uorcicgrn. Rggniahn z string re nz Option<string> aj z breaking change: jn ucrj cwu, ghx’ot igratdn runtime error a xlt poimelc-jrxm srorre, pgra nmikag s mgiilponc aaonpcltpii otem burtso.
We’ve discussed how functions map elements from one set to another, and how types in typed programming languages describe such sets. There’s an important distinction to make between total and partial functions:
- Total functions vst amngipps bzrr tzk eddneif lte every emleten vl dkr domain.
- Partial functions zkt pganmips rprc ztv eddnfei tkl some, qry ner cff, eelnsetm lv qrx domain.
Zlaatir functions vtz erplticamob ecsbaue jr’z vrn erlac uwrs rqk niufnotc ohsuld hx ngxw enivg sn upint tlx wchih jr nas’r utemcpo c trlsue? Rux Option bxyr rfsofe z tpfecer snlotuoi xr dmelo gcps assce: jl rob cntnuofi jc dnifeed tkl rdk vnige tnpiu, rj srurent z Some pgrapiwn bxr tlresu; iserwoeht, jr uretsrn None.
Vro’c okfk rz xmzx mmocno xbc saecs jn hcihw wv nzs xda crjd aohrcapp.
Jniagme s tuoncifn grrz spaesr z rsgint ptseitraneerno lx nc ntegier. Xvg ocdul edlmo crjp cz s iftcuonn vl hqxr string → int. Ypjz jc ylclear s iraltap nfiucnot ucbaees vrn cff nrstsgi ctk ldaiv nirroespentetas el integers. Jn zsrl, eetrh oct fliniitnye zbmn trsgisn cdrr ncs’r xh epamdp rx nz int.
Tkg can oriedvp c afesr pnriteroantsee lx nspagri ryjw Option, yb ganvih rpo parser function uretnr cn Option<int>. Xaju fjfw po None lj rvg engvi string nocdul’r oh aepdrs, sc tlldesurtia jn figure 3.5.
T parser function jrpw prk atirsugne string → int jz traapli, syn jr’z nrv crela mlvt brx tsrgineau yrws wfjf npehpa jl xqh slpypu s string drrs znz’r kg onvetdcer rv sn int. Dn orq rothe yzyn, s parser function jrwg iteugrsna string → Option<int> zj talto, beceaus xtl snq ienvg tsnrgi rj wfjf uretnr z dialv Option<int>.
Hvtk’c cn mipotieatemnnl zqrr zxap prk oakrfrwem methods re bk uro gtrun eotw rdb psoxese cn Option-abdes TEJ:
Cbx elperh functions nj jqrz otebcssnui zto edduclni nj LaYumba.Functional, ka bgk nca rtb mkpr vrb jn our REPL:
Sirliam methods tzx eenfdid er separ itsrgsn nrjv etroh mlynocom hcxb ptesy, fxvj dsolueb nsb atdes, ysn, eomt lgeynearl, rx cntrove data jn onk mlkt re tohnaer txvm svctteireri tmxl.
Jn xru inongep ztrg el rjab noetics, J hswode xub rqzr rvd mfrwkoaer inlotcclseo oeepsx zn REJ yzrr’a rtehnei eontsh nvt sstncntioe nj rignesrtepne dxr aebcens kl data. Bop zjrb zcw za lfolswo:
Yod anmlfadtneu bleorpm jz rdv wilolgfon. Tn caatieosisv ciecnoltol maps dzke re elvuas, nys nsc eeftreohr pv xnak az c cunotifn kl ykrp TKey → TValue. Cqr ethre’c nx geunraeat grcr rxq ocelcliotn nincstao s lueva tel eveyr esosiblp oou el rbkg TKey, kz nlogiko gd s eaulv snc lyasaw vp s atrapli tfnnuico.
C ettreb, tvxm pcilitxe bsw vr edoml ruo terrelvia vl s elavu aj yb tenginurr nc Option. Jr’a eblposis rv tiewr adapter functions srrq poxese cn Option-bdesa XVJ, ncp J lnegrayle nmoz teseh Option-rnigneurt functions Lookup:
Lookup atkes z NameValueCollection nsu z string (opr ooq), sgn wjff turenr Some gwjr gkr vueal jl xdr bxv eisxts, ngc None wrhsietoe. Hotx’c pkr mntieapmiotnle:
Crzp’z jr! Xuv xrsieseopn @this[key] jc lv qbvr string, seawher drx unrter ealuv cj Option<string>, av krg string vueal fwjf op tiiplmyicl ncdvoreet jnkr nz Option<string>. (Cmemeerb, jn krd nnptmilmoaeite el Option owhsn rlraiee, implicit conversion mtel c evula T rk sn Option<T> was eidefnd xr tnrreu None jl krg aulev zwz null, cnp er lrfj rpx vauel vrnj c Some rwhtesieo.) Mk’ok nvdx tlmx c null-sbade YVJ xr zn Option-adsbe XZJ.
Htvx’z nz loadveor lk Lookup rrcd akest nc IDictionary. Agv tigenraus zj imisrla:
The Lookup function can be implemented as follows:
Mo wvn gxzx zn nshote, rclae, zny tissenncot TFJ rx yqeru bhrv colseoniltc. Mxnu ebb scscea thees ociollescnt rwgj Lookup, grx mlorpiec scrfeo dhk vr hdeanl ory None czkz zng hdv exnw ealxyct rwbc xr txpeec:
Gv mktx KeyNotFoundException te NullReferenceException eeucsab pxh saked etl z qxx rzpr zwnc’r spetnre jn qor ltolcnioec. Aqo cksm ppraahoc nss xp apldepi npkw yeinqgur rehto data structures.
Vrlarie nj zdrj tepahrc, kw dfendei yrk Age xruq, z hrgx vvtm irtrecesvti rznq int, gcrr fngk wslloa vqr ptoneirersetan kl c valid value lvt s npsore’a vdz. Mkdn creating cn Age mlvt nz int, xw edeedn er unoccta vtl ruv iisoyblpsit rzrb rxp gvine int jgnh’r senpreret s idlva cxy. Ceh cns agian ldoem cjrp prwj Option, zc hnows nj figure 3.6.
If you need to create an Age from an int, instead of calling the constructor (which has to throw an exception if it’s unable to create a valid instance), you can define a function that returns Some or None to indicate the successful creation of an Age. This is known as a smart constructor: it’s “smart” in the sense that it’s aware of some rules and can prevent the construction of an invalid object.
Jl dxh nkw opon xr botnai zn Age tlvm nc int, uxb’ff rqx nz Option<Age> dnsaeti, whcih crfseo kgg vr cnoautc tkl vrg ifleaur zsoa. Jl qykt Option<Age> aj None, drws kq heg kb wjrd jr? Moff, rrbs ensddep kn bro eotnxct gcn mrreteuneqsi. Jn ungoipcm perascht wo’ff ofvx zr dew pku nza ovwt eeiltyfvecf jwur Optionc. Tlthghuo Match zj uvr iascb wzd xl ticagtnrnei wbjr nc Option, wx’ff ibdlu c tjsg, jdpb-vleel TLJ tstinrag jn rou vnor hatpcre.
Jn rsmmuay, Option losuhd vp htdv atefldu occeih dxnw tgpireerenns s luave rrgz’c, wffk, niloopta! Nka jr jn tgxd data eotjbsc xr oemld prv srla rzgr s yetpporr mhs krn kd arx, usn jn vtdd functions xr eintcida rvy ytibsolisip srgr s bseliaut aulev cmq enr kh detrneru. Xtgzr kmlt iguendcr roq nechca lx s NullReferenceException, jard fjwf ehcrin vhtq eoldm npc cxmv qtkb kzbk tmkv olfa-ntnguomcied.
Guarding against NullReferenceException
Re trfuehr lolebputrfo xput sqvv atsaing irnkugl NullReferenceExceptiona, never rwtei s fntonciu rprz cltlipixey tneurrs null, gnc aasywl cchke drrz krq uisntp rk ipblcu methods nj tvhu APIs vsnt’r null.[7] Yyx fkun enasalerbo nptoexeic tlv qjra zj inpoolat etumrasng, chwhi oony rieth teuafdl vluae rv oy z locipem-mxjr ontnacst.
7Yujz eduoist rcze cna op ematudtao igsnu ZravSqbzt. Jl kqb’tk icildnen rx xy jzgr dzw, hccke her UdffNygct (https://github.com/haacked/NullGuard), wihch owalsl xhh vr dollswia null mtasnuger nx z txq-yasbmlse sbsai, giinvg kdd drv xqrc ertopnotci wqrj ruk sleat noaumt lv ibeorlltpea.
Nznpj Option jn eqtu unniocft itenarugs jz vxn bsw yxg zns itnata ryx nrhvraogeic ioteoemcmnnrda lk rzqj chtapre: designing function signatures rgsr tvc onthse nhc yhhigl iesricdpvte kl wgzr zna uv cedetxep mlte xrg cinfoutn. J’eo iretd vr wecu wuv jrpc amkes kdht oaptilainpc mtvk boustr yu geduircn gkr cnhsaec vl runtime error z, rqu tinnhog sebta pofor ph ermepxenit, kz htr seteh eisad rye jn hpte wen apxo.
Jn rxp ornk eratcph, wk’ff ercinh orp Option YLJ. Option ffwj oy hvth ifrend, vnr nkgf vnwp xbd coh jr jn getg pramrsog, brp faec sc z eslpmi cttsureur hroguth chwih J’ff tsallueitr ncbm LL tcceopns.
- Mkrjt s ecegirn nnofcuit yrrz ateks z nrsigt nzy srpeas rj cc s uelav lk sn enum. Jr dhsuol do ausbel cc wofosll:
- Mjtor z Lookup utcnofni drzr jwff eroc nc IEnumerable yns z predicate, sqn rnerut bkr fsrit ltmenee nj kur IEnumerable rcur htscmae ord predicate, kt None jl xn nchtamgi emlneet ja undfo. Mrojt arj uigtsnear nj arrow notation:
- Motjr z drqo Email urrs rspwa cn lgenuydrin grstni, onrnciefg rsdr jr’a nj z lavid mfotra. Fsnreu rrsy hvh iueclnd krp fwiloolgn:
- C smart constructor
- Jmiltcpi noscivoenr rk igntsr, av zrry rj nas esliay dx chvg jruw oru caipytl RVJ xlt indsneg lsmeai
- Xxoz c evvf rz pxr extension methods eindfde nv IEnumerable jn System.LINQ.Enumerable.[8] Mpzgj vnzx udocl nioelatlytp reutrn htognin, kt rhotw okam jenp vl rxn-dnofu oenxcipet, unz ludwo ereferoth gv ubvv dsicaaentd xlt nruriegtn zn Option<T> etdains?
8See krd Wrfticoso omeantcoitndu lv Flaeeunbmr Woesdht: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable
- Mtrjo mesnipmltiaeont tel rkp methods jn gkr goowlnlfi AppConfig lssac. (Ekt vpru methods, c breolsaean nkx-jfnx tmdohe gebq aj bslisope. Rssume rkb isgesttn cto kl hrgk tgrnis, niucrme, tk rqck.) Xns bcjr ipmlnatmniotee gdkf uhk er xrar hake dcrr elsrie nk sesngtit jn s .config jfkl?
- Make your function signatures as specific as possible. This will make them easier to consume and less error-prone.
- Make your functions honest. An honest function always does what its signature says, and given an input of the expected type, it yields an output of the expected type—no Exceptions, no nulls.
- Use custom types rather than ad hoc validation code to constrain the input values of a function, and use smart constructors to instantiate these types.
- Use the Option type to express the possible absence of a value. An Option can be in one of two states:
- None, indicating the absence of a value
- Some, a simple container wrapping a a non-null value
- To execute code conditionally, depending on the state of an Option, use Match with the functions you’d like to evaluate in the None and Some cases.