a tutorial on programming features in...
TRANSCRIPT
ATutorialonProgrammingFeaturesinATSHongweiXi
<gmhwxiATgmailDOTcom>
Copyright©2010-201?HongweiXi
Allrightsarereserved.Permissionisgrantedtoprintthisdocumentforpersonaluse.
TableofContentsPrefaceI.BasicTutorialTopics
1.Syntax-ColoringforATScode2.FilenameExtensions3.FileInclusioninsideATSCode4.FixityDeclarations5.StaticLoad6.DynamicLoad7.BracketOverloading8.Dot-SymbolOverloading9.Recursion10.Datatypes11.FunctionalLists12.FunctionalSetsandMaps
FunctionalSetsFunctionalMaps
13.Exceptions14.References15.PersistentArrays16.PersistentArrays-with-size17.PersistentMatrices18.PersistentMatrices-with-size19.PersistentHashtables20.Tail-Recursion21.Higher-OrderFunctions
22.Stream-BasedLazyEvaluation23.LinearlyTypedLists
II.AdvancedTutorialTopics24.Extvar-Declaration25.LinearClosure-Functions26.AutomaticCodeGeneration
Generatingadatcon-functionGeneratingadatcontag-functionGeneratingafprint-function
PrefaceThistutorialcoversavarietyofissuesthataprogrammertypicallyencounterswhenprogramminginATS. It is written mostly in the style of learn-by-examples. Although it is possible to learnprogramminginATSbystudyingthetutorial(ifthereaderisfamiliarwithMLandC),IconsiderthebookIntroductiontoProgramminginATSafarmoreapproriatesourceforsomeonetoacquireaviewofATSinacoherentandsystematicmanner.Ofcourse,therewillalsobeconsiderableamountof overlapping between these two books. The primary purpose of the tutorial is to bring moreinsights into a rich set of programming features in ATS and also demonstrate through concreteexamples that these features can be made of effective use in the construction of high-qualityprograms.
I.BasicTutorialTopicsTableofContents1.Syntax-ColoringforATScode2.FilenameExtensions3.FileInclusioninsideATSCode4.FixityDeclarations5.StaticLoad6.DynamicLoad7.BracketOverloading8.Dot-SymbolOverloading9.Recursion10.Datatypes11.FunctionalLists12.FunctionalSetsandMaps13.Exceptions14.References15.PersistentArrays16.PersistentArrays-with-size17.PersistentMatrices18.PersistentMatrices-with-size19.PersistentHashtables20.Tail-Recursion21.Higher-OrderFunctions22.Stream-BasedLazyEvaluation23.LinearlyTypedLists
Chapter1.Syntax-ColoringforATScodeThesyntaxofATSishighlyinvolved,whichcanbeadauntingobstacleforbeginnerstryingtoreadandwriteATScode.Inordertoalleviatethisproblem,ImayemploycolorstodifferentiatevarioussyntacticalentitiesinATScode.TheconventionIadoptforcoloringATSsyntaxisgivenasfollows:
ThekeywordsinATSareallcoloredblack(andpossiblywritteninboldface).
ThecommentsinATSareallcoloredgray.
ThecodeinthestaticsofATSiscoloredblue.
ThecodeinthedynamicsofATSiscoloredredunlessitrepresentsproofs,forwhichthecolordarkgreenisused.
Theexternalcode(inC)iscoloreddeepbrown.
PleasefindanexampleofATScodeon-linethatinvolvesallofthesyntax-coloringmentionedabove.
Chapter2.FilenameExtensionsIn ATS, the filename extensions .sats and .dats are reserved to indicate static and dynamic files,respectively. As these two extensions have some special meaning attached to them, which can beinterpretedbythecommandatscc,theyshouldnotbereplacedarbitrarily.
A static file may contain sort definitions, datasort declarations, static definitions, abstract typedeclarations,exceptiondeclarations,datatypedeclarations,macrodefinitions,interfacesfordynamicvaluesandfunctions,etc.Intermsoffunctionality,astaticfileinATSissomewhatsimilartoaheaderfile(withtheextension.h)inCoraninterfacefile(withtheextension.mli)inOCaml.
Adynamicfilemaycontaineverythinginastaticfile.Inaddition,itmayalsocontaindefinitionsfordynamicvaluesandfunctions.However,interfacesforfunctionsandvaluesinadynamicfileshouldfollowthekeyword extern ,whichontheotherhandshouldnotbepresentwhensuchinterfacesaredeclaredinastaticfile.Forinstance,thefollowingsyntaxdeclaresinterfaces(ortypes)inastaticfileforavaluenamed pi andafunctionnamed area_of_circle :
valpi:double
funarea_of_circle(radius:double):double
When the same interfaces are declared in a dynamic file, the following slightly different syntaxshouldbeused:
externvalpi:double
externfunarea_of_circle(radius:double):double
Pleasenote that a static file is essentially a special caseof adynamic file. It is entirelypossible toreplaceastaticfilewithadynamicone.
Asaconvention,weoftenusethefilenameextension.catstoindicatethatafilecontainssomeCcodethat is supposed to be combined with ATS code in certain manner. There is also the filenameextension.hats,whichweoccassionallyuseforafilethatshouldbeincludedinATScodestoredinanotherfile.However,theuseofthesetwofilenameextensionsarenotmandatory,thatis,theycanbereplacedifneededorwanted.
Chapter3.FileInclusioninsideATSCodeAs is in C, file inclusion inside ATS code is done through the use of the directive #include. Forinstance, the following line indicates that a file named foobar is included, that is, this line is to bereplacedwiththecontentofthefilefoobar:
#include"foobar.hats"
Note that the included file is parsed according to the syntax for statics or dynamics depending onwhetherthefileisincludedinastaticordynamicfile.Asaconvention,thenameofanincludedfileoftenendswiththeextension.hats.
A common use of file inclusion is to keep some constants, flags or parameters being definedconsistently across a set of files.For instance, the fileprelude/params.hats serves such a purpose.Fileinclusioncanalsobeusedtoemulate(inalimitedbutratherusefulmanner)functorssupportedinlanguagessuchasSMLandOCaml.
Chapter4.FixityDeclarationsGivenafunctionf,thestandardsyntaxforapplyingftoanargumentvisf(v);fortwoargumentsv1andv2, thesyntaxis f(v1,v2).Also, it isallowedinATStouse infixnotationforabinaryfunctionapplication,andprefix/postifixnotationforaunaryfunctionapplication.
Each identifier inATS can be assigned one of the following fixities:prefix, infix andpostfix. Thefixitydeclarationsformanycommonlyusedidentifierscanbefoundinprelude/fixity.ats.Often, thenameoperator is used to refer to an identifier that is assigneda fixity.For instance, the followingsyntaxdeclaresthat + and - areinfixoperatorsofaprecedencevalueequalto50:
infixl50+-
Afterthisdeclaration,wecanwriteanexpressionlike 1+2-3 ,whichisparsedinto -(+(1,2),3)intermsofthestandardsyntaxforfunctionapplication.
The keyword infixl indicates that the declared infix operators are left-associative. For right-associative and non-associative infix operators, please use the keywords infixr and infix ,respectively.Iftheprecedencevalueisomittedinafixitydeclaration,itisassumedtobeequalto0.
Wecanalsousethefollowingsyntaxtodeclarethat iadd , fadd , padd and uadd are left-associativeinfixoperatorswithaprecedencevalueequaltothatoftheoperator + :
infixl(+)iaddfaddpadduadd
Thisisusefulasitisdifficultinpracticetoremembertheprecedencevaluesof(alargecollectionof)declaredoperators.Sometimes,wemayneedtospecifythattheprecedencevalueofoneoperatorinrelation to that of another one. For instance, the following syntax declares that opr2 is a left-associativeinfixoperatoranditsprecedencevalueisthatof opr1 plus10:
infixl(opr1+10)opr2
Iftheplussign(+)ischangedtotheminussign(-),thentheprecedencevalueof opr2 isthatof opr1minus10.
Wecanalsoturnanidentifier opr intoanon-associative infixoperator (ofprecedencevalue0)byputtingthebackslashsymbol( \ )infrontofit.Forinstance,theexpression exp1\oprexp2 standsforopr(exp1,exp2) ,where exp1 and exp2 refertosomeexpressions,eitherstaticordynamic.
Thesyntaxfordeclaring(unary)prefixandpostfixoperatorsaresimilar.Forinstance,thefollowingsyntax declares that ~ and ? are prefix and postfix operators of precedence values 61 and 69,respectively:
prefix61~
postfix69?
Asanexample,apostfixoperatorisinvolvedinthefollowing3-lineprogram:
postfix(imul+10)!!
externfun!!(x:int):int
implement!!(x)=ifx>=2thenx*(x-2)!!else1
Foragivenoccurrenceofanoperator,wecandepriveitofitsassignedfixitystatusbysimplyputtingthekeyword op infrontofit.Forinstance 1+2-3 canbewritenas op-(op+(1,2),3) .Itisalsopossibleto(permanently)depriveanoperatorofitsassignedfixitystatus.Forinstance,thefollowingsyntaxdoessototheoperators iadd , fadd , padd and uadd :
nonfixiaddfaddpadduadd
Notethat nonfix isakeywordinATS.
Lastly,pleasenotethateachfixitydeclarationisonlyeffectivewithinitsownlegalscope.
Chapter5.StaticLoadInATS, static load (or staload for short) refers to the creation of a namespace populatedwith thedeclarednamesinaloadedpackage.
Supposethatafilenamedfoo.satscontainsthefollowingcode:
//
datatype
aDatatype=
|A|Bof(int,int)
//
valaValue:int
funaFunction:int->int
//
Thefollowingstaload-declarationintroducesanamespace FOO forthenamesdeclaredinfoo.sats:
staloadFOO="foo.sats"
Theprefix $FOO. needstobeattachedtoanameinthenamespace FOO inorderforittobereferenced.Forinstance,thenamesavailableinthenamespace FOO areallreferencedinthefollowingcode:
vala:$FOO.aDatatype=$FOO.A()
valb:$FOO.aDatatype=$FOO.B(0,$FOO.aFunction($FOO.aValue))
Ifthefilefoo.satsisstaloadedasfollowsforthesecondtime:
staloadFOO2="foo.sats"
thenfoo.satsisnotactuallyloadedbythecompiler.Instead, FOO2 issimplymadetobeanaliasof
FOO .
Itisalsoallowedforfoo.satstobestaloadedasfollows:
staload"foo.sats"
Inthiscase,thenamespaceforthenamesdeclaredinfoo.satsisopened.Forinstance,thefollowing
codeshowsthatthesenamescannowbereferenceddirectly:
vala:aDatatype=A()
valb:aDatatype=B(0,aFunction(aValue))
Letussupposethatwehavethefollowingsequenceofdeclarations:
valaValue=0
staload"foo.sats"
valanotheValue=aValue+1
Doesthesecondoccurrenceof aValue refertotheoneintroducedinthefirstdeclaration,oritrefersto the one declared inside foo.sats? The answer may be a bit surprising: It refers to the one
introducedin thefirstdeclarationasabindingfor theoccurrenceofanameisresolvedinATSbysearchingfirstthroughtheavailablenamesdelcaredinthesamefile.
Chapter6.DynamicLoadIn ATS, dynamic load (or dynload for short) refers to some form of initialization of a loadedpackage.
Supposethatafilenamedfoo.datscontainsthefollowingcode:
//
valx=1000
valy=x+x//=2000
valz=y*y//=4000000
//
extern
funsum_x_y_z():int
//
implementsum_x_y_z()=x+y+z
//
Clearly,thenamesx,y,andzmustbeboundtosomevaluesbeforeacalltothefunction sum_x_y_zcan be evaluated. In order to create such bindings, some form of initialization is required. Let usfurthersupposethatafilenamedfoo2.datscontainsthefollowingcode:
staload"./foo.dats"
dynload"./foo.dats"//forinitialization
implement
main0()=
{
val()=assertloc(4003000=sum_x_y_z())
}(*endof[main0]*)
Wecannowgenerateanexecutablefilemytestbyissuingthefollowingcommand-line:
atscc-omytestfoo.datsfoo2.dats
Notethatatsccmayneedtobechangedtopatscc.
Thelinestartingwiththekeyword dynload isreferredtoasadynload-declaration.Ifitisdeletedfromthefilefoo2.dats,thenexecutingtheabovecommand-lineleadstolink-timereportingofundefined
referencetoavariableofcertainnameendingwiththestring__dynloadflag.Thedynload-declarationforfoo.dats introduces thisspecialvariableand thenmakesacall toaspecial functionassociated
withfoo.dats for thepurposeofperforming some formof initialization.This special function is
referredasadynload-function(forfoo.dats),whichisalwaysidempotent.
There is also a dynload-function generated forfoo2.dats.As the function main0 , a variant of the
special function main , is implemented in foo2.dats, the dynload-function for foo2.dats is
automaticallycalledinsidethebodyofthe main function.
Ifthereisareasontosuppressthegenerationofadynload-function,onecansetthevalueoftheflagATS_DYNLOADFLAG to0.For instance,nodynload-function forfoo.dats is generated if the following
lineisaddedintofoo.dats:
#defineATS_DYNLOADFLAG0
Ofcourse,skippingproperinitializationforfoo.datsmeansthatanerroneousresultisexpectedif
thefunction sum_x_y_z isevercalled.
Ifthereisareasontocallthedynload-functionforfoo2.datsexplicitly,onecanintroduceanalias
foritandthencallthealias.Forinstance,ifthefollowinglineisaddedtofoo2.dats:
#defineATS_DYNLOADNAME"foo2_dynload"
thenthedynload-functionforfoo2.datsisgivenanalias foo2_dynload .
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter7.BracketOverloadingInmanyprogramminglanguages,bracket-notationiscommonlyassociatedwitharraysubscripting.Forinstance, A[i] isaleft-valueinCthatreferstoarray-cell i inthearrayreferredtoby A .ThereissupportinATSforoverloadingbracketswithmultiplefunctionnamessothatbracket-notationcanbeemployedtocallthesefunctions,resultingincodethatreadslikesomeformofsubscripting.Itisexpectedthatthisstyleofcallingfunctionscan,sometimes,makethecodewritteninATSmoreeasilyaccessible.
Letusnowseeasimpleexampleofbracket-notationinoverloading.Inthefollowngcode,alinearabstracttype lock isintroducedtogetherwithtwofunctions:
//
absvtypelock(a:vt@ype)
//
extern
fun{a:vt0p}lock_acquire(!lock(a)):a
extern
fun{a:vt0p}lock_release(!lock(a),a):void
//
As one can imagine, lock_acquire is called to obtain the value stored in a given lock whilelock_release iscalledtoreturnavaluetoagivenlock.
Supposethatwenowintroducethefollowingoverloadingdeclarations:
//
overload[]withlock_acquire
overload[]withlock_release
//
Withthesedeclarations,thefollowingcodetypechecks:
//
val
mylock=$extval(lock(int),"mylock")
//
valx0=mylock[]//=lock_acquire(mylock)
val()=mylock[]:=x0//=lock_release(mylock,x0)
//
Notethatthebracket-notationinanyassigementisonlyallowedtorefertoafunctionthatreturnsthevoid-value.Intheaboveexample,thefunction lock_release returnsthevoid-value.
InATS, bracket-notation is already overloadedwith functions performing list-subscripting, array-subscriptingandmatrix-subscripting,anditcanalsobeusedtoaccessandupdateagivenreference.
Pleasefindon-linetheentiretyofthecodepresentedinthischapter.
Chapter8.Dot-SymbolOverloadingInmanyprogramminglanguages,theso-calleddot-notationiscommonlyassociatedwithselectingafield from a given tuple-value, record-value or object-value. In ATS, field selection can be donethrough either pattern matching or the use of dot-notation. For example, the following codeconstructsaflattupleandalsoaboxedone,andthenusesdot-notationtoselecttheircomponents:
//
valtup_flat=@("a","b")
valtup_boxed=$tup("a","b")
//
val-"a"=tup_flat.0and"b"=tup_flat.1
val-"a"=tup_boxed.0and"b"=tup_boxed.1
//
ThereissupportinATSforoverloadingaspecifieddot-symbolwithmultiplefunctionnamessothatdot-notationcanbeemployedtocallthesefunctions,resultingincodethatreadslikefieldselectionfromtuplesorrecords.Thisstyleofcallingfunctionscan,sometimes,makethecodewritteninATSmoreeasilyaccessible,anditisespeciallysowhenATSinteractswithlanguagesthatsupportobject-orientedprogramming.
Asanexampleofdot-notation inoverloading, letus introduceanon-linearabstract type point forpointsina2-dimensionalspaceandalsodeclaresomeassociatedfunctions:
//
abstypepoint=ptr//boxed
//
extern
fun
point_make
(x:double,y:double):point
//
extern
funpoint_get_x(p:point):double
andpoint_get_y(p:point):double
//
extern
funpoint_set_x(p:point,x:double):void
andpoint_set_y(p:point,x:double):void
//
For getting the x-coordinate and y-coordinate of a given point, the functions point_get_x and
point_get_y canbecalled,respectively.Forsettingthex-coordinateandy-coordinateofagivenpoint,thefunctions point_set_x and point_set_y canbecalled,respectively.Byintroducingtwodot-symbols.x and .y andthenoverloadingthemwithcertainfunctionnamesasfollows:
symintr.x.y
overload.xwithpoint_get_x
overload.xwithpoint_set_x
overload.ywithpoint_get_y
overload.ywithpoint_set_y
wecanusedot-notationtocall thecorrespondingget-functionsandset-functionsasisshowninthefollowingcode:
valp0=point_make(1.0,~1.0)
valx0=p0.x()//point_get_x(p0)
andy0=p0.y()//point_get_y(p0)
val()=p0.x:=y0//point_set_x(p0,y0)
and()=p0.y:=x0//point_set_y(p0,x0)
Notethatwriting p0.x for p0.x() iscurrentlynotsupported.Thedot-notation inanyassigement isonlyallowedtorefertoafunctionthatreturnsthevoid-value.Intheaboveexample,both point_set_xand point_set_y returnthevoid-value.
Letusintroducealinearabstracttype counter forcounterobjectsandafewfunctionsassociatedwithit:
//
absvtypecounter=ptr
//
extern
funcounter_make():counter
extern
funcounter_free(counter):void
//
extern
funcounter_get(cntr:!counter):int
extern
funcounter_incby(cntr:!counter,n:int):void
//
As can be expected, the functions counter_make and counter_free are for creating and destroying acounter object, respectively. The function counter_get returns the current count stored in a give
counter,andthefunction counter_incby increasethatcountbyagivenintegervalue.
Letusintroducethefollowingoverloadingdeclarations:
//
overload.getwithcounter_get
overload.incbywithcounter_incby
//
Asisexpected,onecannowcall counter_get asfollows:
valn0=c0.get()//=counter_get(c0)
Similarly,onecancall counter_incby asfollowstoincreasethecountstoredin c0 by1:
val()=c0.incby(1)//=counter_incby(c0,1)
Ifwerevisit thepreviousexampleinvolving(non-linear)points, thenwecanseethat thefollowingcodealsotypechecks:
valp0=point_make(1.0,~1.0)
valx0=p0.x()//point_get_x(p0)
andy0=p0.y()//point_get_y(p0)
val()=p0.x(y0)//point_set_x(p0,y0)
and()=p0.y(x0)//point_set_y(p0,x0)
Imayusethephrasefunctionaldot-notationtorefertothisformofdot-notationsoastodifferentiateitfromthegeneralformofdot-notation.
Pleasefindon-linetheentiretyofthecodepresentedinthischapterplussometestingcode.
Chapter9.RecursionThe notion of recursion is ubiquitous in ATS. For instance, there are recursively defined sorts(datasorts)andtypes(datatypes)inthestatics,andtherearealsorecursivelydefinedfunctionsinthedynamics. Literally, the word recurse means "go back". When an entity is defined recursively, itmeansthat theentitybeingdefinedcanappear in itsowndefinition.Inthefollowingpresentation,Iwill show severalways to define (or implement) recursive functions and non-recursive functions,wherethelatterisjustaspecialcaseoftheformer.
Thekeyword fun canbeusedtoinitiatethedefinitionofarecursivefunction.Forinstance,followingisthedefinitionofarecursivefunction:
fun
fact(x:int):int=
ifx>0thenx*fact(x-1)else1
(*endof[fact]*)
Anon-recursivefunctionisaspecialkindofrecursivefunctionthatdoesmakeuseofitselfinitsowndefinition. So one can certainly use fun to initiate the definition of a non-recursive function.However, if thereisaneedtoindicateexplicitlythatanon-recursiveisbeingdefined,thenonecanuse the keyword fn to do so. For instance, the definiton of a non-recursive function is given asfollows:
fnsquare(x:int):int=x*x
whichisdirectlytranslatedbythecompilerintothefollowingbindingbetweenanameandalambda-expression:
valsquare=lam(x:int):int=>x*x
Asanotherexample,pleasenotethatthetwooccurrencesofthesymbol fact inthefollowingcoderefertotwodistinctfunctions:
fn
fact(x:int):int=
ifx>0thenx*fact(x-1)else1
(*endof[fact]*)
Whilethefirst fact (totheleftoftheequalitysymbol)referstothe(non-recursive)functionbeing
defined,thesecondoneissupposedtohavebeendeclaredpreviously.
A recursive function can also be defined as a recursive value. For instance, the recursive functionfact definedabovecanbedefinedasfollows:
val
rec
fact:int->int=
lam(x)=>
ifx>0thenx*fact(x-1)else1
(*endof[fact]*)
wherethekeyword rec indicatesthat fact isdefinedrecursively,thatis,itisallowedtoappearinitsowndefinition.Infact, theformerdefinitionof fact isdirectly translated into the latteronebythecompiler.Ofcourse,onemayalsouseareferencetoimplementrecursion:
val
fact=ref<int->int>($UNSAFE.cast(0))
val()=
!fact:=
(
lam(x:int):int=>ifx>0thenx*!fact(x-1)else1
)(*endof[val]*)
But this isdefinitelynot a style Iwould like toadvocate.For the sakeof completion, Ipresentyetanotherwaytodefine fact asafixed-pointexpression:
val
fact=
fixf(x:int):int=>
ifx>0thenx*f(x-1)else1
(*endof[fact]*)
Of course, if one wants to, then one can always replace a lambda-expression with a fixed-pointexpression(orsimplyfix-expressionforshort).Forinstance, lambda(x:int):int=>x+x canbewrittenas fix_(x:int):int=>x+x .
For defining mutually recursive functions, one can simply use the keyword and to concatenatefunctiondefinitions.Forinstance,thefollowingcodedefinestwofunctions isevn and isodd mutuallyrecursively:
fun
isevn(x:int):bool=
ifx>0thenisodd(x-1)elsetrue
and
isodd(x:int):bool=
ifx>0thenisevn(x-1)elsefalse
The code, as one may have guessed, is translated by the compiler into the following form (fordefiningtwomutuallyrecursivevalues):
val
rec
isevn:int->bool=
lam(x)=>ifx>0thenisodd(x-1)elsetrue
and
isodd:int->bool=
lam(x)=>ifx>0thenisevn(x-1)elsefalse
One can certainly use the keyword and to concatenate definitions of non-recursive functions, butdoingsoisprobablyjustacuriosity(insteadofameaningfulpractice).
Even at this point, I have not presented all of the possible ways to define functions in ATS. Forinstance,onecanalsodefinestack-allocatedclosure-functionsinATS,whichmaybeeitherrecursiveornon-recursive.Iplantointroducesuchfunctionselsewhereinthistutorial.
Often, the interface (that is, type) for a function is declared at one place and its definition (orimplementation) is given at another place. For instance, one may first introduce the followingdeclaration:
externfunfact(x:int):int
Later,onecanimplement fact accordingtotheabovedeclaration:
implement
fact(x)=
ifx>0thenx*fact(x-1)else1
//endof[fact]
When implement is used to initiate the definition of a function, any previously declared symbols(includingtheonethatisbeingdefined)canappearinthedefinition.Ifitisdesirable,onemayreplaceimplement with implmnt .
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter10.DatatypesDatatypesareaformofuser-definedtypesforclassifyingdata(orvalues).ThesupportfordatatypesandpatternmatchinginATSisprimarilyadoptedfromML.
Thefollowingcodedeclaresadatatypeofthename weekday forvaluesrepresentingweekdays:
datatypeweekday=
|Monday|Tuesday|Wednesday|Thursday|Friday
There are five data constructors associated with weekday , which are Monday , Tuesday , Wednesday ,Thursday , and Friday .All of these data constructors are nullary, that is, they take no arguments toformvalues(ofthetype weekday ).
Eachnullaryconstructorisrepresentedasasmallinteger(e.g.,onethatislessthan1024).Onecanusethefollowingfunction weekday2int tofindouttheintegersreprsentingtheconstructorsassociatedwith weekday :
//
staloadUN="prelude/SATS/unsafe.sats"
//
fun
weekday2int
(wd:weekday):int=$UN.cast{int}($UN.cast{intptr}(wd))
//
Thesmallintegerrepresentinganullaryconstructorisoftenreferredtoasthetagoftheconstructor.In this case, the tags for Monday , Tuesday , Wednesday , Thursday , and Friday are 0, 1, 2, 3, and 4,respectively.
Givenanullaryconstructor foo ,both foo and foo() canbeused to refer thevalueconstructedbyfoo . However, only foo() can be used as the pattern that matches this value. For instance, thefollowing function testswhether a given value of the type weekday is formedwith the constructorFriday :
fun
isFriday(x:weekday):bool=
case+xofFriday()=>true|_=>false
Notethatthepattern Friday() cannotbereplacedwith Friday asthelatteristreatedasavariablewhenusedasapattern.Ontheotherhand,bothofthefollowingassertionsholdatrun-timeas Friday andFriday() refertothesamevalue:
val()=assertloc(isFriday(Friday))
val()=assertloc(isFriday(Friday()))
If thereisonlyoneconstructorassociatedwithadatatype, thenthereisnotagintherepresentationforvaluesofthisdatatype.
Adatatypeislist-likeiftherearetwodataconstructorsassociatedwithitsuchthatoneisnullary(nil-like)andtheotherisnon-nullary(cons-like).Forinstance,thedatatype ab declaredasfollowsislist-like:
datatypeab=Aof()|Bof(bool)
Thevaluesofalist-likedatatypearerepresentedinaspecialway.Givenavalueofthedatatype;ifitisconstructedwiththenil-likeconstructor,thenitisrepresentedasthenull-pointer;ifitisconstructedwith the cons-like constructor, then it is reprenstend as a heap-allocated tuple containing all of theargumentspassedtotheconstructor.Intheabovecase,thevalue A() isrepresentedasthenullpointer,andthevalue B(b) foreachboolean b isrepresentedasaheap-allocatedsingletontuplecontainingtheonlycomponent b .Thisdescriptioncanbereadilyverifiedwiththefollowingcode:
val()=assertloc(iseqz($UN.cast{ptr}(A())))
val()=assertloc(true=$UN.ptr0_get<bool>($UN.cast{ptr}(B(true))))
val()=assertloc(false=$UN.ptr0_get<bool>($UN.cast{ptr}(B(false))))
Thefollowingdeclarationintroducesadatatypeofthename abc :
datatypeabc=
|Aof()|Bof(bool)|Cof(int,double)
The three constructors associated with abc are A , B , and C ; A is nullary; B is unary, taking aboolean to form a value (of the type abc ); C is binary, taking an integer and a float (of doubleprecision)toformavalue(ofthetype abc ).
Inageneralcase,ifadataconstructorisn-aryforsomepositiven,theneachvalueitconstructsisaheap-allocatedtupleofn+1components,wherethefirstoneisthetagassignedtotheconstructorandtherestaretheargumentspassedtotheconstructor.Forinstance,thefollowingfunctioncanbecalled
tofindoutthetagsassignedtotheconstructorsassociatedwith abc :
fun
abc2tag
(x:abc):int=let
valp=$UN.cast{intptr}(x)
in
//
case+0of
|_whenp<1024=>$UN.cast{int}(p)
|_(*heap-allocated*)=>$UN.ptr0_get<int>($UN.cast{ptr}(p))
//
end//endof[abc2tag]
Inthiscase,thetagsassignedto A , B ,and C are0,1,and2,respectively.
Datatypes can be defined recursively. As an example, the following declaration introduces arecursivelydefineddatatype intexp (forrepresentingarithemeticintegerexpressions):
datatype
intexp=
|Intofint
|Negof(intexp)
|Addof(intexp,intexp)
|Subof(intexp,intexp)
Forinstance, (1+2)-3 canberepresentedas Sub(Add(Int(1),Int(2)),Int(3)) .Asanotherexample,thefollowing code introduces two mutually recursively defined datatypes intexp and boolexp (forintegerexpressionsandbooleanexpressions,respectively):
datatype
intexp=
|Intofint
|Negof(intexp)
|Addof(intexp,intexp)
|Subof(intexp,intexp)
|IfThenElseof(boolexp,intexp,intexp)
and
boolexp=
|Boolofbool//constant
|Notof(boolexp)//negation
|Lessof(intexp,intexp)//Less(x,y):x<y
|LessEqof(intexp,intexp)//LessEq(x,y):x<=y
|Conjof(boolexp,boolexp)//Conj(x,y):x/\y
|Disjof(boolexp,boolexp)//Disj(x,y):x\/y
The code below implements two mutually recursive functions eval_intexp and eval_boolexp forevaluatingintegerexpressionsandbooleanexpressions,respectively:
//
symintreval
//
extern
funeval_intexp:intexp->int
extern
funeval_boolexp:boolexp->bool
//
overloadevalwitheval_intexp
overloadevalwitheval_boolexp
//
(**************)
//
implement
eval_intexp
(e0)=(
//
case+e0of
|Int(i)=>i
|Neg(e)=>~eval(e)
|Add(e1,e2)=>eval(e1)+eval(e2)
|Sub(e1,e2)=>eval(e1)-eval(e2)
|IfThenElse
(e_test,e_then,e_else)=>
ifeval(e_test)theneval(e_then)elseeval(e_else)
//
)(*endof[eval_intexp]*)
//
implement
eval_boolexp
(e0)=(
//
case+e0of
|Bool(b)=>b
|Not(e)=>~eval(e)
|Less(e1,e2)=>eval(e1)<eval(e2)
|LessEq(e1,e2)=>eval(e1)<=eval(e2)
|Conj(e1,e2)=>eval(e1)&&eval(e2)
|Disj(e1,e2)=>eval(e1)||eval(e2)
//
)(*endof[eval_boolexp]*)
//
(**************)
Thedatatypespresentedinthischapteraresimpledatatypes.Othermoreadvancedformsofdatatypesinclude polymorphic datatypes, dependent datatypes, and linear datatypes, which will be coveredelsewhere.
Pleasefindon-linetheentiretyofthecodeusedinthischapterplussomecodefortesting.
Chapter11.FunctionalListsA functional list is just a singly-linked list that is immutable after its construction. The followingdatatypedeclarationintroducesatype list forfunctionallists:
//
datatype
list(a:t@ype,int)=
|list_nil(a,0)of()
|{n:nat}
list_cons(a,n+1)of(a,list(a,n))
//
GivenatypeTandanintegerN,thetype list(T,N) isforalistoflengthNthatcontainselementsoftype T. The interfaces for various functions on functional lists can be found in the SATS fileprelude/SATS/list.sats,whichisautomaticallyloadedbyatsopt.
There are various functions in ATSLIB for list construction. In practice, a list is usually built bymakingdirectuseof theconstructors list_nil and list_cons . For instance, the following functionfromto buildsalistofintegersbetweentwogivenones:
//
fun
fromto
{m,n:int|m<=n}
(
m:int(m),n:int(n)
):list(int,n-m)=
ifm<nthenlist_cons(m,fromto(m+1,n))elselist_nil()
//
Traversingalistiscommonlydonebymakinguseofpatternmatching.Forinstance,thefollowingcodeimplementsafunctionforcomputingthelengthofagivenlist:
//
fun
{a:t@ype}
list_length
{n:nat}
(
xs:list(a,n)
):int(n)=let
//
fun
loop
{i,j:nat}
(
xs:list(a,i),j:int(j)
):int(i+j)=
(
case+xsof
|list_nil()=>j
|list_cons(_,xs)=>loop(xs,j+1)
)
//
in
loop(xs,0)
end//endof[list_length]
//
Given a non-empty list xs, xs.head() and xs.tail() refer to the head and tail of xs, respectively,where .head() and .tail() areoverloadeddot-symbols.Forinstance,thefunction list_foldleft forfoldingagivenlistfromtheleftcanbeimplementedasfollows:
//
fun
{a,b:t@ype}
list_foldleft
{n:nat}
(
f:(a,b)->a,ini:a,xs:list(b,n)
):a=
ifiseqz(xs)
theninielselist_foldleft(f,f(ini,xs.head()),xs.tail())
//endof[if]
//
And the function list_foldright for folding a given list from the right can be implemented asfollows:
//
fun
{a,b:t@ype}
list_foldright
{n:nat}
(
f:(a,b)->b,xs:list(a,n),snk:b
):b=
ifiseqz(xs)
thensnkelsef(xs.head(),list_foldright(f,xs.tail(),snk))
//endof[if]
//
Notethat list_foldleft istail-recursivebut list_foldright isnot.
Asanapplicationof list_foldleft , thefollowingcodeimplementsafunctionforreversingagivenlist:
fun
{a:t@ype}
list_reverse
(
xs:List0(a)
):List0(a)=
(
list_foldleft<List0(a),a>
(lam(xs,x)=>list_cons(x,xs),list_nil,xs)
)
wherethetypeconstructor List0 isforlistsofunspecifiedlength:
typedefList0(a:t@ype)=[n:nat]list(a,n)
Clearly, list_reverse is length-preserving, that is, it always returns a list of the same length as itsinput.Unfortunately,thisinvariantisnotcapturedintheaboveimplementationof list_reverse basedon list_foldleft .Forthepurposeofcomparison,anotherimplementationof list_reverse isgivenasfollowsthatcapturestheinvariantof list_reverse beinglength-preserving:
fun
{a:t@ype}
list_reverse
{n:nat}
(
xs:list(a,n)
):list(a,n)=let
//
fun
loop{i,j:nat}
(
xs:list(a,i),ys:list(a,j)
):list(a,i+j)=
case+xsof
|list_nil()=>ys
|list_cons(x,xs)=>loop(xs,list_cons(x,ys))
//
in
loop(xs,list_nil)
end//endof[list_reverse]
Asanapplicationof list_foldright ,thefollowingcodeimplementsafunctionforconcatenatingtwogivenlists:
//
fun
{a:t@ype}
list_append
(
xs:List0(a),ys:List0(a)
):List0(a)=
list_foldright<a,List0(a)>(lam(x,xs)=>list_cons(x,xs),ys,xs)
//
Thetypeassignedto list_append statesthatthisfunctiontakestwolistsasitsargumentsandreturnsoneofunspecifiedlength.Forthepurposeofcomparison,anotherimplementationof list_append isgivenasfollows:
//
fun
{a:t@ype}
list_append
{m,n:nat}
(
xs:list(a,m),ys:list(a,n)
):list(a,m+n)=
(
case+xsof
|list_nil()=>ys
|list_cons(x,xs)=>list_cons(x,list_append(xs,ys))
)
//
where the type assigned to list_append states that this function takes two lists of length m and n,respectively,andreturnsanotherlistoflengthm+n.
Onemaythinkofafunctional listasarepresentationfor thefinitemappingthatmapseachnatural
numberilessthanthelengthofthelisttoelementiinthelist.Thefollowingfunction list_get_at isforaccessingalistelementatagivenposition:
//
fun
{a:t@ype}
list_get_at
{n:nat}
(
xs:list(a,n),i:natLt(n)
):a=
ifi>0thenlist_get_at(xs.tail(),i-1)elsexs.head()
//
This function can be called through the use of the bracket notation. In other words, xs[i] isautomatically interpreted as list_get_at(xs, i) whenever xs and i are a list and an integer,respectively. Note that the time-complexity of list_get_at(xs, i) is O(i). If one uses list_get_at
frequentlywhenhandlinglists,thenitisalmostalwaysasuresignofpoorprogrammingstyle.
There is no destructive update on a functional list as it is immutable. The following functionlist_set_at canbecalledtoconstructalistthatdiffersfromagivenoneonlyatagivenposition:
//
fun
{a:t@ype}
list_set_at
{n:nat}
(
xs:list(a,n),i:natLt(n),x0:a
):list(a,n)=
ifi>0
thenlist_cons(xs.head(),list_set_at(xs.tail(),i-1,x0))
elselist_cons(x0,xs.tail())
//endof[if]
//
Whileitisfinetocall list_set_at occasionally,aneedtodosorepeatedlyoftenindicatesthatanotherdatastructureshouldprobablybechoseninplaceoffunctionallist.
Functionallistsarebyfarthemostwidelyuseddatastructureinfunctionalprogramming.However,oneshouldnotattempttouseafunctionallistlikeanarrayasdoingsoisinefficientbothtime-wiseandmemory-wise.
Pleasefindon-linetheentiretyofthecodeusedinthischapterplussometestingcode.
Chapter12.FunctionalSetsandMapsBoth(finite)setsand(finite)mapsarecommonlyuseddatastructures.Functionalsetsandmapsareimmutableaftertheirconstruction.Insertionintoorremovalfromafunctionalset/mapresultsintheconstructionofanewset/mapwhiletheoriginaliskeptintact.Usuallythenewlyconstructedset/mapandtheoriginalonesharealotofunderlyingrepresentation.Notethatafunctionalset/mapcannotbesafely freed explicitly and thememory for representing it can only be reclaimed throughgarbagecollection(GC).
FunctionalSets
Supposethatasetisneededforcollectingvaluesoftype elt_t .Thefollowingcodeessentiallysetsupaninterfaceforcreatingandoperatingonsuchasetbasedonabalanced-treeimplementationinATSLIB/libats:
local
//
typedefelt=elt_t
//
staload
FS="libats/ML/SATS/funset.sats"
implement
$FS.compare_elt_elt<elt>(x,y)=compare(x,y)
//
in(*in-of-local*)
#include"libats/ML/HATS/myfunset.hats"
end//endof[local]
Pleasefindon-linetheHATSfilementionedinthecode,whichisjustaconveniencewrappermadetosimplifyprogrammingwith functional sets.Note that it is assumedhere that there is a comparisonfunctiononvaluesof the type elt_t thatoverloads the symbol compare . If this is not the case, oneneedstoimplementsuchafunction.
Assumethat elt_t is int .Thefollowinglineofcodecreatesafunctionalset(ofintegers)containingnoelements:
valmyset=myfunset_nil()
Thefunctionforinsertinganelementintoagivensetisassignedthefollowingtype:
//
funmyfunset_insert(xs:&myset>>_,x0:elt):bool
//
Thedot-symbol .insert isoverloadedwiththefunction myfunset_insert .Notethatthefirstargumentof myfunset_insert is call-by-reference. If the given element is inserted into the given set, then thenewlycreatedsetisstoredintothecall-by-referenceargumentand false isreturned(toindicatenoerror).Otherwise, true isreturned(toindicateafailure).Thefollowingfewlinesofcodeshowshow
insertioncanbeoperatedonafunctionalset:
//
varmyset=myset
//
val-false=myset.insert(0)//inserted
val-(true)=myset.insert(0)//notactuallyinserted
val-false=myset.insert(1)//inserted
val-(true)=myset.insert(1)//notactuallyinserted
//
The first line in theabovecodemayseempuzzling: Its solepurpose is tocreatea left-value tobepassed as the first argument to myfunset_insert .During the course of debugging, onemaywant toprintoutthevaluescontainedinagivenset:
//
val()=fprintln!(stdout_ref,"myset=",myset)
//
where the symbol fprint is overloadedwith fprint_myset . The function for removing an elementfromagivensetisassignedthefollowingtype:
//
funmyfunset_remove(xs:&myset>>_,x0:elt):bool
//
Thedot-symbol .remove isoverloadedwiththefunction myfunset_remove .Notethatthefirstargumentof myfunset_remove iscall-by-reference.Ifthegivenelementisremovedfromthegivenset,thenthenewlycreatedsetisstoredintothecall-by-referenceargumentand true isreturned.Otherwise, falseisreturned.Thefollowingfewlinesofcodeshowshowremovalcanbeoperatedonafunctionalset:
val-true=myset.remove(0)//removed
val-false=myset.remove(0)//notactuallyremoved
val-true=myset.remove(1)//removed
val-false=myset.remove(1)//notactuallyremoved
Various common set operations can be found in libats/ML/HATS/myfunset.hats.By following thetypes assigned to these operations, one should have no difficulty in figuring out how they aresupposedtobecalled.Pleasefindtheentiretyofthecodeusedinthissectionon-line.
FunctionalMaps
Supposethatamapisneededformappingkeysoftype key_t toitemsoftype itm_t .Thefollowingcodeessentiallysetsupaninterfaceforcreatingandoperatingonsuchamapbasedonabalanced-treeimplementationinATSLIB/libats:
local
//
typedef
key=key_tanditm=itm_t
//
staload
FM="libats/ML/SATS/funmap.sats"
implement
$FM.compare_key_key<key>(x,y)=compare(x,y)
//
in(*in-of-local*)
#include"libats/ML/HATS/myfunmap.hats"
end//endof[local]
Pleasefindon-linetheHATSfilementionedinthecode,whichisjustaconveniencewrappermadetosimplifyprogrammingwithfunctionalmaps.Notethatitisassumedherethatthereisacomparisonfunctiononvaluesof the type key_t thatoverloads the symbol compare . If this is not the case, oneneedstoimplementsuchafunction.
Assumethat key_t is string and itm_t is int .Thefollowinglineofcodecreatesanemptyfunctionalmap:
valmymap=myfunmap_nil()
Thefollowingfewlinesinsertsomekey/itempairsinto mymap :
//
varmymap=mymap
//
val-~None_vt()=mymap.insert("a",0)
val-~Some_vt(0)=mymap.insert("a",1)
//
val-~None_vt()=mymap.insert("b",1)
val-~Some_vt(1)=mymap.insert("b",2)
//
val-~None_vt()=mymap.insert("c",2)
val-~Some_vt(2)=mymap.insert("c",3)
//
Thedot-symbol .insert isoverloadedwithafunctionofthename myfunmap_insert .Thefirstlineintheabovecodemayseempuzzling:Itssolepurposeistocreatealeft-valuetobepassedasthefirstargument to myfunmap_insert . Given a key and an item, mymap.insert inserts the key/item pair intomymap .Ifthekeyisinthedomainofthemaprepresentedby mymap beforeinsertion,thentheoriginalitemassociatedwiththekeyisreturned.Otherwise,noitemisreturned.Ascanbeexpected,thesizeof mymap is3atthispoint:
val()=assertloc(mymap.size()=3)
Thedot-symbol .size is overloadedwith a function of the name myfunmap_size , which returns thenumberofkey/itempairsstoredinagivenmap.Duringthecourseofdebugging,onemaywanttoprintoutthekey/itempairsinagivenmap:
//
val()=fprintln!(stdout_ref,"mymap=",mymap)
//
where the symbol fprint is overloadedwith fprint_mymap . The next two lines of code show howsearchwithagivenkeyoperatesonamap:
val-~None_vt()=mymap.search("")
val-~Some_vt(1)=mymap.search("a")
Thedot-symbol .search isoverloadedwithafunctionofthename myfunmap_search ,whichreturnstheitemassociatedwithagivenkeyifitisfound.Thenextfewlinesofcoderemovesomekey/itempairsfrom mymap :
//
val-true=mymap.remove("a")
val-false=mymap.remove("a")
//
val-~Some_vt(2)=mymap.takeout("b")
val-~Some_vt(3)=mymap.takeout("c")
//
Thedot-symbol .remove isoverloadedwitha functionof thename myfunmap_remove for removingakey/itempairofagivenkey.Ifakey/itempairisremoved,thenthefunctionreturnstrue.Otherwise,
itreturnsfalsetoindicatesthatnokey/itempairofthegivenkeyisstoredinthemapbeingoperatedon.Thedot-symbol .takeout is overloadedwith a functionof thename myfunmap_takeout , which issimilarto myfunmap_remove exceptingforreturningtheremoveditem.
Variouscommonmapoperationscanbefoundinlibats/ML/HATS/myfunmap.hats.Byfollowingthetypes assigned to these operations, one should have no difficulty in figuring out how they aresupposedtobecalled.Pleasefindtheentiretyofthecodeusedinthissectionon-line.
Chapter13.ExceptionsWhile exceptions can be very useful in practice, it is also very common to see code thatmisusesexceptions.
Generallyspeaking,thereareexceptionsthataremeanttoberaisedbutnotcapturedforthepurposeofabortingprogramexecution,andtherearealsoexceptions(oftendeclaredlocally)thataremeantto be raised and then captured so as to change the flow of program execution. For instance, theexception ArraySubscriptExn is raisedwhenout-of-boundsarraysubscripting isdetectedat run-time.Once it is raised, ArraySubscriptExn is usually not meant to be captured. While there is certainlynothingpreventingaprogramerfromwritingcodethatcapturesaraised ArraySubscriptExn ,amajorconcern is that reasoning can become greatly complicated on code that does so. In the followingpresentation,Iwillsoleyfocusonexceptionsthataremeanttoberaisedandthencaptured.
Let usnow take a look at the following code that implements a function for finding the rightmostelementinalistthatsatisfiesagivenpredicate:
extern
fun{a:t@ype}
list_find_rightmost
(List(a),(a)-<cloref1>bool):Option_vt(a)
//
implement{a}
list_find_rightmost
(xs,pred)=let
//
funaux
(
xs:List(a)
):Option_vt(a)=
case+xsof
|nil()=>None_vt()
|cons(x,xs)=>let
valres=aux(xs)
in
case+resof
|Some_vt_=>res
|~None_vt()=>
ifpred(x)thenSome_vt(x)elseNone_vt()
//endof[None]
end(*endof[cons]*)
//
in
aux(xs)
end//endof[list_find_rightmost]
Supposethat list_find_rightmost iscalledonalistxsoflengthN(forsomelargenaturalnumberN)andapredicatepred.Theevaluationofthiscallleadstoacalltotheinnerfunction aux ,whichinturngeneratesN additional recursive calls to aux .Assume that only the last element of xs satisfies thepredicate pred. Then there are still N-1 call frames for aux on the call stack when the rightmostelement satisfying the given predicate is found, and these frames need to be unwindedone-by-onebefore the found element can be returned to the original call to list_find_rightmost . This form ofinefficiencyiseliminatedinthefollowingexception-basedimplementationof list_find_rightmost :
implement{a}
list_find_rightmost
(xs,pred)=let
//
exceptionFoundof(a)
//
funaux
(
xs:List(a)
):void=
case+xsof
|nil()=>()
|cons(x,xs)=>let
val()=aux(xs)
in
ifpred(x)then$raiseFound(x)else()
end(*endof[cons]*)
//
in
//
trylet
val()=aux(xs)
in
None_vt()
endwith
|~Found(x)=>Some_vt(x)
//
end//endof[list_find_rightmost]
Whenatry-with-expressionisevaluated,alabeliscreatedfortheportionofthecallstackneededtoevaluate theclauses (oftenreferred toasexception-handlers) following thekeyword with , and this
label is thenpushedonto adesignatedglobal stack.When an exception is raised, the labels on theglobalstackaretriedone-by-oneuntiltheraisedexceptioniscapturedbyanexception-handler(thatis, the value representing the exceptionmatches the pattern guard of the exception-handler) or thecurrentprogramevaluationaborts.Theaboveexception-basedimplementationof list_find_rightmostusesaraisedexceptiontocarrytheelementfoundduringarecursivecallto aux sothatthiselementcan be returned in a single jump to the original call to list_find_rightmost , bypassing all theintermediatecallframes(forrecursivecallsto aux )onthecallstack.Ingeneral,therangebetweenthe pointwhere an exception is raised and the pointwhere the raised exception is captured shouldspanmultiplecallframes.Ifnot,thentheuseofexceptionmaybequestionable.
Theimplementationoftherun-timesupportforexceptionsinATSmakesuseofthefunction allocadeclaredinalloca.handthefunctions setjmp and longjmp declaredinsetjmp.h.Ifgccorclang is
usedtocompiletheCcodegeneratedfromATSsource,onecanpasstheflag-D_GNU_SOURCEsoastomakesurethattheheaderfilealloca.hisproperlyincluded.
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter14.ReferencesAreferenceisasingletonarray,thatis,anarrayofsize1.Itispersistentinthesensethatthe(heap-allocated)memory for storing the content of a reference cannot be freedmanually in a type-safemanner.Instead,itcanonlybereclaimedthroughgarbagecollection(GC).
GivenaviewtypeVT,thetypeforreferencestovaluesofviewtypeVTis ref (VT).Forconvenience,thetypeconstructor ref isdeclaredtobeabstractinATS.However,itcanbedefinedasfollows:
typedefref(a:vt@ype)=[l:addr](vbox(a@l)|ptrl)
Theinterfacesforvariousfunctionsonreferencescanbefoundinprelude/SATS/reference.sats.
Forcreatingareference,thefunctiontemplate ref_make_elt ofthefollowinginterfacecanbecalled:
fun{a:vt@ype}ref_make_elt(x:a):<!wrt>refa
Itisalsoallowedtousetheshorthand ref for ref_make_elt .Notethatthesymbol !wrt indicatesthattheso-called wrt -effectmayoccurwhen ref_make_elt iscalled.
Forreadingfromandwritingthroughareference,thefunctiontemplates ref_get_elt and ref_set_eltcanbeused,respectively,whichareassignedthefollowingtypes:
fun{a:t@ype}ref_get_elt(r:refa):<!ref>a
fun{a:t@ype}ref_set_elt(r:refa,x:a):<!refwrt>void
Note that the symbol !ref indicates that the so-called ref-effect may occur when ref_get_elt isevaluated.Similarly, !refwrt meansbothref-effectandwrt-effectmayoccurwhen ref_set_elt .Givenareference r andavalue v , ref_get_elt(r) and ref_set_elt(r,v) canbewrittenas !r and !r:=v ,respectively,andcanalsobewrittenas r[] and r[]:=v ,respectively,intermsofbracket-notation.
Areferenceistypicallyemployedtorecordsomeformofpersistentstate.Forinstance,followingissuchanexample:
local
//
#defineBUFSZ128
//
valcount=ref<int>(0)
//
in(*inof[local]*)
fungenNewName
(prfx:string):string=let
valn=!count
val()=!count:=n+1
varres=@[byte][BUFSZ]((*void*))
valerr=
$extfcall(
int,"snprintf",addr@res,BUFSZ,"%s%i",prfx,n
)(*endof[$extfcall]*)
in
strptr2string(string0_copy($UNSAFE.cast{string}(addr@res)))
end//endof[genNewName]
end//endof[local]
The function genNewName is called to generate fresh names.As the integer content of the referencecount is updated whenever a call to genNewName is made, each name returned by genNewName isguaranteedtohavenotbeengeneratedbefore.Notethat theuseof $extfcall isformakingadirectcalltothefunction snprintf inC.
MisuseofReferencesReferencesarecommonlymisusedinpractice.Thefollowingprogramisoftenwritten by a beginner of functional programming who has already learned (some) imperativeprogramming:
funfact
(n:int):int=let
valres=ref<int>(1)
funloop(n:int):<cloref1>void=
ifn>0then(!res:=n*!res;loop(n-1))else()
val()=loop(n)
in
!res
end//endof[fact]
Thefunction fact iswritteninsuchastyleassomewhatadirecttranslationofthefollowingCcode:
intfact(intn){
intres=1;
while(n>0){res=n*res;n=n-1;};
returnres;
}
In the ATS implementation of fact , res is a heap-allocated reference and it becomes garbage(waitingtobereclaimedbytheGC)afteracallto fact returns.Ontheotherhand,thevariable res intheCimplementationof fact isstack-allocated(oritcanevenbemappedtoamachineregister),andthere is no generated garbage after a call to fact returns. A proper translation of the CimplementationinATScanactuallybegivenasfollows:
funfact
(n:int):int=let
funloop(n:int,res:int):int=
ifn>0thenloop(n-1,n*res)elseres
//endof[loop]
in
loop(n,1)
end//endof[fact]
whichmakesnouseofreferencesatall.
Unlessstrongjustificationcanbegiven,makingextensiveuseof(dynamicallycreated)referencesisoftenasuresignofpoorcodingstyle.
Statically Allocated References Creating a reference by calling ref_make_elt involves dynamicmemoryallocation.Ifthisisnotdesirableorevenacceptable,itispossibletoonlyemploystaticallyallocatedmemoryinareferencecreationasisshownbelow:
varmyvar:int=0
valmyref=ref_make_viewptr(view@(myvar)|addr@(myvar))
Thefunction ref_make_viewptr takesapointerandaproofofsomeat-viewassociatedwiththepointerandreturnsareferenceafterconsumingtheproof.As ref_make_viewptr isacast-function,itcausesnorun-timeoverhead.Intheabovecode, myvar isstaticallyallocatedanditisnolongeravailableafteritsat-viewproofisconsumedby ref_make_viewptr .Itshouldbeinterestingtoobservethatboth myvarand myref are just the same pointer in C but they are the reification of fundamentally differentconceptsinATS:theformerisalinearvariablewhilethelatterisanon-linearreference.
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter15.PersistentArraysApersistentarrayofsizenisjustnheap-allocatedcells(orreferences)inarow.Itispersistentinthesensethatthememoryallocatedforthearraycannotbefreedmanually.Instead,itcanonlybesafelyreclaimedthroughgarbagecollection(GC).
Given a viewtype VT, the type for persistent arrays containing N values of viewtype VT isarrayref(VT, N) .Note that arrays inATS are the same as those inC:There is no size informationattachedtothem.TheinterfacesforvariousfunctionsonpersistentarrayscanbefoundintheSATSfileprelude/SATS/arrayref.sats,whichisautomaticallyloadedbyatsopt.
There are various functions in ATSLIB for array creation. For instance, the following two arecommonlyused:
fun{a:t@ype}
arrayref_make_elt
{n:nat}(asz:size_tn,elt:a):<!wrt>arrayref(a,n)
//endof[arrayref_make_elt]
fun{a:t@ype}
arrayref_make_listlen
{n:int}(xs:list(a,n),n:intn):<!wrt>arrayref(a,n)
//endof[arrayref_make_listlen]
Appliedtoasizeandanelement, arrayref_make_elt returnsanarrayofthegivensizeinwhicheachcell is initialized with the given element. Applied to a list of elements and the length of the list,arrayref_make_listlen returnsanarrayofsizeequaltothegivenlengthinwhicheachcellisinitializedwiththecorrespondingelementinthegivenlist.
Forreadingfromandwritingtoanarray,thefunctiontemplates arrayref_get_at and arrayref_set_atcanbeused,respectively,whichareassignedthefollowinginterfaces:
fun{a:t@ype}
arrayref_get_at
{n:int}(A:arrayref(a,n),i:sizeLt(n)):<!ref>a
fun{a:t@ype}
arrayref_set_at
{n:int}(A:arrayref(a,n),i:sizeLt(n),x:a):<!ref>void
Givenanarray A ,anindex i andavalue v , arrayref_get_at(A,i) and arrayref_set_at(A,i,v) canbewrittenas A[i] and A[i]:=v ,respectively.
Asanexample,thefollowingfunctiontemplatereversesthecontentofagivenarray:
fun{a:t@ype}
arrayref_reverse{n:nat}
(
A:arrayref(a,n),n:size_t(n)
):void=let
//
funloop
{i:nat|i<=n}.<n-i>.
(
A:arrayref(a,n),n:size_tn,i:size_ti
):void=let
valn2=half(n)
in
ifi<n2thenlet
valtmp=A[i]
valni=pred(n)-i
in
A[i]:=A[ni];A[ni]:=tmp;loop(A,n,succ(i))
endelse()//endof[if]
end//endof[loop]
//
in
loop(A,n,i2sz(0))
end//endof[arrayref_reverse]
Ifthetest i<n2 ischangedto i<=n2 ,atype-erroristobereported.Why?Thereasonisthat A[n-1-i] becomesout-of-boundsarraysubscriptinginthecasewhere n and i bothequalzero.Giventhatitis very unlikely to encounter a case where an array of size 0 is involved, a bug like this, if notdetectedearly,canbeburiedsoscarilydeep!
Thecarefulreadermayhavealreadynoticedthatthesort t@ype isassignedtothetemplateparametera . In other words, the above implementation of arrayref_reverse cannot handle a case where thevaluesstoredinagivenarrayareofsomelineartype.Thereasonforchoosingthesort t@ype isthatboth arrayref_get_at and arrayref_set_at can only be applied to an array containing values of anonlinear type. In thefollowingimplementation, the templateparameter isgiven thesort vt@ype sothatanarraycontaininglinearvalues,thatis,valuesofsomelineartypecanbehandled:
fun{a:vt@ype}
arrayref_reverse{n:nat}
(
A:arrayref(a,n),n:size_t(n)
):void=let
//
funloop
{i:nat|i<=n}.<n-i>.
(
A:arrayref(a,n),n:size_tn,i:size_ti
):void=let
valn2=half(n)
in
ifi<n2thenlet
val()=arrayref_interchange(A,i,pred(n)-i)inloop(A,n,succ(i))
endelse()//endof[if]
end//endof[loop]
//
in
loop(A,n,i2sz(0))
end//endof[arrayref_reverse]
Theinterfaceforthefunctiontemplate arrayref_interchange isgivenbelow:
fun{a:vt@ype}
arrayref_interchange{n:int}
(A:arrayref(a,n),i:sizeLtn,j:sizeLtn):<!ref>void
//endof[arrayref_interchange]
Note that arrayref_interchange can not be implemented in terms of arrayref_get_at andarrayref_set_at (unlesssomeformoftype-unsafecodeisemployed).
Therearevariousfunctionsavailablefortraversinganarrayfromlefttorightorfromrighttoleft.Also,thefollowingtwofunctionscanbeconvenientlycalledtotraverseanarrayfromlefttoright:
//
fun{a:t0p}
arrayref_head{n:pos}(A:arrayref(a,n)):(a)//A[0]
fun{a:t0p}
arrayref_tail{n:pos}(A:arrayref(a,n)):arrayref(a,n-1)
//
overload.headwitharrayref_head
overload.tailwitharrayref_tail
//
Forinstance,thefold-leftfunctionforarrayscanbeimplementedasfollows:
fun{a,b:t@ype}
arrayref_foldleft{n:int}
(
f:(a,b)->a,x:a,A:arrayref(b,n),n:size_t(n)
):a=
(
ifn>0
thenarrayref_foldleft<a,b>(f,f(x,A.head()),A.tail(),pred(n))
elsex
//endof[if]
)(*endof[arrayref_foldleft]*)
Ascanbeexpected, A.head and A.tail translateinto A[0] and ptr_succ<T>(p0) ,respectively,whereTisthetypefortheelementsstoredinAandp0isthestartingaddressofA.
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter16.PersistentArrays-with-sizeIusethenamearray-with-size to refer toapersistentarraywithattachedsize information.GivenaviewtypeVT,thetypeforanarray-with-sizethatcontainsNvaluesofviewtypeVTis arrszref(VT,N) .Essentially,suchavalueisaboxedpairoftwocomponentsoftypes arrayref(VT,N) and size_t(N) .The interfaces for various functions on persistent arrays-with-size can be found inprelude/SATS/arrayref.sats.
For creating an array-with-size, the following functions arrszref_make_arrpsz andarrszref_make_arrayref canbecalled:
fun{}
arrszref_make_arrpsz
{a:vt0p}{n:int}(arrpsz(INV(a),n)):arrszref(a)
fun{}
arrszref_make_arrayref
{a:vt0p}{n:int}(arrayref(a,n),size_t(n)):arrszref(a)
//endof[arrszref_make_arrayref]
Asanexample,thefollowingcodecreatesanarray-with-sizecontainingallthedecimaldigits:
valDIGITS=(arrszref)$arrpsz{int}(0,1,2,3,4,5,6,7,8,9)
Notethat arrszref isoverloadedwith arrszref_make_arrpsz .
For reading from and writing to an array-with-size, the function templates arrszref_get_at andarrszref_set_at canbeused,respectively,whichareassignedthefollowinginterfaces:
fun{a:t@ype}
arrszref_get_at(A:arrszref(a),i:size_t):(a)
fun{a:t@ype}
arrszref_set_at(A:arrszref(a),i:size_t,x:a):void
Givenanarray-with-sizeA,anindexiandavaluev, arrszref_get_at(A,i) and arrszref_set_at(A,i,v) canbewrittenas A[i] and A[i]:=v ,respectively.Noticethatarray-boundscheckingisperformedat run-time whenever arrszref_get_at or arrszref_set_at is called, and the exceptionArraySubscriptExn israisedincaseofout-of-boundsarrayaccessbeingdetected.
Asasimpleexample,thefollowingcodeimplementsafunctionthatreversesthecontentofthearrayinsideagivenarray-with-size:
fun{a:t@ype}
arrszref_reverse
(
A:arrszref(a)
):void=let
//
valn=A.size()
valn2=half(n)
//
funloop
(i:size_t):void=let
in
ifi<n2thenlet
valtmp=A[i]
valni=pred(n)-i
in
A[i]:=A[ni];A[ni]:=tmp;loop(succ(i))
endelse()//endof[if]
end//endof[loop]
//
in
loop(i2sz(0))
end//endof[arrszref_reverse]
Arrays-with-sizecanbeagoodchoiceoverarraysinaprototypeimplementationasitisoftenmoredemanding to programwith arrays. Also, for programmerswho are yet to become familiar withdependenttypes,itisdefinitelyeasiertoworkwitharrays-with-sizethanarrays.WhenprogramminginATS,Ioftenstartoutwitharrays-with-sizeandthenreplacethemwitharrayswhenIcanseeclearbenefitsfromdoingso.
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter17.PersistentMatricesA persistent matrix of dimension m by n is just a persistent array of size m*n. Like in C, therepresentation of a matrix in ATS is row-major. In other words, element (i, j) in a matrix ofdimensionmbyniselementi*n+jintheunderlyingarraythatrepresentsthematrix.
GivenaviewtypeVTandtwointegersMandN,thetype matrixref(VT,M,N) isforpersistentmatricesofdimensionMbyNthatcontainelementsoftheviewtypeVT.Thereisnodimensioninformationattachedtomatrixref-valuesexplicitly.TheinterfacesforvariousfunctionsonpersistentmatricescanbefoundintheSATSfileprelude/SATS/matrixref.sats,whichisautomaticallyloadedbyatsopt.
Thefollowingfunctioniscommonlyusedtocreateamatrixref-value:
fun{a:t0p}
matrixref_make_elt{m,n:int}
(m:size_tm,n:size_tn,x0:a):<!wrt>matrixref(a,m,n)
//endof[matrixref_make_elt]
Giventwosizesmandnplusanelementx0, matrixref_make_elt returnsamatrixofdimensionmbyninwhicheachcellisinitializedwiththeelementx0.
Also,thefollowingcastfunctioncanbecalledtoturnanarrayintoamatrix:
castfn
arrayref2matrixref
{a:vt0p}{m,n:nat}(A:arrayref(a,m*n)):<>matrixref(a,m,n)
//endof[arrayref2matrixref]
Foraccessingandupdatingthecontentofamatrix-cell,thefollowingtwofunctions matrixref_get_atand matrixref_set_at canbecalled:
//
fun{a:t0p}
matrixref_get_at
{m,n:int}
(
A:matrixref(a,m,n),i:sizeLt(m),n:size_t(n),j:sizeLt(n)
):<!ref>(a)//endof[matrixref_get_at]
//
fun{a:t0p}
matrixref_set_at
{m,n:int}
(
A:matrixref(INV(a),m,n),i:sizeLt(m),n:size_tn,j:sizeLt(n),x:a
):<!refwrt>void//endof[matrixref_set_at]
//
Note that it is not enough to just supply the coordinates of amatrix-cell in order to access it; thecolumndimensionofthematrixneedstobesuppliedaswell.
Inthefollowingpresentation,Igiveanimplementationofafunctionthatturnsagivensquarematrixintoitstranspose:
//
extern
fun{a:t0p}
matrixref_transpose
{n:nat}
(
M:matrixref(a,n,n),n:size_t(n)
):void//endof[matrixref_transpose]
//
implement{a}
matrixref_transpose
{n}(M,n)=let
//
macdef
mget(i,j)=
matrixref_get_at(M,,(i),n,,(j))
macdef
mset(i,j,x)=
matrixref_set_at(M,,(i),n,,(j),,(x))
//
funloop
{i,j:nat|
i<j;j<=n
}.<n-i,n-j>.
(
i:size_t(i),j:size_t(j)
):void=
ifj<nthenlet
valx=mget(i,j)
val()=mset(i,j,mget(j,i))
val()=mset(j,i,x)
in
loop(i,j+1)
endelselet
vali1=succ(i)
in
ifi1<nthenloop(i1,succ(i1))else()
end//endof[if]
//
in
ifn>0thenloop(i2sz(0),i2sz(1))else()
end//endof[matrixref_transpose]
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter18.PersistentMatrices-with-sizeIuse thenamematrix-with-size to refer toapersistentmatrixwithattacheddimension information(thatis,numberofrowsandnumberofcolumns).GivenaviewtypeVT,thetypeforamatrix-with-size that contains M rows and N columns of elements of viewtype VT is mtrxszref(VT, M, N) .Essentially,suchavalueisaboxedrecordofthreecomponentsoftypes arrayref(VT, N) , size_t(M)and size_t(N) .Theinterfacesforvariousfunctionsonpersistentmatrices-with-sizecanbefoundinprelude/SATS/matrixref.sats.
Thefollowingfunctioniscommonlyusedtocreateamatrix-with-size:
fun{a:t0p}
mtrxszref_make_elt(m:size_t,n:size_t,x0:a):mtrxref(a)
//endof[mtrxszref_make_elt]
Given two sizesm and n plus an element x0, mtrxszref_make_elt returns amatrix-with-size of thedimensionmbyninwhicheachmatrix-cellisinitializedwiththegivenelementx0.
Foraccessingandupdatingthecontentofamatrix-cell,thefollowingtwofunctions mtrxszref_get_atand mtrxszref_set_at canbecalled:
fun{a:t0p}
mtrxszref_get_at(M:mtrxszref(a),i:size_t,j:size_t):(a)
fun{a:t0p}
mtrxszref_set_at(M:mtrxszref(a),i:size_t,j:size_t,x:a):void
Given a matrix-with-size M, two indices i and j, and a value v, mtrxszref_get_at(M, i, j) andmtrxszref_set_at(M, i, j, v) can be written as M[i,j] and M[i,j] := v , respectively. Notice thatmatrix-boundschecking isperformedat run-timewhenever mtrxszref_get_at or mtrxszref_set_at iscalled, and theexception MatrixSubscriptExn is raised in caseofout-of-boundsmatrix accessbeingdetected.
As a simple example, the following code implements a function that transpose the content of thematrixinsideagivenmatrix-with-size:
//
extern
fun{a:t0p}
mtrxszref_transpose(M:mtrxszref(a)):void
//
implement{a}
mtrxszref_transpose
(M)=let
//
valn=M.nrow()
//
val((*void*))=assertloc(M.nrow()=M.ncol())
//
funloop
(
i:size_t,j:size_t
):void=
ifj<nthenlet
valx=M[i,j]
val()=M[i,j]:=M[j,i]
val()=M[j,i]:=x
in
loop(i,succ(j))
endelselet
vali1=succ(i)
in
ifi1<nthenloop(i1,succ(i1))else()
end//endof[if]
//
in
ifn>0thenloop(i2sz(0),i2sz(1))else()
end//endof[mtrxszref_transpose]
Likearrays-with-size,matrices-with-sizeareeasiertoprogramwiththandependentlytypedmatrices.However,thelattercannotonlyleadtomoreeffectiveerrordetectionatcompile-timebutalsomoreefficentcodeexecutionatrun-time.ForsomeoneprogramminginATS,itisquitereasonabletostartoutwithmatrices-with-sizeand thenreplace themwithmatriceswhen thereareclearbenefits fromdoingso.
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter19.PersistentHashtablesHashtables are commonly used to implement finite maps. In ATSLIB/libats, there are hashtableimplementations based on linear chaining and linear probing. There is also support for linearhashtablesaswellaspersistenthashtables.The linearonescanbesafely freedby theprogrammer,andthepersistentones(whicharedirectlybasedonlinearones)canonlybesafelyreclaimedthroughgarbagecollection(GC).Inthischapter,Ishowhowpersistenthashtablescanbecreatedandoperatedon.
Supposethatamapisneededformappingkeysoftype key_t toitemsoftype itm_t .ThefollowingcodeessentiallysetsupaninterfaceforcreatingandoperatingonsuchamapbasedonahashtableimplementationinATSLIB/libats:
local
typedef
key=key_tanditm=itm_t
in(*in-of-local*)
#include"libats/ML/HATS/myhashtblref.hats"
end//endof[local]
Pleasefindon-linetheHATSfilementionedinthecode,whichisjustaconveniencewrappermadetosimplifyprogrammingwithhashtables.
Assumethat key_t is string and itm_t is int .Thefollowinglineofcodecreatesahashtablewithitsinitialcapacitysettobe1000:
valmymap=myhashtbl_make_nil(1000)
Note that thecapacity in thiscase is thesizeof thearrayassociatedwith thecreatedhashtable.Theunderlyinghashtable implementation isbasedon linearchaining,and thishashtablecanstoreup to5000 (5*1000) items without need for resizing. When resizing is indeed needed, it is doneautomatically.Thefollowingfewlinesinsertsomekey/itempairsinto mymap :
//
val-~None_vt()=mymap.insert("a",0)
val-~Some_vt(0)=mymap.insert("a",1)
//
val-~None_vt()=mymap.insert("b",1)
val-~Some_vt(1)=mymap.insert("b",2)
//
val-~None_vt()=mymap.insert("c",2)
val-~Some_vt(2)=mymap.insert("c",3)
//
Thedot-symbol .insert isoverloadedwithafunctionofthename myhashtbl_insert .Givenakeyandan item, mymap.insert inserts the key/item pair into mymap . If the key is in the domain of themaprepresented by mymap before insertion, then the original item associated with the key is returned.Otherwise,noitemisreturned.Ascanbeexpected,thesizeof mymap is3atthispoint:
val()=assertloc(mymap.size()=3)
Thedot-symbol .size isoverloadedwitha functionof thename myhashtbl_get_size ,which returnsthenumberofkey/itempairsstoredinagivenhashtable.Duringthecourseofdebugging,onemaywanttoprintoutthekey/itempairsinagivenhashtable:
//
val()=
fprintln!(stdout_ref,"mymap=",mymap)
//
wherethesymbol fprint isoverloadedwith fprint_myhashtbl .Thenexttwolinesofcodeshowhowsearchwithagivenkeyoperatesonahashtable:
val-~None_vt()=mymap.search("")
val-~Some_vt(1)=mymap.search("a")
Thedot-symbol .search isoverloadedwitha functionof thename myhashtbl_search ,which returnstheitemassociatedwithagivenkeyifitisfound.Thenextfewlinesofcoderemovesomekey/itempairsfrom mymap :
//
val-true=mymap.remove("a")
val-false=mymap.remove("a")
//
val-~Some_vt(2)=mymap.takeout("b")
val-~Some_vt(3)=mymap.takeout("c")
//
Thedot-symbol .remove isoverloadedwithafunctionofthename myhashtbl_remove forremovingakey/itempairofagivenkey.Ifakey/itempairisremoved,thenthefunctionreturnstrue.Otherwise,it returns false to indicates that no key/item pair of the given key is stored in the hashtable beingoperatedon.Thedot-symbol .takeout isoverloadedwithafunctionof thename myhashtbl_takeout ,whichissimilarto myhashtbl_remove exceptingforreturningtheremoveditem.Thenextfewlinesofcodemakeuseofseverallesscommonlyusedfunctionsonhashtables:
//
val()=mymap.insert_any("a",0)
val()=mymap.insert_any("b",1)
val()=mymap.insert_any("c",2)
valkxs=mymap.listize1((*void*))
val((*void*))=fprintln!(stdout_ref,"kxs=",kxs)
valkxs=mymap.takeout_all((*void*))
val((*void*))=fprintln!(stdout_ref,"kxs=",kxs)
//
val()=assertloc(mymap.size()=0)
//
Thedot-symbol .insert_any isoverloadedwitha functionof thename myhashtbl_insert_any , whichalwaysinsertsagivenkey/itempairregardlesswhetherthekeyisalreadyinuse.Oneshouldreallyavoidusingthisfunctionoronlycallitwhenitisabsolutelysurethatthegivenkeyisnotalreadyinuse for otherwise the involved hashtable would be corrupted. The dot-symbols .listize1 and.takeout_all are overloaded with two functions of the names myhashtbl_listize1 andmyhashtbl_takeout_all ,respectively.Bothofthemreturnalistconsistingofallthekey/itempairsinagivenhashtable;theformerkeepsthehashtableunchangedwhilethelatteremptiesit.Last,Ipresentasfollowstheinterfaceforaniteratorgoingoverallthekey/itempairsinagivenhashtable:
//
extern
fun
myhashtbl_foreach_cloref
(
tbl:myhashtbl
,fwork:(key,&(itm)>>_)-<cloref1>void
):void//end-of-function
//
Asanexample,thefollowingcodeprintsoutallthekey/itempairsinagivenhashtable:
//
val()=
myhashtbl_foreach_cloref
(
mymap
,lam(k,x)=>fprintln!(stdout_ref,"k=",k,"and","x=",x)
)(*myhashtbl_foreach_cloref*)
//
Please find the entirety of the code used in this chapter on-line. Also, there is a hashtable-basedimplementationofsymboltableavailableon-line.
Chapter20.Tail-RecursionPleaseseethisarticleforadetailedexplanationontail-recursionandthesupportinATSforturningtail-recursivecallsintolocaljumps.
Chapter21.Higher-OrderFunctionsAhigher-order function isone that takesanother functionas its argument.Let us useBT to rangeoverbasetypessuchas int , bool , char , double and string .AsimpletypeTisformedaccordingtothefollowinginductivedefinition:
BTisasimpletype.
(T1,...,Tn)->T0isasimpletypeifT0,T1,...Tnaresimpletypes.
Letorderbeafunctionfromsimpletypestonaturalnumbersdefinedasfollows:
order(BT)=0
order((T1,...,Tn)->T0)=max(order(T0),1+order(T1),...,1+order(Tn))
GivenafunctionfofsomesimpletypeT,letussaythatfisanth-orderfunctioniforder(T)=n.Forinstance,afunctionofthetype(int,int)->intis1st-order,andafunctionofthetypeint->(int->int)is also 1st-order, and a function of the type ((int -> int), int) -> int is 2nd-order. In practice,mostfunctionsare1st-orderandmosthigher-orderfunctionsare2nd-order.
As an example, let us implement as follows a 2nd-order function find_root that takes as its onlyargumentafunctionffromintegerstointegersandsearchesforarootoffbyenumeration:
fnfind_root
(
f:int-<cloref1>int
):int=let
//
funloop
(
f:int-<cloref1>int,n:int
):int=
iff(n)=0thennelse(
ifn<=0thenloop(f,~n+1)elseloop(f,~n)
)//endof[else]//endof[if]
in
loop(f,0)
end//endof[find_root]
Thefunction find_root computesthevaluesoffat0,1,-1,2,-2,etc.untilitfindsthefirstintegernin
thissequencethatsatisfiesf(n)=0.
Asanotherexample, letusimplementasfollowsthefamousNewton-Raphson'smethodforfindingrootsoffunctionsonreals:
typedef
fdouble=double-<cloref1>double
//
macdefepsilon=1E-6(*precision*)
//
//[f1]isthederivativeof[f]
//
fun
newton_raphson
(
f:fdouble,f1:fdouble,x0:double
):double=let
funloop(
f:fdouble,f1:fdouble,x0:double
):double=let
valy0=fx0
in
ifabs(y0/x0)<epsilonthenx0else
letvaly1=f1x0inloop(f,f1,x0-y0/y1)end
//endof[if]
end//endof[loop]
in
loop(f,f1,x0)
end//endof[newton_raphson]
With newton_raphson , both the square root function and the cubic root function can be readilyimplementedasfollows:
//squarerootfunction
fnsqrt(c:double):double=
newton_raphson(lamx=>x*x-c,lamx=>2.0*x,1.0)
//cubicrootfunction
fncbrt(c:double):double=
newton_raphson(lamx=>x*x*x-c,lamx=>3.0*x*x,1.0)
Higher-orderfunctionscanbeofgreatuseinsupportingaformofcodesharingthatisbothcommonandflexible.Asfunctionargumentsareoftenrepresentedasheap-allocatedclosuresthatcanonlybereclaimedthroughgarbagecollection(GC),higher-orderfunctionsareusedinfrequently,ifatall,ina settingwhereGC is not present. InATS, linear closures, which can be freed explictly in a safe
manner,areavailabletosupporthigher-orderfunctionsintheabsenceofGC,makingitpossibletoemploy higher-order functions extensively in systems programming (where GC is unavailable orsimplydisallowed).Thedetailsonlinearclosuresaretobegivenelsewhere.
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter22.Stream-BasedLazyEvaluationWhilethecoreofATSisbasedoncall-by-valueevaluation, thereisalsodirectsupport inATSforlazy(thatis,call-by-need)evaluation.
There is a special language construct $delay for delaying or suspending the evaluation of anexpression (by forming a thunk), and there is also a special function lazy_force for resuming asuspendedevaluation(representedbyathunk).Theabstracttypeconstructor lazy ofthesort (t@ype)=> type formsa (boxed) typewhenapplied toa type.Givenanexpressionexpof typeT, thevalue$delay(exp) of the type lazy(T) represents thesuspendedevaluationofexp.GivenavalueVof thetype lazy(T) forsometypeT,calling lazy_force onVresumesthesuspendedevaluationrepresentedbyV.Ifthecallreturns,thenthereturnedvalueisoftypeT.Theinterfaceforthefunctiontemplatelazy_force isgivenasfollows:
fun{a:t@ype}lazy_force(lazyval:lazy(a)):<!laz>a
wherethesymbol !laz indicatesaformofeffectassociatedwithlazy-evaluation.Notethatthespecialprefixoperator ! inATSisoverloadedwith lazy_force .
In prelude/SATS/stream.sats, the following types stream_con and stream are declared mutuallyrecursivelyforrepresentinglazystreams:
datatype
stream_con(a:t@ype+)=
|stream_nilof((*void*))|stream_consof(a,stream(a))
wherestream(a:t@ype)=lazy(stream_con(a))
Also, a number of common functions on streams are declared in prelude/SATS/stream.sats andimplementedinprelude/DATS/stream.dats.
ThefollowingcodegivesastandardimplementationofthesieveofEratosthenes:
//
fun
from(n:int):stream(int)=
$delay(stream_cons(n,from(n+1)))
//
funsieve
(
ns:stream(int)
):<!laz>stream(int)=$delaylet
//
//[val-]meansnowarningmessagefromthecompiler
//
val-stream_cons(n,ns)=!ns
in
stream_cons(n,sieve(stream_filter_cloref<int>(ns,lamx=>xmodn>0)))
end//endof[$delaylet]//endof[sieve]
//
valthePrimes=sieve(from(2))
//
Astreamisconstructedconsistingofalltheintegersstartingfrom2;thefirstelementofthestreamiskeptandallthemultiplesofthiselementareremovedfromthetailofthestream;thisprocessisthenrepeatedonthetailofthestreamrecursively.Clearly,thefinalstreamthusgeneratedconsistsofalltheprimenumbersorderedascendingly.
Thefunctiontemplate stream_filter_cloref isofthefollowinginterface:
fun{a:t@ype}
stream_filter_cloref
(xs:stream(a),pred:a-<cloref>bool):<!laz>stream(a)
//endof[stream_filter_cloref]
Givena streamandapredicate, stream_filter_cloref generatesanother streamconsistingof all theelementsinthegivenstreamthatsatisfythegivenpredicate.
Letusseeanotherexampleoflazyevaluation.ThefollowcodedemonstratesaninterestingapproachtocomputingtheFibonaccinumbers:
//
val_0_=$UNSAFE.cast{int64}(0)
val_1_=$UNSAFE.cast{int64}(1)
//
val//thefollowingvaluesaredefinedmutuallyrecursively
rectheFibs_0
:stream(int64)=$delay(stream_cons(_0_,theFibs_1))//fib0,fib1,...
andtheFibs_1
:stream(int64)=$delay(stream_cons(_1_,theFibs_2))//fib1,fib2,...
andtheFibs_2
:stream(int64)=//fib2,fib3,fib4,...
(
stream_map2_fun<int64,int64><int64>(theFibs_0,theFibs_1,lam(x,y)=>x+y)
)(*endof[val/and/and]*)
//
Thefunctiontemplate stream_map2_fun isassignedthefollowinginterface:
fun{
a1,a2:t0p}{b:t0p
}stream_map2_cloref
(
xs1:stream(a1),xs2:stream(a2),f:(a1,a2)-<fun>b
):<!laz>stream(b)//endof[stream_map2_cloref]
Giventwostreamsxs1andxs2andabinaryfunctionf, stream_map2_fun formsastreamxssuchthatxs[n](thatis,elementninxs),ifexists,equalsf(xs1[n],xs2[n]),wherenrangesovernaturalnumbers.
Letusseeyetanotherexampleoflazyevaluation.AHammingnumberisapositivenaturalnumberwhoseprimefactorscancontainonly2,3and5.ThefollowingcodeshowsastraightforwardwaytogenerateastreamconsistingofalltheHammingnumbers:
//
val
compare_int_int=
lam(x1:int,x2:int):int=<fun>compare(x1,x2)
//
macdef
merge2(xs1,xs2)=
stream_mergeq_fun<int>(,(xs1),,(xs2),compare_int_int)
//
val
rectheHamming
:stream(int)=$delay
(
stream_cons(1,merge2(merge2(theHamming2,theHamming3),theHamming5))
)(*endof[theHamming]*)
andtheHamming2
:stream(int)=stream_map_fun<int><int>(theHamming,lamx=>2*x)
andtheHamming3
:stream(int)=stream_map_fun<int><int>(theHamming,lamx=>3*x)
andtheHamming5
:stream(int)=stream_map_fun<int><int>(theHamming,lamx=>5*x)
//
Thefunctiontemplate stream_mergeq_fun isgiventhefollowinginterface:
fun{a:t0p}
stream_mergeq_fun
(
xs1:stream(a),xs2:stream(a),(a,a)-<fun>int
):<!laz>stream(a)//endof[stream_mergeq_fun]
Given twostreamsandanordering (representedbya function)such that the twostreamsare listedascendingly according to the ordering, stream_mergeq_fun returns a stream listed ascendingly thatrepresents theunionof the twogivenstreamssuch thatanyelements in thesecondstreamthatalsooccurinthefirststreamaredropped.
Withstream-basedlazyevaluation,anillusionofinfinitedatacanbereadilycreated.Thisillusionisgiven a simple programming interface plus automatic support for memoization, enabling aprogrammingstylethatcanoftenbebothelegantandintriguing.
Ingeneral,itisdifficulttoestimatethetime-complexityandspace-complexityofaprogrambasedonlazyevaluation.Thisisregardedasaseriousweakness.Withlinearstream-basedlazyevalution,thisweaknesscanessentiallyberemoved.
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter23.LinearlyTypedListsA linearly typed list inATS is also referred to as a linear list,which essentially corresponds to asingly-linkedlistinC.Thefollowinglineardatatypedeclarationintroducesalineartype list_vt forlinearlists:
//
datavtype
list_vt(a:vt@ype,int)=
|list_vt_nil(a,0)of()
|{n:nat}
list_vt_cons(a,n+1)of(a,list_vt(a,n))
//
Notethatthekeyword datavtype canalsobewrittenas dataviewtype .Givena(possiblylinear)typeTandanintegerN,thetype list_vt(T,N) isforalistoflengthNthatcontainselementsoftypeT.Theinterfaces for various functions on linear lists can be found in the SATS fileprelude/SATS/list_vt.sats,whichisautomaticallyloadedbyatsopt.
Thefollowingfunction list_vt_length showsa typicalwayofhandlinga linear list in a read-onlysituation:
//
fun
{a:vt@ype}
list_vt_length
(xs:!list_vt(a,n)):int(n)=
(
case+xsof
|list_vt_nil()=>0
|list_vt_cons(x,xs2)=>1+list_vt_length<a>(xs2)
)
//
When xs ismatchedwith the pattern list_vt_nil() , the type of xs is list_vt(a, 0) .When xs ismatchedwith the pattern list_vt_cons(x, xs2) , the type of xs is list_vt(a, N+1) for some naturalnumberNandthetypesof x and xs2 are a and list_vt(a,N) ,respectively.Notethatboth x and xs2arenamesforvalues,andtheirtypesarerequiredtostayunchanged.
Thefollowingfunction list_vt_foreach showsatypicalwayofmodifyingelementsstoredinalinear
list:
//
fun
{a:vt@ype}
list_vt_foreach
(
xs:!list_vt(a,n)
,fwork:(&(a)>>_)-<cloref1>void
):void=
(
case+xsof
|list_vt_nil()=>()
|@list_vt_cons(x,xs2)=>(fwork(x);list_vt_foreach<a>(xs2,fwork);fold@(xs))
)
//
When xs ismatchedwiththepattern @list_vt_cons(x,xs2) ,thetypeof xs is list_vt(a,N+1) forsomenaturalnumberNandthetypesof x and xs2 are a and list_vt(a,N) ,respectively.Notethatboth xand xs2 are variables (that are a formof left-values).At the beginning of the body following thepattern @list_vt_cons(x,xs2) ,thetypeof xs isassumedtobe list_vt_cons_unfold(L0,L1,L2) ,whichisaviewtypeforalist-nodecreatedbyacallto list_vt_cons suchthatthenodeislocatedatL0andthetwoargumentsof list_vt_cons arelocatedatL1andL2whiletheproofsfortheat-viewsassociatedwithL1andL2areput in thestore forcurrentlyavailableproofs.Therefore,as left-values, x andxs2 haveaddressesL1andL2,respectively,andtheviewsoftheproofsassociatedwithL1andL2area@L1 and list_vt_cons(a,N)@L2 ,respectively.Theapplication fold@(xs) turns xs intoavalueof thetype list_vt(a,N+1) whileconsumingtheproofsassociatedwithL1andL2.Noticethatthetypeof xscanbedifferent fromtheoriginaloneassigned to itafter folding.Thefollowingexampleshowsacaseassuch:
//
fun
{a:vt@ype}
list_vt_append
{m,n:nat}
(
xs:list_vt(a,m),ys:list_vt(a,n)
):list_vt(a,m+n)=let
//
fun
loop{m:nat}
(
xs:&list_vt(a,m)>>list_vt(a,m+n),ys:list_vt(a,n)
):void=
(
case+xsof
|~list_vt_nil()=>(xs:=ys)
|@list_vt_cons(x,xs2)=>(loop(xs2,ys);fold@(xs))
)
//
in
case+xsof
|~list_vt_nil()=>ys
|@list_vt_cons(x,xs2)=>(loop(xs2,ys);fold@(xs);xs)
end//endof[list_vt_append]
//
Themeaningofthesymbol ~ infrontofapatternistobeexplainedbelow.Theimplementationoflist_vt_append exactlycorrespondstothestandardimplementaionofconcatenatingtwosingly-linkedlistsinC:Letxsandysbetwogivenlists;ifxsisempty,thenysisreturned;otherwise,thelastnodeinxsislocatedandysisstoredinthefieldofthenodereservedforthenextnode.
Thefollowingfunction list_vt_free freesagivenlinearlistcontainingnon-linearelements:
//
fun
{a:vt@ype}
list_vt_free
{n:nat}
(
xs:list_vt(a?,n)
):void=
(
case+xsof
|~list_vt_nil()=>()
|~list_vt_cons(x,xs2)=>list_vt_free<a>(xs2)
)
//
When xs is matched with the pattern ~list_vt_nil() , the type of xs changes to a special oneindicating that xs is no longer available for subsequent use.When xs ismatchedwith the pattern~list_vt_cons(x,xs2) , the typeof xs changesagain toa specialone indicating that xs isno longeravailableforsubsequentuse.Inthelattercase,thetwovaluesrepresentingtheheadandtailofthelistreferredtoas xs canbesubsequentlyreferredtoas x and xs2 ,respectively.Sowhatisreallyfreedhereisthememoryforthefirstlist-nodeinthelistreferredtoas xs .
Pleasefindon-linetheentiretyofthecodeusedinthischapterplussometestingcode.
II.AdvancedTutorialTopicsTableofContents24.Extvar-Declaration25.LinearClosure-Functions26.AutomaticCodeGeneration
Chapter24.Extvar-DeclarationATSputsgreatemphasisoninteractingwithotherprogramminglanguages.
SupposethatIhaveinsomeCcodea(global)integervariableofthename foo andIwanttoincreaseinsomeATScodethevaluestoredin foo by1.Thiscanbedoneasfollows:
valx0=$extval(int,"foo")//getthevalueoffoo
valp_foo=$extval(ptr,"&foo")//gettheaddressoffoo
val()=$UNSAFE.ptr_set<int>(p_foo,x0+1)//updatefoo
wheretheaddress-ofoperator(&)inCisneededfortakingtheaddressof foo .IfIwanttointeractinATSwithalanguagethatdoesnotsupporttheaddress-ofoperator(e.g.,JavaScriptandPython),thenIcandoitasfollows:
extvar"foo"=x0+1
wherethekeyword extvar indicatesthatthestringfollowingitreferstoanexternalvariable(orleft-value)thatshouldbeupdatedwiththevalueoftheexpressionontheright-handsideoftheequalitysymbolfollowingthestring.Ofcourse,thisworksforlanguageslikeCthatdosupporttheaddress-ofoperatoraswell.Thisso-calledextvar-declarationcanalsobewrittenasfollows:
externvar"foo"=x0+1
where extvar expandsinto externvar .
Asforanotherexample,letussupposethat foo2 isarecordvariablethatcontainstwointegerfieldsnamed first and second . Then the following code assigns integers 1 and 2 to these two fields offoo2 :
extvar"foo2.first"=1
extvar"foo2.second"=2
Byitsverynature, thefeatureofextvar-declarationis inherentlyunsafe,anditshouldonlybeusedwithcaution.
Pleasefindon-linetheentiretyofthecodepresentedinthischapter.
Chapter25.LinearClosure-FunctionsAclosure-functionisaboxedrecordthatcontainsapointertoanenvlessfunctionplusbindingsthatmapcertainnamesinthebodyoftheenvlessfunctiontovalues.Inpractice,afunctionargumentofahigher-order function isoftena closure-function (insteadof anenvless function).For instance, thefollowinghigher-orderfunction list_map_cloref takesaclosure-functionasitssecondargument:
fun{
a:t@ype}{b:t@ype
}list_map_cloref{n:int}
(xs:list(a,n),f:(a)-<cloref>b):list_vt(b,n)
Closure-functionscanbeeitherlinearornon-linear,andlinearonescanbeexplicitlyfreedinasafemanner.Thekeyword -<cloref> isusedtoformatypefornon-linearclosure-functions.Asavariantof list_map_cloref , the following higher-order function list_map_cloptr takes a linear closure-functionasitssecondargument:
fun{
a:t@ype}{b:t@ype
}list_map_cloptr{n:int}
(xs:list(a,n),f:!(a)-<cloptr>b):list_vt(b,n)
Ascanbeeasilyguessed,thekeyword -<cloptr> isusedtoformatypeforlinearclosure-functions.Note that the symbol ! indicates that the second argument is still available after a call tolist_map_cloptr returns.
Atypicalexamplemakinguseof list_map_cloptr isgivenasfollows:
funfoo{n:int}
(
x0:int,xs:list(int,n)
):list_vt(int,n)=reswhere
{
//
valf=lam(x)=<cloptr>x0+x
valres=list_map_cloptr(xs,f)
val()=cloptr_free($UNSAFE.cast{cloptr(void)}(f))
//
}(*endof[foo]*)
Notethatalinearclosureisfirstcreatedinthebodyofthefunction foo ,anditisexplicitlyfreedafter
itsuse.Thefunction cloptr_free isgiventhefollowinginterface:
funcloptr_free{a:t0p}(pclo:cloptr(a)):void
where cloptr is abstract. The cast $UNSAFE.cast{cloptr(void)}(f) can certainly be replaced withsomethingsaferbutitwouldmakeprogrammingmorecurbersome.
There is also some interesting interaction between currying and linear closure-functions. Infunctional programming, currying means turning a function taking multiple argumentssimutaneously into a corresponding one that takes these arguments sequentially. For instance, thefunction acker2 inthefollowingcodeisacurriedversionofthefunction acker ,which implementsthefamousAckermannfunction(thatisrecursivebutnotprimitiverecursive):
fun
acker(m:int,n:int):int=
(
case+(m,n)of
|(0,_)=>n+1
|(m,0)=>acker(m-1,1)
|(_,_)=>acker(m-1,acker(m,n-1))
)(*endof[acker]*)
funacker2(m:int)(n:int):int=acker(m,n)
Suppose that we apply acker2 to two integers 3 and 4: acker2(3)(4) ; the application acker2(3)
evaluatestoa(non-linear)closure-function;theapplicationofthisclosure-functionto4evaluatestoacker(3,4) ,whichfurtherevaluatestotheinteger125.Notethattheclosure-functiongeneratedfromevaluating acker2(3) becomesaheap-allocatedvaluethatisnolongeraccessibleaftertheevaluationof acker2(3)(4) finishes,and thememoryforsuchavaluecanonly tobesafelyreclaimedthroughgarbagecollection(GC).
Itisalsopossibletodefineacurriedversionof acker asfollows:
funacker3(m:int)=lam(n:int):int=<cloptr1>acker(m,n)
While the evaluation of acker3(3)(4) yields the same result as acker2(3)(4) , the compiler of ATS(ATS/Postiats) inserts code that automatically frees the linear closure-function generated fromevaluating acker3(3) aftertheevaluationof acker3(3)(4) finishes.
InATS1,linearclosure-functionsplayapivotalroleinsupportingprogrammingwithhigher-order
functions in theabsenceofGC.Duetoadvancedsupportfor templates inATS2, theroleplayedbylinear closure-functions in ATS1 is greatly diminished. However, if closure-functions need to bestoredinadatastructurebutGCisunavailableorundesirable,thenusinglinearclosure-functionscanleadtoasolutionthatavoidstheriskofgeneratigmemoryleaksatrun-time.
Pleasefindon-linetheentiretyofthecodeusedinthischapter.
Chapter26.AutomaticCodeGenerationInpractice,oneoftenencountersaneedtowriteboilerplatecodeorcodethattendstofollowcertainclearly recognizable patterns. It is commonly seen that meta-programming (of various forms) isemployed toautomaticallygenerate suchcode, thusnotonly increasingprogrammingproductivitybut also potentially eliminating bugs that would otherwise be introduced due to manual codeconstruction.
Inthefollowingpresentation,IamtoshowthattheATScompilercanbedirectedtogeneratethecodeforcertainfunctionsonvaluesofadeclareddatatype.Followingisthedatatypeusedforillustration:
//
datatypeexpr=
|Intofint
|Varofstring
|Addof(expr,expr)
|Subof(expr,expr)
|Mulof(expr,expr)
|Divof(expr,expr)
|Ifgtzof(expr,expr,expr)//ifexpr>0then...else...
|Ifgtezof(expr,expr,expr)//ifexpr>=0then...else...
//
whichisforsomekindofabstractsyntaxtreesrepresentingarithmeticexpressions.
Generatingadatcon-function
Givenadatatype, itsdatcon-function is theone that takesavalueof thedatatypeand thenreturnsastringrepresentingthenameofthe(outmost)constructorintheconstructionofthevalue.Wecanusethe following directive to indicate (to theATS compiler) that the datcon-function for the datatypeexpr needstobegenerated:
#codegen2("datcon",expr)
Bydefault,thenameofthegeneratedfunctionis datcon_expr .Ifadifferentnameisneeded,itcanbesupplied as the third argument of the #codegen2 -directive. For instance, the following directiveindicatesthatthegeneratedfunctionisofthegivenname my_datcon_expr :
#codegen2("datcon",expr,my_datcon_expr)
Assumethatafileofthenameexpr.datscontainsthefollowingdirective(asatopleveldeclaration):
#codegen2("datcon",expr)
and the definition for expr is accessible at the point where the codegen2 -directive is declared. Byexecutingthefollowingcommand-line:
patscc--codegen-2-dexpr.dats
wecanseesomeoutputofATScodethatimplements datcon_expr :
(**************)
//
implement
{}(*tmp*)
datcon_expr
(arg0)=
(
case+arg0of
|Int_=>"Int"
|Var_=>"Var"
|Add_=>"Add"
|Sub_=>"Sub"
|Mul_=>"Mul"
|Div_=>"Div"
|Ifgtz_=>"Ifgtz"
|Ifgtez_=>"Ifgtez"
)
//
(**************)
If the output needs to be stored in a file of the name fprint_expr.hats ,we can issue the followingcommand-line:
patscc-ofprint_expr.hats--codegen-2-dexpr.dats
Note that the funtion template datcon_expr is required to be declared somewhere in order for thegeneratedcodetobecompiledproperly:
fun{}datcon_expr:(expr)->string//afunctiontemplate
Pleasefindon-line theentiretyof thispresentedexampleplusaMakefile (for illustrating thecodegenerationprocess).
Generatingadatcontag-function
Adatcontag-functionisverysimilartoadatcon-function.Givenadatatype,itsdatcontag-functionistheonethattakesavalueofthedatatypeandthenreturnsthetag(whichisasmallinteger)assignedtothe (outmost) constructor in the construction of the value.We can use the following directive toindicate(totheATScompiler)thatthedatcontag-functionforthedatatype expr needstobegenerated:
#codegen2("datcontag",expr)
Bydefault,thenameofthegeneratedfunctionis datcontag_expr .Ifadifferentnameisneeded,itcanbe supplied as the third argument of the #codegen2 -directive. For instance, the following directiveindicatesthatthegeneratedfunctionisofthegivenname my_datcontag_expr :
#codegen2("datcontag",expr,my_datcontag_expr)
ThefollowingATScodeisexpectedtobegeneratedthatimplements datcontag_expr :
(**************)
//
implement
{}(*tmp*)
datcontag_expr
(arg0)=
(
case+arg0of
|Int_=>0
|Var_=>1
|Add_=>2
|Sub_=>3
|Mul_=>4
|Div_=>5
|Ifgtz_=>6
|Ifgtez_=>7
)
//
(**************)
Notethatthefuntiontemplate datcontag_expr isrequiredtobedeclaredsomewhereinorderfor thegeneratedcodetobecompiledproperly:
fun{}datcontag_expr:(expr)->intGte(0)//afunctiontemplate
Pleasefindon-line theentiretyof thispresentedexampleplusaMakefile (for illustrating thecodegenerationprocess).
Generatingafprint-function
A fprint-function takes a file-handle (of the type FILEref ) and a value and then outputs a textrepresentationofthevaluetothefile-handle.Givenadatatype,oneisofteninneedofafunctionthatcanoutputcertainkindoftextrepresentationforvaluesofthisdatatype.Forinstance,suchafunctioncanbeofgreatuseindebugging.
Letusfirstdeclareafunctiontemplate fprint_expr asfollows:
fun{}fprint_expr:(FILEref,expr)->void//afunctiontemplate
Wecanthenusethedirectivebelowtoindicate(totheATScompiler)thatthefprint-functionforthedatatype expr needstobegenerated:
#codegen2("fprint",expr,fprint_expr)
The third argument of the codegen2 -directive can be omitted in this case as it coincides with thedefault.Thegeneratedcodethatimplements fprint_expr islistedasfollows:
(**************)
//
extern
fun{}
fprint_expr$Int:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Var:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Add:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Sub:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Mul:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Div:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtz:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtez:$d2ctype(fprint_expr<>)
//
(**************)
//
implement{}
fprint_expr
(out,arg0)=
(
case+arg0of
|Int_=>fprint_expr$Int<>(out,arg0)
|Var_=>fprint_expr$Var<>(out,arg0)
|Add_=>fprint_expr$Add<>(out,arg0)
|Sub_=>fprint_expr$Sub<>(out,arg0)
|Mul_=>fprint_expr$Mul<>(out,arg0)
|Div_=>fprint_expr$Div<>(out,arg0)
|Ifgtz_=>fprint_expr$Ifgtz<>(out,arg0)
|Ifgtez_=>fprint_expr$Ifgtez<>(out,arg0)
)
//
(**************)
//
extern
fun{}
fprint_expr$sep:(FILEref)->void
implement{}
fprint_expr$sep(out)=fprint(out,",")
//
extern
fun{}
fprint_expr$lpar:(FILEref)->void
implement{}
fprint_expr$lpar(out)=fprint(out,"(")
//
extern
fun{}
fprint_expr$rpar:(FILEref)->void
implement{}
fprint_expr$rpar(out)=fprint(out,")")
//
extern
fun{a:t0p}
fprint_expr$carg:(FILEref,INV(a))->void
implement{a}
fprint_expr$carg(out,arg)=fprint_val<a>(out,arg)
//
(**************)
//
extern
fun{}
fprint_expr$Int$con:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Int$lpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Int$rpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Int$arg1:$d2ctype(fprint_expr<>)
//
implement{}
fprint_expr$Int(out,arg0)=
{
//
val()=fprint_expr$Int$con<>(out,arg0)
val()=fprint_expr$Int$lpar<>(out,arg0)
val()=fprint_expr$Int$arg1<>(out,arg0)
val()=fprint_expr$Int$rpar<>(out,arg0)
//
}
implement{}
fprint_expr$Int$con(out,_)=fprint(out,"Int")
implement{}
fprint_expr$Int$lpar(out,_)=fprint_expr$lpar(out)
implement{}
fprint_expr$Int$rpar(out,_)=fprint_expr$rpar(out)
implement{}
fprint_expr$Int$arg1(out,arg0)=
letval-Int(arg1)=arg0infprint_expr$carg(out,arg1)end
//
extern
fun{}
fprint_expr$Var$con:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Var$lpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Var$rpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Var$arg1:$d2ctype(fprint_expr<>)
//
implement{}
fprint_expr$Var(out,arg0)=
{
//
val()=fprint_expr$Var$con<>(out,arg0)
val()=fprint_expr$Var$lpar<>(out,arg0)
val()=fprint_expr$Var$arg1<>(out,arg0)
val()=fprint_expr$Var$rpar<>(out,arg0)
//
}
implement{}
fprint_expr$Var$con(out,_)=fprint(out,"Var")
implement{}
fprint_expr$Var$lpar(out,_)=fprint_expr$lpar(out)
implement{}
fprint_expr$Var$rpar(out,_)=fprint_expr$rpar(out)
implement{}
fprint_expr$Var$arg1(out,arg0)=
letval-Var(arg1)=arg0infprint_expr$carg(out,arg1)end
//
extern
fun{}
fprint_expr$Add$con:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Add$lpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Add$rpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Add$sep1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Add$arg1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Add$arg2:$d2ctype(fprint_expr<>)
//
implement{}
fprint_expr$Add(out,arg0)=
{
//
val()=fprint_expr$Add$con<>(out,arg0)
val()=fprint_expr$Add$lpar<>(out,arg0)
val()=fprint_expr$Add$arg1<>(out,arg0)
val()=fprint_expr$Add$sep1<>(out,arg0)
val()=fprint_expr$Add$arg2<>(out,arg0)
val()=fprint_expr$Add$rpar<>(out,arg0)
//
}
implement{}
fprint_expr$Add$con(out,_)=fprint(out,"Add")
implement{}
fprint_expr$Add$lpar(out,_)=fprint_expr$lpar(out)
implement{}
fprint_expr$Add$rpar(out,_)=fprint_expr$rpar(out)
implement{}
fprint_expr$Add$sep1(out,_)=fprint_expr$sep<>(out)
implement{}
fprint_expr$Add$arg1(out,arg0)=
letval-Add(arg1,_)=arg0infprint_expr$carg(out,arg1)end
implement{}
fprint_expr$Add$arg2(out,arg0)=
letval-Add(_,arg2)=arg0infprint_expr$carg(out,arg2)end
//
extern
fun{}
fprint_expr$Sub$con:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Sub$lpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Sub$rpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Sub$sep1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Sub$arg1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Sub$arg2:$d2ctype(fprint_expr<>)
//
implement{}
fprint_expr$Sub(out,arg0)=
{
//
val()=fprint_expr$Sub$con<>(out,arg0)
val()=fprint_expr$Sub$lpar<>(out,arg0)
val()=fprint_expr$Sub$arg1<>(out,arg0)
val()=fprint_expr$Sub$sep1<>(out,arg0)
val()=fprint_expr$Sub$arg2<>(out,arg0)
val()=fprint_expr$Sub$rpar<>(out,arg0)
//
}
implement{}
fprint_expr$Sub$con(out,_)=fprint(out,"Sub")
implement{}
fprint_expr$Sub$lpar(out,_)=fprint_expr$lpar(out)
implement{}
fprint_expr$Sub$rpar(out,_)=fprint_expr$rpar(out)
implement{}
fprint_expr$Sub$sep1(out,_)=fprint_expr$sep<>(out)
implement{}
fprint_expr$Sub$arg1(out,arg0)=
letval-Sub(arg1,_)=arg0infprint_expr$carg(out,arg1)end
implement{}
fprint_expr$Sub$arg2(out,arg0)=
letval-Sub(_,arg2)=arg0infprint_expr$carg(out,arg2)end
//
extern
fun{}
fprint_expr$Mul$con:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Mul$lpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Mul$rpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Mul$sep1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Mul$arg1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Mul$arg2:$d2ctype(fprint_expr<>)
//
implement{}
fprint_expr$Mul(out,arg0)=
{
//
val()=fprint_expr$Mul$con<>(out,arg0)
val()=fprint_expr$Mul$lpar<>(out,arg0)
val()=fprint_expr$Mul$arg1<>(out,arg0)
val()=fprint_expr$Mul$sep1<>(out,arg0)
val()=fprint_expr$Mul$arg2<>(out,arg0)
val()=fprint_expr$Mul$rpar<>(out,arg0)
//
}
implement{}
fprint_expr$Mul$con(out,_)=fprint(out,"Mul")
implement{}
fprint_expr$Mul$lpar(out,_)=fprint_expr$lpar(out)
implement{}
fprint_expr$Mul$rpar(out,_)=fprint_expr$rpar(out)
implement{}
fprint_expr$Mul$sep1(out,_)=fprint_expr$sep<>(out)
implement{}
fprint_expr$Mul$arg1(out,arg0)=
letval-Mul(arg1,_)=arg0infprint_expr$carg(out,arg1)end
implement{}
fprint_expr$Mul$arg2(out,arg0)=
letval-Mul(_,arg2)=arg0infprint_expr$carg(out,arg2)end
//
extern
fun{}
fprint_expr$Div$con:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Div$lpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Div$rpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Div$sep1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Div$arg1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Div$arg2:$d2ctype(fprint_expr<>)
//
implement{}
fprint_expr$Div(out,arg0)=
{
//
val()=fprint_expr$Div$con<>(out,arg0)
val()=fprint_expr$Div$lpar<>(out,arg0)
val()=fprint_expr$Div$arg1<>(out,arg0)
val()=fprint_expr$Div$sep1<>(out,arg0)
val()=fprint_expr$Div$arg2<>(out,arg0)
val()=fprint_expr$Div$rpar<>(out,arg0)
//
}
implement{}
fprint_expr$Div$con(out,_)=fprint(out,"Div")
implement{}
fprint_expr$Div$lpar(out,_)=fprint_expr$lpar(out)
implement{}
fprint_expr$Div$rpar(out,_)=fprint_expr$rpar(out)
implement{}
fprint_expr$Div$sep1(out,_)=fprint_expr$sep<>(out)
implement{}
fprint_expr$Div$arg1(out,arg0)=
letval-Div(arg1,_)=arg0infprint_expr$carg(out,arg1)end
implement{}
fprint_expr$Div$arg2(out,arg0)=
letval-Div(_,arg2)=arg0infprint_expr$carg(out,arg2)end
//
extern
fun{}
fprint_expr$Ifgtz$con:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtz$lpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtz$rpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtz$sep1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtz$sep2:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtz$arg1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtz$arg2:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtz$arg3:$d2ctype(fprint_expr<>)
//
implement{}
fprint_expr$Ifgtz(out,arg0)=
{
//
val()=fprint_expr$Ifgtz$con<>(out,arg0)
val()=fprint_expr$Ifgtz$lpar<>(out,arg0)
val()=fprint_expr$Ifgtz$arg1<>(out,arg0)
val()=fprint_expr$Ifgtz$sep1<>(out,arg0)
val()=fprint_expr$Ifgtz$arg2<>(out,arg0)
val()=fprint_expr$Ifgtz$sep2<>(out,arg0)
val()=fprint_expr$Ifgtz$arg3<>(out,arg0)
val()=fprint_expr$Ifgtz$rpar<>(out,arg0)
//
}
implement{}
fprint_expr$Ifgtz$con(out,_)=fprint(out,"Ifgtz")
implement{}
fprint_expr$Ifgtz$lpar(out,_)=fprint_expr$lpar(out)
implement{}
fprint_expr$Ifgtz$rpar(out,_)=fprint_expr$rpar(out)
implement{}
fprint_expr$Ifgtz$sep1(out,_)=fprint_expr$sep<>(out)
implement{}
fprint_expr$Ifgtz$sep2(out,_)=fprint_expr$sep<>(out)
implement{}
fprint_expr$Ifgtz$arg1(out,arg0)=
letval-Ifgtz(arg1,_,_)=arg0infprint_expr$carg(out,arg1)end
implement{}
fprint_expr$Ifgtz$arg2(out,arg0)=
letval-Ifgtz(_,arg2,_)=arg0infprint_expr$carg(out,arg2)end
implement{}
fprint_expr$Ifgtz$arg3(out,arg0)=
letval-Ifgtz(_,_,arg3)=arg0infprint_expr$carg(out,arg3)end
//
extern
fun{}
fprint_expr$Ifgtez$con:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtez$lpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtez$rpar:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtez$sep1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtez$sep2:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtez$arg1:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtez$arg2:$d2ctype(fprint_expr<>)
extern
fun{}
fprint_expr$Ifgtez$arg3:$d2ctype(fprint_expr<>)
//
implement{}
fprint_expr$Ifgtez(out,arg0)=
{
//
val()=fprint_expr$Ifgtez$con<>(out,arg0)
val()=fprint_expr$Ifgtez$lpar<>(out,arg0)
val()=fprint_expr$Ifgtez$arg1<>(out,arg0)
val()=fprint_expr$Ifgtez$sep1<>(out,arg0)
val()=fprint_expr$Ifgtez$arg2<>(out,arg0)
val()=fprint_expr$Ifgtez$sep2<>(out,arg0)
val()=fprint_expr$Ifgtez$arg3<>(out,arg0)
val()=fprint_expr$Ifgtez$rpar<>(out,arg0)
//
}
implement{}
fprint_expr$Ifgtez$con(out,_)=fprint(out,"Ifgtez")
implement{}
fprint_expr$Ifgtez$lpar(out,_)=fprint_expr$lpar(out)
implement{}
fprint_expr$Ifgtez$rpar(out,_)=fprint_expr$rpar(out)
implement{}
fprint_expr$Ifgtez$sep1(out,_)=fprint_expr$sep<>(out)
implement{}
fprint_expr$Ifgtez$sep2(out,_)=fprint_expr$sep<>(out)
implement{}
fprint_expr$Ifgtez$arg1(out,arg0)=
letval-Ifgtez(arg1,_,_)=arg0infprint_expr$carg(out,arg1)end
implement{}
fprint_expr$Ifgtez$arg2(out,arg0)=
letval-Ifgtez(_,arg2,_)=arg0infprint_expr$carg(out,arg2)end
implement{}
fprint_expr$Ifgtez$arg3(out,arg0)=
letval-Ifgtez(_,_,arg3)=arg0infprint_expr$carg(out,arg3)end
//
(**************)
Thecodefor fprint_expr isentirelytemplate-based.Thisstylemakesthecodeextremelyflexibleforadaption through template re-mplementation. As the datatype expr is recursively defined, thefollowingtemplateimplementationneedstobeaddedinordertomake fprint_expr work:
implementfprint_expr$card<expr>=fprint_expr
Forinstance,applying fprint_expr totheexpression Add(Int(10),Mul(Int(1),Int(2))) outputsthesametextrepresentation.Asanexampleofadaptation,letusaddthefollowingtemplateimplementations:
implement
fprint_expr$Add$con<>(_,_)=()
implement
fprint_expr$Add$sep1<>(out,_)=fprint!(out,"+")
When fprint_expr isappliedtotheexpression Add(Int(10),Mul(Int(1),Int(2))) thistime,theoutputisexpectedtoread (Int(10)+Mul(Int(1),Int(2))) .
Afterproperadaptationisdone,onecanintroducea(non-template)functionasfollows:
//
extern
fun
my_fprint_expr(FILEref,expr):void
implement
my_fprint_expr(out,x)=fprint_expr<>(out,x)
//
Inthisway,onlyoneinstanceof fprint_expr iscompiledevenifrepeatedcallsto my_fprint_expr aremade.
Pleasefindon-line theentiretyof thispresentedexampleplusaMakefile (for illustrating thecodegenerationprocess).
Done.