Exploring Programming Language Architecture in Perl.pdf

Exploring Programming Language Architecture in Perl.pdf

ID:34213987

大小:2.63 MB

页数:368页

时间:2019-03-04

上传者:不努力梦想只是梦
Exploring Programming Language Architecture in Perl.pdf_第1页
Exploring Programming Language Architecture in Perl.pdf_第2页
Exploring Programming Language Architecture in Perl.pdf_第3页
Exploring Programming Language Architecture in Perl.pdf_第4页
Exploring Programming Language Architecture in Perl.pdf_第5页
资源描述:

《Exploring Programming Language Architecture in Perl.pdf》由会员上传分享,免费在线阅读,更多相关内容在学术论文-天天文库

ExploringProgrammingLanguageArchitectureinPerlBillHailsSunMar1410:43:132010UST c2010byBillHails.Somerightsreserved.Authormaybecontactedatme@billhails.net.TypesetbytheauthorinXMLandtranslatedintoLATEX2"bycustomsoftware.CoverimagewritteninPOVRaybytheauthor.Description:anonlinebookusingthePerlprogramminglanguagetoexplorevariousaspectsofprogramminglanguagearchitecture.Keywords:perl,scheme,interpreter,pschemeISBN:978-1-4452-2592-0 ContentsIImplementingaScheme-LikeInterpreter11Introduction31.1WhyPerl?.............................................41.2WhyScheme?...........................................41.3References.............................................51.4Typography............................................51.5ANoteontheInterpreterVersions...............................52AnIntroductiontoPScheme72.1PSchemeSyntax.........................................72.2SimpleExpressions........................................82.3Conditionals............................................92.4GlobalVariables.........................................102.5Functions.............................................112.6LocalVariables..........................................123InterpreterVersion0.0.0153.1TheRead-Eval-PrintLoop....................................153.2TheEnvironment.........................................163.3TheReader............................................173.4PSchemeExpressions.......................................223.5Evaluation.............................................253.5.1EvaluationofLiterals..................................253.5.2EvaluationofSymbols..................................253.5.3EvaluationofLists....................................263.6PrimitiveOperations.......................................273.7SpecialForms...........................................283.8Output..............................................293.9Summary.............................................313.10Tests................................................333.11Listings..............................................343.11.1PScm.pm..........................................343.11.2PScm/Env.pm.......................................363.11.3PScm/Read.pm......................................373.11.4PScm/Token.pm......................................393.11.5PScm/Primitive.pm...................................40iii ivCONTENTS3.11.6PScm/SpecialForm.pm..................................423.11.7PScm/Expr.pm......................................433.11.8t/PScm.t.........................................463.11.9t/lib/PScm/Test.pm..................................473.11.10t/interactive......................................484Implementinglet494.1TheEnvironment.........................................494.1.1AStack-basedEnvironment...............................494.1.2ALinkedListEnvironment...............................504.2GlobalEnvironmentshaveaProblem.............................524.3EnvironmentPassing.......................................534.4letItself.............................................554.5Summary.............................................564.6Tests................................................564.7Listings..............................................574.7.1t/PScmLet.t.......................................575Implementinglambda595.1lambda...............................................615.2EvaluatingaClosure.......................................665.3PrintingaClosure........................................685.4Summary.............................................695.5Tests................................................695.6Listings..............................................705.6.1PScm/Closure.pm....................................705.6.2t/PScmLambda.t.....................................726Recursionandletrec736.1letrec...............................................746.1.1Assignment........................................756.1.2PSCM::LetRecitself...................................766.2Summary.............................................786.3Tests................................................796.4Listings..............................................806.4.1t/PScmLetrec.t.....................................807AnotherVariationonlet817.1SequentialBinding........................................817.2let*................................................827.3Summary.............................................837.4Tests................................................847.5Listings..............................................857.5.1t/PScmLetStar.t....................................85 CONTENTSv8ListProcessing878.1quote...............................................878.2list................................................888.3carandcdr............................................888.4cons................................................898.4.1DotNotation.......................................898.5Implementation..........................................918.5.1ChangestoExpressions.................................928.5.2ChangestoPrimitivesandSpecialForms.......................968.5.3ChangestoClosures...................................998.5.4ChangestotheEnvironment..............................998.5.5ChangestotheReader..................................1018.6Summary.............................................1038.7Tests................................................1048.8Listings..............................................1058.8.1t/PScmList.t......................................1058.8.2t/PScmDot.t.......................................1069Macros1079.1macro...............................................1089.2EvaluatingMacros........................................1089.2.1Tryingitout.......................................1099.2.2AnImprovement.....................................1129.2.3OneLastAddition....................................1179.3Summary.............................................1189.4Tests................................................1199.5Listings..............................................1209.5.1t/PScmMacro.t.....................................1209.5.2t/PScmEval.t......................................12210SideE ects12310.1TheBeautyofFunctionalLanguages..............................12310.2VariableAssignment.......................................12410.3Sequences.............................................12610.4Summary.............................................12710.5Tests................................................12810.6Listings..............................................12910.6.1t/PScmSideEffects.t.................................12911define13111.1EnvironmentChanges......................................13111.2ThedefineSpecialForm....................................13111.3PersistantEnvironments.....................................13211.4Tests................................................13311.5Listings..............................................13411.5.1t/PScmDefine.t.....................................134 viCONTENTS12ClassesandObjects13512.1Featuresofthisimplementation.................................13512.1.1Inheritance........................................13612.1.2ClassVariablesandMethods..............................13812.1.3superCalls........................................13812.1.4FeatureSummary....................................13912.2Implementation..........................................13912.2.1ClassCreationwithmake-class............................14012.2.2ObjectCreation.....................................14112.2.3initMethodInvocation.................................14512.2.4GeneralMethodInvocation...............................14812.2.5superMethodInvocation................................14812.2.6Wiringitup.......................................14912.3SummaryandVariations.....................................15012.4Tests................................................15112.5Listings..............................................15312.5.1PScm/Class.pm......................................15312.5.2t/PScmOO.t.......................................15513Continuations15913.1TailRecursionandTailCallOptimization...........................16113.2ContinuationPassingStyle...................................16413.3ExamplecpsTransformations..................................17213.4TheTrampoline..........................................17613.5Usingcps.............................................17913.6Implementation..........................................18613.6.1OurTrampolineImplementation............................18613.6.2cpsletandlambda...................................18813.6.3cpsletrec........................................20213.6.4cpslet*.........................................20413.6.5cpsListProcessing...................................20613.6.6cpsmacroandunquote.................................20813.6.7cpsSequencesandAssignment.............................21113.6.8cpsdefine........................................21213.6.9cpsoop..........................................21313.7cpsWithoutClosures......................................21813.8cpsFun..............................................22013.8.1AnerrorHandler....................................22013.8.2yield...........................................22213.9Summary.............................................22413.10Listings..............................................22513.10.1PScm/Continuation.pm.................................22513.10.2t/PScmCallCC.t.....................................22613.10.3t/CPSError.t......................................22713.10.4t/CPSYield.t......................................228 CONTENTSvii14Threads23114.1Variations.............................................23414.2Tests................................................23414.3Listings..............................................23514.3.1t/CPSSpawn.t......................................23515BetterErrorHandling23715.1TheBuiltinerrorPrimitive..................................23715.2UsingtheerrorBuiltinforInternalErrors..........................23915.3Tests................................................24515.4Listings..............................................24615.4.1t/CPSBuiltInError.t.................................24616ChronologicalBacktracking24916.1Introducingamb..........................................24916.2ExamplesofambinAction....................................25216.2.1TheLiars"Puzzle....................................25216.2.2BarrelsofFun......................................25716.2.3PythagoreanTriples...................................25816.2.4ParsingNaturalLanguage................................26016.3Implementingamb........................................26616.3.1ChangestoContinuations................................26616.3.2MechanicalTransformationsoftheInterpreter....................26716.3.3RemainingContinuationChanges............................27016.3.4ambItself.........................................27216.3.5Changestodefine....................................27316.3.6Changestoset!.....................................27416.3.7Changestorepl()....................................27516.3.8AdditionalChanges...................................27816.4SupportforTestingamb.....................................27916.4.1andandor........................................27916.4.2NumericInequalityTests................................28116.4.3eq?............................................28216.4.4Wiringitup.......................................28316.5SummaryandDirections.....................................28516.5.1AnAlternativeImplementation.............................28516.6Tests................................................28616.7Listings..............................................28716.7.1t/PScmCompare.t....................................28716.7.2t/AMBrepl.t.......................................28916.7.3t/AMBamb.t.......................................29017Uni cationandLogicProgramming29917.1LogicProgrammingExamples..................................29917.1.1MaryandJohn......................................29917.1.2J.S.BachandFamily..................................30117.1.3AppendingLists.....................................302 viiiCONTENTS17.1.4FactorialAgain......................................30417.1.5SymbolicDi erentiation.................................30417.2PatternMatching.........................................30617.2.1APerlPatternMatcher.................................30717.3Uni cation............................................31117.3.1APerlUni er.......................................31217.3.2ImplementingunifyinPScheme............................31617.4LogicProgramming.......................................32817.5MoreLogicProgrammingExamples..............................33317.5.1Parsing(again)......................................33317.5.2SimplifyingAlgebraicExpressions...........................33717.6Summary,ShortcomingsandShort-cuts............................33817.6.1FunctorsandArity....................................33817.6.2TheCut..........................................33917.6.3BacktrackingEciency.................................34017.7Tests................................................34017.8Listings..............................................34117.8.1t/PScmUnify.t.....................................34117.8.2t/AMBUnify.t......................................34218Summary351Bibliography353Index354 ListofFigures3.1ExamplePSchemeStructurefor(foo("bar"10)baz)...................173.2PScm::Exprclasses.......................................223.3PScm::Exprnew()andvalue()methods..........................233.4PScm::ExprEval()Methods.................................253.5PScm::Exprasstring()methods..............................303.6PScm::Exprmethods......................................324.1StacksDestroyOldEnvironmentFrames............................504.2LinkedListsDon'tDestroyOldEnvironmentFrames.....................514.3Environmentduringevaluationofexamplelet".......................525.1Functionsextendanenvironmentjustlikeletdoes......................605.2Closuresextendthelexicalenvironment............................605.3letbindsnto2.........................................635.4ClosureCapturestheLocalEnvironment...........................655.5letbindstimes2anda.....................................655.6ClosureExtendsCapturedEnv.................................676.1Whyrecursiondoesn'twork...................................746.2Recursiveenvironments.....................................747.1Nestedenvironments.......................................828.1ConsCellRepresentationofanestedlist(foo("bar"10)baz)..............908.2Thepair(a.b)........................................908.3Thestructure(ab.c)....................................919.1Examplemyletinternalstructure................................11012.1Notionalobjectstructure....................................14212.2Realobjectstructure.......................................14312.3Exampleclassesandobjects...................................15213.1Producer/consumerpair.....................................16013.2Tailcallwithouttco.......................................16213.3Tailcallwithtco........................................16313.4Control owforthesimplescript................................18113.5Control owwithcontinuations.................................184ix xLISTOFFIGURES13.6Continuationsarejust(anonymous)subroutines.......................18413.7Continuationsreallyarejustsubroutines............................18416.1Control owduring(list(amb12)(amb'a'b))....................25116.2OnepossibleparseofTime ieslikeanarrow"........................26116.3AparsetreeforFruit ieslikeabannanna".........................26216.4defineinstallsafailurecontinuationlast...........................27417.1Bach'sFamilyTree........................................30117.2Uni cationof['f',['g','A'],'A']with['f','B','abc'].............31217.3Amorecomplexuni cationexample..............................313 PartIImplementingaScheme-LikeInterpreter1 Chapter1IntroductionWhywouldanyonewanttowriteaSchemeinterpreterinPerl?{FelixLeeMadness.{LarryWallBytheendofthisbookyoushouldhaveathoroughunderstandingoftheinnerworkingsofaprogramminglanguageinterpreter.Thesourcecodeispresentedinfull,andseveraliterationsaddmorefeaturesuntilitcouldbeconsideredprettycomplete.Theinterpreteriswrittentobeaseasytounderstandaspossible;ithasnocleveroptimizationsthatmightobscurethebasicideas,andthecodeandtheideaswillbedescribedtothebestofmyabilitywithoutanyunexplainedtechnicaljargon.ItishoweverassumedthatyouhaveagoodworkingknowledgeofPerl(Perl5),includingitsobject-orientedfeatures.The nalimplementationwilldemonstrate:primitivearithmeticoperations;conditionalevaluation;localvariables;functionsandclosure;recursion;listprocessing;quote|preventingevaluation;asimplemacrofacility;variableassignmentandside-e ects;procedures(asopposedtofunctions)andsequences;objectsandclasses;continuations;3 4CHAPTER1.INTRODUCTIONthreads;exceptions;non-determinismandchronologicalbacktracking;logicprogramming.Havingsaidthat,timeandspaceisnotwasted eshingtheinterpreteroutwithnumerouscut'n'pastesysteminterfaces,i/oorevenmuchbasicarithmetic(the nalimplementationhasonlymultiplication,additionandsubtraction|enoughforthetestsandexamplestowork,)butbythenitshouldbeatrivialmatterforanyonetoaddthosethemselvesiftheyfeelsoinclined.Anotherpointworthmentioningupfrontisthatnoclaimsaremadethatthisisinanywayaproduction-quality,orevenanecientimplementation.Itisjustmeanttobeeasytounderstand.Puttingitanotherway,ifyou'vecomeherelookingforano -theshelfscheme-likeinterpreterthatyoucanuse,you'vecometothewrongplace:therearemanyandbetterfreelyavailableimplementationsonthenet.Ontheotherhandifyou'remoreinterestedinhowsuchinterpretersmightwork,I'dliketothinkthatyoumight ndwhatyou'relookingforhere.1.1WhyPerl?MymotivationforwritingthisbookisthatIhavealwaysbeenfascinatedbythefactthatprogramminglanguagesarejustprograms,butIfounditverydicultinthepasttoworkoutwhatwasactuallygoingoninthehandfulofpublicdomain"1implementationsofprogramminglanguagesavailableatthetime.Thetemptationalwaysseemedtobetherefortheauthorstoaddallsortsofbellsandwhistlestotheirpetprojectuntilthecoreideasbecameobscuredandobfuscated.Thefactthattheywereinvariablyimplementedinthelow-levelsystemlanguageCdidn'thelpmatters.ItwasonlywhenIfoundoutthattheeasiestimplementationsofSchemetounderstandwerewritteninSchemeitselfthatImadeanyrealprogresswiththatparticularlanguage.Howeverimplementinganinterpreterintermsofitself(so-calledmeta-circularevaluation")easilyleadstoconfusion,anditstruckmethatPerl,withitsveryhigh-levelconstructsandhighsignaltonoiseratioistheperfectvehicletodemonstratetheprogramminglanguageconceptsthatI'vesopainfullygleanedthroughtime,withoutanyincestuousmeta-circularissuestodealwith.Anotherreasonformywantingtowriteaboutprogramminglanguagesisthewonderfulgestaltin-herantintheirconstruction:howsuchanapparentlysmallamountofcodecouldachievesomuch.Thisisofcourseduetothedeeplyrecursivenatureoftheirdesign,andthissmallimplementationinPerlattemptstobeasconciseandrecursiveaspossible.1.2WhyScheme?SchemeisoneoftheyoungermembersoftheLispfamilyofprogramminglanguages.LISPstandsforLIStProcessing"2,andthisisanappropriatenamesincethefundamentaldatatypeintheselanguagesisthelist.ThemainreasonforchoosingSchemetodemonstratetheinternalsofaninterpreteristhatSchemeisaverysimplelanguage,andatthesametimeanastonishinglypowerfulone.Ananalogymightbe1yes,I'molderthanyourgrandfather2OrLotsofIrritatingSingleParentheses". 1.3.REFERENCES5thatifCisthechess"ofprogramminglanguages,thenSchemeismorelikego".TheocialstandardforScheme,theRevised(6)ReportontheAlgorithmicLanguageScheme"orR6RS[12]asitisknown,hasthistosay:Programminglanguagesshouldbedesignednotbypilingfeatureontopoffeature,butbyremovingtheweaknessesandrestrictionsthatmakeadditionalfeaturesappearnecessary.Whetherornotoneagreeswiththat,andit'shardtoargue,itstronglysuggeststhatsuchaconsistentlanguagemightbeprettystraightforwardtoimplement.AnotherinterestingfeatureofSchemeandtheLispfamilyoflanguagesisthatthelistdatathattheyworkwithisalsousedtoconstructtheinternalrepresentationoftheprogramsthemselves.ThereforeSchemeprogramscandirectlymanipulatetheirownsyntaxtrees.Thismakesthede nitionofmacros(syntacticextensions)particularlyeasyfromwithinthelanguageitself,withoutrecoursetoanyseparatepreprocessingstage.Finally,anothergoodreasonforchoosingSchemeisthatitisextremelyeasytoparse,asweshallsee.1.3ReferencesIprovideandrefertoaselectbibliography.Almostalloftheconceptsinthisbookarewellknowninacademiccirclesanditwouldbedisingenuousofmetotrytopassthemo asmyown.Thebibliographyshouldprovideyouwithasmallcollectionofusefuljumpingo pointsshouldyouwishtoinvestigateanyofthesetopicsfurther.1.4TypographyAllofthesourcecodelistingsandextractsfromthesourceareshowninfixed-widthtypewithlinenumbers,andarepulleddirectlyfromthecodeoftheworkinginterpreter.Furthermore,whendisplayinganewerversionofanindividualmethodorpackage,thedi erencesbethweenthatversionandthepreviousonearecalculatedautomaticallyanddisplayedinbold.PackagenamesaredisplayedLike::Thisandmethodslikethis().Schemecodelooks(likethis).Otherin-linecode,suchasSchemeandoccasionallyPerlexamplesareunfortunatelynotsorigorouslyconstrained.ThepossibilityexiststhateventhoughIhavemanuallytestedallofthoseexamplestherecouldbeanerrorortwointhere,forwhichIcanonlyapologise.1.5ANoteontheInterpreterVersionsYouwon't ndthisinterpreteroncpan.Thereasonforthisisthateachversionoftheinterpreter,whilebuildingonthefeaturesofpreviousversions,isathingofitselfandexiststodemonstratespeci cpedagogicalpoints.Icouldn'tpublishonlythe nalversiontocpansinceitis tfornopurposeotherthanthisbookandtoocomplexforcasualperousalwithouthaving rstdigestedtheearlierversions.Howevereachversionisavailableonlineforyoutodownloadandplaywith,theyarelinkedtofromthetextattheendofeachchapter. 6CHAPTER1.INTRODUCTION Chapter2AnIntroductiontoPSchemeInsubsequentdiscussions,PSchememeansthisparticularimplementationofaScheme-likeinterpreter(Perl-Scheme).Theimplementationlacksanumberofthefeaturesofacompleteimplementation,anddi ersfromtheSchemestandardatanumberofpoints.HoweveritcouldbearguedthatitiscloseenoughtocallitselfScheme-like",it'scertainlyclosertoareferenceimplementationofSchemethanitistoanyotherlanguageintheLispfamily.2.1PSchemeSyntaxPSchemehasaverysimplesyntax.APSchemeexpressioniseitheranumber,astring,asymbol,oralistofexpressionsseparatedbyspacesandenclosedinroundbrackets(whereanexpressioniseitheranumber,astring,asymbol,oralistof...).Wecanwritethisrecursivede nitioninaspecialpurposenotationfordescribingprogramminglanguagegrammarscalledBackus-NaurFormat(bnf)asfollows:hexpressioni::=hnumberi|hstringi|hsymboli|'('hexpressioni...')'Read::="asisa",and|"asor".APSchemenumberisasequenceofdigits,optionallyprecededbya+"ora-".PSchemedoesnotsupport oatingpointorothermorecomplexnumbertypes.1APSchemestringisanysequenceofcharactersenclosedbydoublequotes.Withinastring,adoublequotemaybeescapedwithabackslash.PSchemehasarathermorerelaxedideaofwhatconstitutesasymbolthanmostlanguages,essentiallyit'sanythingthatisn'tanopenorclosebraceanddoesn'tlooklikeanumberorastring,uptothenextwhitespacecharacterorroundbracket.Sox",this-is-a-symbol",<",&foo",and$%*@!fg"areallsymbols.PSchemereadsanexpression,thenevaluatesit,thenprintstheresult.Therulesforevaluationarealsoverysimple:Theresultofevaluatinganumberorastringisjustthatnumberorstring;1AfullSchemeimplementationsupportsalargerangeofnumerictypes,fromarbitrarilylargeintegersthrough oatingpoint,precision-preservingfractions,andcomplexnumbers.7 8CHAPTER2.ANINTRODUCTIONTOPSCHEMETheresultofevaluatingasymbolisthevaluethatthatsymbolcurrentlyhas,oranerrorifthesymbolhasnovalue;Theresultofevaluatingalistofexpressionsistheresultofevaluatingeachexpressioninturn,thenapplyingthe rstevaluatedexpression(whichshouldbeafunction)totheotherevaluatedexpressions.2.2SimpleExpressionsPSchemeisaninteractivelanguage,itpresentsaprompt,andtheusertypesinexpressions.Theinterpreterevaluatesthoseexpressionsthenprintstheresults:>22The>"isthePSchemeprompt.Wegavetheinterpretera2,anditrepliedwith2,because2is2is2.Let'strysomethingabitmoreadventurous:>xError:nobindingforxinPScm::EnvWeaskedforthevalueofasymbol,x,andbecausetheinterpreterdoesn'tknowwhatxis,wegetanerror.Here'ssomethingthatdoeswork:>(*22)4Nowthatmightlookstrangeat rst,butrememberthe rstsubexpressioninalistshouldevaluatetoafunction.Themultiplicationsymbol*"doesindeedevaluatetotheinternalprimitivede nitionofhowtomultiply;wetoldPSchemetomultiply2by2,anditreplied4.Indetailwhatithasdoneis:1.evaluatethesymbol*togetitsvalue:themultiplicationfunction.2.evaluatethe rst2toget2;3.evaluatethesecond2toget2;4.appliedthemultiplicationfunctiontoarguments2and2;5.printedtheresult:4.OneimportantthingtonotehereisthatPSchememakesnodistinctionbetweenfunctionsandoperators,theoperationalwayscomes rst.Thishassomeadvantages;becausetheoperationalwayscomes rst,itcanoftenapplytovariablenumbersofarguments:>(*2222)16Amoresyntax-richlanguagewouldrequiresomethinglike2*2*2*2togetthesameresult.Nowforsomethingjustalittlemorecomplex: 2.3.CONDITIONALS9>(*(-83)2)10Herewetoldtheinterpretertosubtract3from8,thenmultiplytheresultby2.Itdiditby:1.Evaluatingthesymbol*togetthemultiplicationfunction;2.Evaluatingtheexpression(-83)toget5,whichitdidby:(a)Evaluatingthesymbol-"togetthesubtractionfunction;(b)Evaluating8toget8;(c)Evaluating3toget3;(d)Applyingthesubtractionfunctiontoarguments8and3.3.Evaluating2toget2;4.Applyingthemultiplicationfunctiontoarguments5and2;5.Printingtheresult:10.Hopefullyitisobviousthattheinterpreterisfollowingaverysimplesetofruleshere,albeitrecursively.Thisincidentallydemonstratesanotherbigsimpli cationthatPSchememakes:itisimpossiblefortheretobeanyambiguityaboutoperatorprecedence,becausethelanguageforcestheprecedencetobeexplicit.InfactthereisnonotionofoperatorprecedenceinPScheme.Inamoresyntax-richlanguage,toachievetheaboveresultonewouldhavetowrite(8-3)*2becausetheequallylegal8-3*2wouldbemisinterpreted(alovelyexpression)as8-(3*2).2.3ConditionalsThekeywordifintroducesaconditionalstatement.Thegeneralformofanifexpressionis:(ifhtestihtrue-resultihfalse-resulti)Thisissimpleenough,ifexpects(inthisimplementationatleast)threearguments:atest,aconsequent(trueresult)andanalternative(falseresult).Forexample:>(if0>3>(-83))5Inthisexamplesincethetest,0,isfalse(again,inthisimplementation)thealternative(83=5)isreturned.Evenherewecanstarttoseesomeofthepowerofthelanguage: 10CHAPTER2.ANINTRODUCTIONTOPSCHEME>((if0-*)45)20Intheauthor'sopinionthisisabeautifulexampleofremovingtheweaknessesandrestrictionsthatmakeadditionalfeaturesappearnecessary";becausethelanguagetreatstheoperatorpositionjustlikeanyotherexpression,anyexpressionthatevaluatestoanoperationisvalidinthatposition.Furthermorebecauseprimitiveoperationsarerepresentedbysymbolsjustlikeanythingelse,theycanbetreatedjustlikeanyothervariable:theifwithafalse(0)testargumentselectsthevalueof*"toreturn,ratherthanthevalueof-".Soit'sthemultiplicationfunctionthatgetsappliedtothearguments4and5.Howeverthereisaslightcomplication,Considerthis:>(if0>(a-long-calculation)>(-83))5Wereifanormalfunction,thenormalrulesforevaluationwouldapply:evaluateallthecom-ponentsofthelist,thenapplytheiffunctiontotheevaluatedarguments.Thatwouldmean(a-long-calculation)and(-83)wouldbothgetevaluated,thenifwouldpicktheresult.Al-thoughthevalueofthewholeifexpressionisuna ected,provided(a-long-calculation)doesn'thaveanyside-e ects,westilldon'twanttohavethatcalculationexecutedunnecessarily.NowrememberitwassaidthatPSchemeevaluateseachcomponentofthelistinalistexpression?Wellthat'snotentirelythecase.Italwaysevaluatesthe rstcomponentofthelist,andiftheresultisasimplefunctionlikemultiplication,itthengoesontoevaluatetheotheritemsonthelistandpassestheresultstothefunctionjustashasalreadybeendescribed.Howeverifthe rstcomponentiswhatiscalledaspecialform,suchasthede nitionofif,PSchemepassestheun-evaluatedargumentstothespecialformandthatspecialformcandowhatitlikeswiththem.Inthecaseofif,ifevaluatesits rstargument(thetest)andiftheresultistrueitevaluatesandreturnsitssecondargument(theconsequent),otherwiseitevaluatesandreturnsitsthirdargument(thealternative).Wecandemonstratethatwithasimpleexample:>(if1>10>x)10Becausethetestresultwastrue,theifonlyevaluatedtheconsequentexpression,therewasnoerrorfromtheunde nedsymbolxinthealternative.2.4GlobalVariablesdefineisthewayweassociatevalueswithglobalvariablesinPScheme.Ithasthegeneralform:(definehsymbolihexpressioni)wherehsymboliisbeingboundtothevalueofhexpressioni.Forexample: 2.5.FUNCTIONS11>(definex5)x>x5Intheaboveexamplewede nedxtobe5.ThenwhenweaskedforthevalueofxPSchemereplied5.Noteagainthattheoperation(defineinthiscase)alwayscomes rst.Notealsothatdefinemustbeaspecialform,becausewedidn'tgetanerrorattemptingtoevaluatexduringthede nition.definedoeshoweverevaluateitssecondargumentso:>(defineab)Error:nobindingforbinPScm::Envcausesanimmediateerrorattemptingtoevaluatetheunde nedsymbolbbeforeassigningtheresulttoa.2.5Functionslambda,anotherspecialform,createsafunction.Thegeneralformofalambdaexpressionis:(lambda(hsymboli...)hexpressioni)The(hsymboli...)partisthenamesoftheargumentstothefunction,andthehexpressioniisthebodyofthefunction.Here'sanexample:>(definesquare>(lambda(x)(*xx)))squareNowthatmayalsolookabitstrangeat rst,butsimplyput,lambdacreatesananonymousfunction,andthatisseparatefromgivingthatfunctionanamewithdefine.Thefunctionbeingde nedinthisexampletakesoneargumentxanditsfunctionbodyis(*xx).Thefunctionbodywillexecutewhenthefunctionisinvoked.ThisismoreorlessequivalenttothisPerlsnippet:our$square=subfmy($x)=@_;$x*$x;g;Infact,Perl'sanonymoussubf...gsyntaxcanbeconsideredprettymuchsynonymouswithPScheme's(lambda...).Thebigdi erenceisthatinPSchemethat'stheonlywaytocreateafunction2.Havingcreatedasquarefunction,itcanbecalled:2ThereareexamplesofSchemecodethatshowthingslike:(define(squarex)(*xx))Thisformofdefine,wheretheexpressionbeingde nedisalist,isjustsyntacticsugarfortheunderlyingform.defineessentiallyre-writesitintothesimplerlambdastatementbeforeevaluatingit.Sincethede nitionheremimicstheintendedusageofthefunctionitiscertainlyalittlebiteasiertoread,butpersonallyI ndthatsinceIhavetouselambdainsomeexpressionsanyway,itmakessensetoalwaysuseit.Plusthesyntacticsugartendstoobscurewhatisreallygoingon.InanycasePSchemedoesnotsupportthisalternativesyntaxforfunctionde nition. 12CHAPTER2.ANINTRODUCTIONTOPSCHEME>(square4)16Althoughsquarewascreatedbyassignment,whenitisuseditissyntacticallyindistinguishablefromanybuilt-infunction.Anonymousfunctionscanalsobecalleddirectlywithoutgivingthemaname rst:>((lambda(x)(*xx))3)9Againthisismuchsimplerthanitmight rstappear.The rsttermofthelistexpression,thelambdaexpression,getsevaluatedresultinginafunctionwhichwillsquareit'sargument.Thatfunctionthenimmediatelygetsappliedto3resultingin9.Itispossibletodosomethingsimilarinperl,likethis:subfmy($x)=@_;$x*$xg->(3);butit'snotacommonidiom.Asanaside,youmaybewonderingwhattheeleventhletteroftheGreekalphabethastodowiththecreationofafunction.Thetermcomesfromabranchofmathemeticscalledthelambdacalculuswhichisconcernedwithdescribingandreasoningaboutthebehaviourofmathematicalfunctionsingeneral.Eventhoughthelambdacalculuswasdevisedbeforethecreationofthe rstcomputer,itturnsoutthatitprovidesasoundtheoreticalbasisfortheimplementationofprogramminglanguages,andLispwasthe rstprogramminglanguagetoexploitthatfact.Thereisagoodintroductiontothelambdacalculusin[10],andamoredetailedandrigoroustreatmentin[11].2.6LocalVariablesMovingon,howcanPSchemecreatelocalvariableslimited(lexically)toagivenscope?Thisisdonewiththeletspecialform.Thegeneralformofaletexpressionis:(let(hbindingi...)hexpressioni)wherehbindingiis:(hsymbolihexpressioni)lettakesalistofbindings(symbol-valuepairs)andabodytoexecutewiththosebindingsine ect.Forexample:>(let((a10)>(b(+1010)))>(+ab))30 2.6.LOCALVARIABLES13Thatcanbereadaloudasleta=10andb=10+10intheexpressiona+b".Symbolaisgiventhevalue10andsymbolbthevalue20whilethebodyisevaluated.Howeverifalaterexpressionwastoaskforthevalueofaorboutsideofthescope(thelastclosingbrace)ofthelet,therewouldbeanerror(assumingthereweren'tglobalbindingsofaandbine ect.)Thecarefulreaderwillhavenoticedthattheseweredescribedaslexicallyscopedvariables,andyes,anyfunctionsde nedinthescopeofthosevariablesareclosuresjustlikePerlclosuresandhaveaccesstothosevariableswhenexecutedevenifexecutedoutsideofthatscope.Forexample:>(definetimes2>(let((n2))>(lambda(x)(*nx))))times2>(times24)8Whenreadingthisit'susefultorememberthatdefinedoesevaluateitssecondargument.Thatmeansthatthisexpressionde nestimes2tobetheresultofevaluatingtheletexpression.Nowthatletexpressionbindsnto2,thenreturnstheresultofevaluatingthelambdaexpression(creatingafunction)withthatbindingine ect.Itisthatnewlycreatedfunctionthatgetsboundtothesymboltimes2.Whentimes2islaterused,forexamplein(times24),thebodyofthefunction(*nx)canstillsee"thevalueofnthatwassuppliedbythelet,eventhoughthefunctionisexecutedoutsideofthatscope.ThisissimilartothecommonPerltricktogetaprivatestaticvariable:fmy$n=2;subtimes2fmy($x)=@_;$n*$x;ggbuttobetruthfulit'sclosertothemoreobtuse:our$times2=dofmy$n=2;subfmy($x)=@_;$n*$x;gg;Andthat'sprettymuchallthatisneededfornow.Ofcoursethe nallanguagehasmanyotherinterestingfeatures,butthesewillbeintroducedinlatersectionsastheneedarises.Let'stakealookatour rstcutataninterpreter3.3IfyouwantmoreofanintroductiontoSchemeingeneral,youcoulddoworsethanlookat[6]. 14CHAPTER2.ANINTRODUCTIONTOPSCHEME Chapter3InterpreterVersion0.0.0Thispreliminaryversionoftheinterpretersupportsonlythreeoperations,namelymultiplication(*),sub-traction(-),andconditionalevaluation(if).Itdoeshoweverlaythegroundworkformoresophisticatedinterpreterslateron.Schemelispinterpreters,beinginteractive,arebasedaroundwhatiscalledareadevalprintloop": rstreadanexpression,thenevaluateit,thenprinttheresult,thenloop.Thislong-windedtermisoftenabbreviatedtorepl.Inorderfortherepltoevaluatetheexpression,theremustadditionallybeanenvironmentinwhichsymbolscanbegivenvaluesandinwhichvaluescanbelookedup.Allthismeansthattherearesixprinciplecomponentstosuchaninterpreter.AReaderthatconstructsinternalrepresentationsoftheexpressionstobeevaluated;AnEvaluatorthatactuallydeterminesthevalueoftheexpression,usingAStructurereturnedbytheReader,representingtheexpression(andincidentallyreturnedbytheEvaluator,representingtheresult);AnEnvironmentinwhichsymbolscanbeassociatedwithvaluesandthevaluesofsymbolscanbelookedup.ASetofPrimitiveOperationsboundtosymbolsintheinitialenvironment,whichimplementalloftheindividualbuiltincommands.APrintSystemwhichconvertstheresultofevaluationbacktotextanddisplaysittotheuser.Theimplementationwe'reabouttodiscusstakesafairlystrictOOapproach,witheachofthesecom-ponentsandprettymucheverythingelserepresentedbyclassesofobjects.AsaconsequenceofthistheEvaluatorandthePrintsystemaredistributedthroughouttheStructurecomponent.ThismeansthatforexampletoevaluateanexpressionyoucallitsEvalmethod,andtoprintaresultyoucallthePrintmethodontheresultobject.Thereisagooddealofscopeforpolymorphismwiththisapproach,sincedi erenttypesofobjectcanresponddi erentlytothesamemessage.3.1TheRead-Eval-PrintLoopThetop-levelread-eval-printloop(repl)forthePSchemeinterpreterisinthepackagePScminList-ing3.11.1onpage34.Allotherpackagesinheritfromthispackage,althoughthat'smainlyjustaconvenience.15 16CHAPTER3.INTERPRETERVERSION0.0.0Firstly,onLines31-35aglobalenvironment,$PScm::GlobalEnvisinitialisedtoanewPScm::Envobject.031our$GlobalEnv=newPScm::Env(032'*'=>newPScm::Primitive::Multiply(),033'-'=>newPScm::Primitive::Subtract(),034if=>newPScm::SpecialForm::If(),035);Thereareonlythreethingsinthatenvironment.Theyaretheobjectsthatwillperformtheprimitiveoperationsofmultiplication,subtractionandconditionalevaluation,andthey'reboundto*",-"andif"respectively.We'llseehowtheyworkpresently.ReadEvalPrint()onLines37-46isthecentralcontrolroutineofthewholeinterpreter.Ittakesaninput lehandleandanoutput lehandleasarguments.StartingonLine40itdefaultstheoutput lehandletostdout,thenonLine41itcreatesanewPScm::Readobjectontheinput lehandle,andonLines42-45itentersitsmainloop.ThelooprepeatedlycollectsanexpressionfromtheReader,thenevaluatestheexpressionbycallingitsEval()method,thenprintstheresultbycallingitsPrint()method:037subReadEvalPrintf038my($infh,$outfh)=@_;039040$outfh||=newFileHandle(">-");041my$reader=newPScm::Read($infh);042while(defined(my$expr=$reader->Read))f043my$result=$expr->Eval();044$result->Print($outfh);045g046gThebasisoftheprintsystemcanbeseeninthePrint()andasstring()methodsinPScm.pm,butwe'regoingtoleavediscussionoftheprintsystemuntillateron.Inthenextsectionwe'lllookatour rst,verysimple,implementationofanenvironment.3.2TheEnvironmentAllanenvironmenthastodoistoreturnthecurrentvalueforanargumentsymbol.Perlhashesareidealforthistask,andourimplementationusesthem.OurenvironmentisimplementedbyPScm::EnvinListing3.11.2onpage36.ItisnomorethananobjectwrapperaroundaPerlhash.Thenew()method(Lines7-11)createsanobjectwithasetofbindings(nametovaluemappings)thatwerepassedinasarguments:007subnewf008my($class,%bindings)=@_;009010blessfbindings=>f%bindingsg,g,$class;011g 3.3.THEREADER17TheLookUp()methodonLines13-22looksupasymbolinthebindings,die-ingifthesymboldoesnothaveabinding:013subLookUpf014my($self,$symbol)=@_;015016if(exists($self->fbindingsgf$symbol->valueg))f017return$self->fbindingsgf$symbol->valueg;018gelsef019die"nobindingfor@f[$symbol->value]g",020"in@f[ref($self)]g ";021g022gNotethatthe$symbolpassedinisanobject,andLookUp()mustcallthesymbol'svalue()methodtogetastringsuitableforahashkey.Thevalue()methodforasymboljustreturnsthenameofthesymbolasaperlstring.Becausethis rstversionoftheinterpreterhasnosupportforlocalvariables,thisclassdoesn'tprovideanymethodsforaddingvaluestotheenvironment.Thatwillcomelater.Andthat'sallthereistoourenvironmentclass.Let'smoveontolookattheReader.3.3TheReaderThejoboftheReaderistotakeastreamoftextandconvertitintoastructurethattheevaluatorcanmoreeasilyworkwith.Soforexamplewewanttotakeanexpressionsuchas(foo("bar"10)baz)andconvertitintoanequivalentstructuresuchasshowninFigure3.1.Figure3.1:ExamplePSchemeStructurefor(foo("bar"10)baz)ListSymbolListSymbolfoobazStringNumber"bar"10(foo("bar"10)baz) 18CHAPTER3.INTERPRETERVERSION0.0.0Inthis gure,showingtheresultofparsingthatexpression,thetop-levellistobjecthasthreecomponents.Readinglefttorightitcontainsthesymbolobjectfoo,anotherlistobjectandthesymbolobjectbaz.Thesub-listcontainsthestringobject"bar"andthenumberobject10.Itisapparentthatthatthestructureisadirectrepresentationofthetext,whereeachlistcorrespondstothecontentsofamatchingpairofbraces.ItshouldalsobeobviousthatthesestructuresarepracticallyidenticaltoPerllistreferences.Theschemelist(foo("bar"10)baz)correspondsdirectlytothenestedperllistref[$foo,["bar",10],$baz]1.Tosimplifythecreationofsuchastructurefromaninputstream,itisoftenconvenienttosplittheprocessintotwoparts:Atokeniserwhichrecognisesandreturnsthebasictokensofthetext(braces,symbols,numbersandstrings);Abuilderorparserwhichassemblesthosetokensintomeaningfulstructures(lists).ThatistheapproachtakenbytheReaderdescribedhere.ItwasmentionedearlierthatSchemewasextremelyeasytoparse,wellhere'stheproof.ThecodefortheReader,PScm::ReadinListing3.11.3onpage37isonly63lineslong.Aswiththerestoftheimplementation,itusesanOOstyle,sotheReaderisanobjectthatiscreatedwithanargumentFileHandleandbehavesasaniteratorreturningthenextparsedexpressionfromthestreamoneachcalltoRead().Thenew()method(Lines9-15)simplystashesitsinput lehandleargumentalongwithanemptystringrepresentingthecurrentline,andreturnstheminthenewobject.009subnewf010my($class,$fh)=@_;011blessf012FileHandle=>$fh,013Line=>'',014g,$class;015gApartfromnew()theonlyotherpubliclyavailablemethodisRead(),whichreturnsthenextcompleteexpression,asastructure,fromtheinput le.TheRead()methodcallstheprivatenexttoken()method(thetokeniser)foritstokens.SkippingovertheRead()methodfornow,nexttoken()onLines38-61simplychompsthenexttokeno theinputstreamandreturnsit.Itknowsenoughtoskipwhitespaceandblanklinesandtoreturnundefateof(Lines41-45).Ifthereisalinelefttotokenise,thenafewsimpleregexesaretriedinturntostripthenexttokenfromit.Assoonasatokenofaparticulartypeisrecognised,itisreturnedtothecaller.038sub_next_tokenf039my($self)=@_;040041while(!$self->fLineg)f042$self->fLineg=$self->fFileHandleg->getline();043returnundefunlessdefined$self->fLineg;1butlooksalotprettier. 3.3.THEREADER19044$self->fLineg=~s/^s+//s;045g046047for($self->fLineg)f048s/^(s*//&&returnPScm::Token::Open->new();049s/^)s*//&&returnPScm::Token::Close->new();050s/^([-+]?d+)s*//051&&returnPScm::Expr::Number->new($1);052s/^"((?:(?:\.)|([^"]))*)"s*//&&dof053my$string=$1;054$string=~s/\//g;055returnPScm::Expr::String->new($string);056g;057s/^([^s()]+)s*//058&&returnPScm::Expr::Symbol->new($1);059g060die"can'tparse:$self->fLineg";061gLines47-59dotheactualtokenisation.Thetokeniseronlyneedstodistinguishopenandclosebraces,numbers,stringsandsymbols,whereanythingthatdoesn'tlooklikeanopenorclosebrace,anumberorastringmustbeasymbol.nexttoken()returnsitsdatainobjects,whichincidentallyhappenstobeaveryconvenientwayoftaggingthetypeoftokenreturned.Theobjectsareoftwobasictypes:PScm::Token;andPScm::Expr.ThePScm::TokentypesPScm::Token::OpenandPScm::Token::Closerepresentanopenandaclosebracerespectively,andcontainnodata.ThethreePScm::Exprtypes,PScm::Expr::Number,PScm::Expr::StringandPScm::Expr::Symbolcontaintherelevantnumber,stringorsymbol.Nowthatweknowhownexttoken()works,wecangobackandtakealookatRead().TheRead()method(Lines17-36)hastoreturnthenextcompleteexpressionfromtheinputstream.Thatcouldbeasimplesymbol,stringornumber,oranarbitrarilynestedlist.Itstartsbycallingnexttoken()atLine20andreturningundefifnexttoken()returnedundef(signifyingendof le).017subReadf018my($self)=@_;019020my$token=$self->_next_token();021returnundefunlessdefined$token;022023return$tokenunless$token->is_open_token;024025my@res=();026027while(1)f028$token=$self->Read;029die"unexpectedEOF"030if!defined$token;031lastif$token->is_close_token; 20CHAPTER3.INTERPRETERVERSION0.0.0032push@res,$token;033g034035returnnewPScm::Expr::List(@res);036gThen,atLine23ifthetokenisanythingotherthananopenbrace(determinedbythecalltoisopen-token()2),Read()justreturnsit.Otherwise,thetokenjustreadisanopenbrace,soRead()initialisesanemptyresult@restoholdthelistitexpectstoaccumulatethenentersaloopcallingitselfrecursivelytocollectthe(possiblynested)componentsofthelist.Itisanerrorifitdetectseofwhilealistisunclosed,andifitdetectsaclosebrace(isclosetoken())itknowsitsworkisdoneanditreturnstheaccumulatedlistasanewPScm::Expr::Listobject.ThestructurereturnedbyRead()iscompletelycomposedofsubtypesofPScm::Expr,sincethePScm::Tokentypesdonotactuallygetenteredintothestructure.Let'sworkthroughtheparsingofthatsimpleexpression(foo("bar"10)baz).Inthefollowing,thesubscriptnumberkeepstrackofwhichparticularinvocationofRead()wearetalkingabout.Read1callsnexttoken()andgetsa(soitentersitsloop.Read1callsRead2fromwithinitsloop.{Read2callsnexttoken()andgetsafoo,soitreturnsit.Read1putsthefooatthestartofitslist:(foo.Read1callsRead3.{Read3callsnexttoken()andgetsa(soitentersitsloop.{Read3callsRead4.Read4callsnexttoken()andgetsa"bar"soitreturnsit.{Read3putsthe"bar"atthestartofitslist:("bar".{Read3callsRead5.Read5callsnexttoken()andgetsa10soitreturnsit.{Read3addsthe10toitsgrowinglist:("bar"10.{Read3callsRead6.Read6callsnexttoken()andgetsa)soitreturnsit.{Read3getsthe)soitknowsithasreachedtheendofitslistandreturnstheresult:("bar"10).Read1addsthe("bar"10)totheendofitsowngrowinglist:(foo("bar"10).2Itcouldhavejustsaidreturn$tokenunless$token->isa('PScm::Token::Open');butIalwaysthinkit'sabitrudetopeepintotheimplementationlikethat,muchbettertoaskitwhatitthinksitis,notforciblyextractitsdatatype. 3.3.THEREADER21Read1callsRead7.{Read7callsnexttoken()andgetsabazsoitreturnsit.Read1addsthebaztotheendofitsowngrowinglist:(foo("bar"10)baz.Read1callsRead8.{Read8callsnexttoken()andgetsa)soitreturnsit.Read1getsthe)soitknowsithasreachedtheendofitslistandreturnstheresult:(foo("bar"10)baz).SotheReaderdoesindeedreturnthestructureexpected.ThePScm::TokenandPScm::Exprclassesareintheireponymous les.ThePScm::TokenclassesinListing3.11.4onpage39arepurelyparse-related.Asmentionedearlier,theyarereturnedbythetokenisertoindicateopenandclosebraces.Thesetokensareusedtoguidetheparser,butitdoesnotactuallyincludethemintheresult.PScm::Token::OpenandPScm::Token::ClosebothinheritfromPScm::Token.PScm::Tokende nesdefaultimplementationsforisopentoken()andisclosetoken(),whichthetwoderivedclassesoverrideappropriately.PScm::Tokenisjust:001packagePScm::Token;002003usestrict;004usewarnings;005usebaseqw(PScm);006007subis_open_tokenf0g008subis_close_tokenf0gPScm::Token::Openoverridesisopentoken():011packagePScm::Token::Open;012013usebaseqw(PScm::Token);014015subis_open_tokenf1gandPScm::Token::Closeoverridesisclosetoken():018packagePScm::Token::Close;019020usebaseqw(PScm::Token);021022subis_close_tokenf1g0230241;PScm::Tokeninheritsastubnew()methodfromthePScmclassthatjustblessesanemptyhashwiththeargumentclass. 22CHAPTER3.INTERPRETERVERSION0.0.0AsforthePScm::ExprobjectsthatRead()accumulatesandreturns,asnotedRead()hasdonealloftheworkinconstructingatreeofthemforus,sotheyaremoreproperlydiscussedinthenextsectionwherewelookatexpressions.3.4PSchemeExpressionsThevariousPScm::Exprobjectsarede nedinPScm/Expr.pm.Theseobjectsrepresentthebasicdatatypesthatarevisibletotheuser:strings;numbers;symbols;andlists.TheyarethetypesreturnedbytheReaderandprintedbytheprintsystem.ItwouldbeprematuretogointoallthedetailsofthePScm::Exprpackagerightnow,butitisworthpointingoutafewsalientfeaturesaboutit.FirstlytheclassesarrangethemselvesinaCompositePattern[8,pp163{173]accordingtothehier-archyofPSchemetypesasinFigure3.2.Figure3.2:PScm::ExprclassesPScm::ExprPscm::Expr::ListPScm::Expr::AtomPScm::Expr::LiteralPScm::Expr::SymbolPScm::Expr::StringPScm::Expr::NumberThis gureisdrawnusingastandardsetofconventionsfordiagrammingtherelationshipsbetweenclassesinanOOdesign,calledtheUni edModellingLanguage",orUML.[5]Forthosewhodon'tknowUML,thetriangularshapemeansinheritsfrom"orisasubclassof",andtheblackarrowandcirclecomingfromthewhitediamondmeansaggregateszeroormoreof".Theclasseswithnamesinitalicsareabstract"classes.AsfarasPerlisconcerned,callingaclassabstract"justmeansthatwepromisenottocreateanyactualobjectinstancesofthatparticularclass.TheunterminateddottedlinesimplyimpliesthatwewillbederivingotherclassesfromPScm::Exprlateron.TherootofthehierarchyisPScm::Expr,representinganyandallexpressions.Thatdividesintolists(PScm::Expr::List)andatoms(PScm::Expr::Atom). 3.4.PSCHEMEEXPRESSIONS23Listsarecomposedofexpressions(theaggregationrelationship.)Atomsrepresentanydatatypethatcannotbetriviallytakenapart,anythingthat'snotalistinotherwords.Atomsaresubclassedintoliterals(PScm::Expr::Literal)andsymbols(PScm::Expr::Symbol),andliteralsaresubclassedintostrings(PScm::Expr::String)andnumbers(PScm::Expr::Number).We'llseealotofthisdiagraminvariousguisesasweprogress.Here'sthesamediagram,inFigure3.3withthelocationofthenew()andvalue()methodsadded.Figure3.3:PScm::Exprnew()andvalue()methodsPScm::ExprvaluePscm::Expr::ListPScm::Expr::AtomnewnewvaluevaluePScm::Expr::LiteralPScm::Expr::SymbolPScm::Expr::StringPScm::Expr::NumbernewAsyoucansee,therearethreenew()methodsintheclassstructure.ThePScm::Expr::Atomabstractclassistheparentclassforstringsandnumbers(viaPScm::Expr::Literal)andforsymbols.Sinceallofthesetypesaresimplescalars,thenew()methodinPScm::Expr::Atomdoesformostofthem:itblessesareferencetothescalarintotheappropriateclass.023subnewf024my($class,$value)=@_;025bless$value,$class;026gHoweverthePScm::Expr::Numberpackagesuppliesitsownnew()method,becauseweavailourselvesofthecoreMath::BigIntpackageforourintegers.Whileitisnicetohavearbitrarysizedintegersbydefault,themainreasonfordoingthisistoavoidtheembarrassmentofPerl'sautomatictypeconversionto oatingpointonintegerover owwhenimplementingalanguagethatisonlysupposedtosupportintegerarithmetic. 24CHAPTER3.INTERPRETERVERSION0.0.0082packagePScm::Expr::Number;083usebaseqw(PScm::Expr::Literal);084085useMath::BigInt;086087subnewf088my($class,$value)=@_;089$value=newMath::BigInt($value)unlessref($value);090$class->SUPER::new($value);091gThePScm::Expr::Listclasshastheothernew()methodthatsimplybundlesupitsargumentPerllistinanewobject:036subnewf037my($class,@list)=@_;038039$class=ref($class)||$class;040bless[@list],$class;041gAllthreeofthesenew()methodshavealreadybeenseeninactionintheReader.Alongsidemostofthenew()methodsisavalue()methodthatdoestheexactreverseofnew()andretrievestheunderlyingvaluefromtheobject.Inthecaseofatoms,itdereferencesthescalarvalue:028subvaluef$f$_[0]ggandinthecaseoflists,itdereferencesthelist:043subvaluef@f$_[0]ggEventhoughPScm::Expr::Numberhasitsownnew()method,wedon'tneedaseparatevalue()methodfornumbers,weneverneedtoretrievetheactualperlnumberfromtheMath::BigIntobjectsowejustinheritvalue()fromPScm::Expr::Atom.Wedohoweverprovideadefaultvalue()methodinPScm::Expr.Thisdefaultmethodjustreturns$self.017subvaluef$_[0]gThisissolelyforthebene tofthoseas-yetundescribedadditionalPScm::Exprsubclasses,whichwillallstandfortheirownvalues.We'veseenthatthevariousPSchemeexpressiontypes(lists,numbers,stringsandsymbols)arrangethemselvesnaturallyintoahierachyoftypesandalsoformarecogniseddesignpatterncalledCompos-ite".Nextwe'regoingtolookathowthoseexpressionsareevaluated. 3.5.EVALUATION25Figure3.4:PScm::ExprEval()MethodsPScm::ExprEvalPscm::Expr::ListPScm::Expr::AtomEvalPScm::Expr::LiteralPScm::Expr::SymbolEvalPScm::Expr::StringPScm::Expr::Number3.5EvaluationToevaluateaPScm::Expr,asmentionedearlier,thetoplevelReadEvalPrint()loopjustcallstheexpression'sEval()method.TheEval()methodsofPScm::ExprarelocatedinthreeofitssubclassesasshowninFigure3.4.The gureshowsthatthereisaseparateEval()methodforlistsandforsymbols,andadefaultmethodforallotherPScm::Expr.3.5.1EvaluationofLiteralsLet'slook rstatthedefaultEval()methodinPScm::Exprwhichcurrentlyappliestoliterals.PScm::Expr::StringandPScm::Expr::NumbersharethisdefaultEval()method,whichjustreturns$self:012subEvalf013my($self)=@_;014return$self;015gThismeansthatnumbersandstringsevaluatetothemselves,astheyshould,andifweweretoaddothertypesofexpressionlateron,theytoowouldbydefaultevaluatetothemselves.3.5.2EvaluationofSymbolsEvaluationofasymbolisonlyslightlymorecomplex.TheEval()methodinPScm::Expr::Symbollooksupitsvalueintheglobalenvironment$PScm::GlobalEnv: 26CHAPTER3.INTERPRETERVERSION0.0.0072subEvalf073my($self)=@_;074return$PScm::GlobalEnv->LookUp($self);075gRememberthatLookUp()fromPScm::Envexpectsasymbolobjectasargumentandcallsitsvalue()methodtogetastringthatitcanthenusetoretrievetheactualvaluefromthehashrepresentingtheenvironment.3.5.3EvaluationofListsBeforeshowinghowPScm::Expr::Listobjectsareevaluated,weneedtoconsideracoupleofsupportmethodsforlists:first()andrest().Thefirst()methodofPScm::Expr::Listjustreturnsthe rstcomponentofthelist:045subfirstf$_[0][0]gTherest()methodofPScm::Expr::Listreturnsallbutthe rstcomponentofthelistasanewPScm::Expr::Listobject:047subrestf048my($self)=@_;049050my@value=$self->value;051shift@value;052return$self->new(@value);053gNowwecanlookattheevaluationoflistexpressions.Here'sPScm::Expr::List::Eval():062subEvalf063my($self)=@_;064my$op=$self->first()->Eval();065return$op->Apply($self->rest);066gIt'ssurprisinglysimple.aPScm::Expr::Listjustevaluatesits rstelement(Line64).ThatshouldreturnoneofPScm::Primitive::Multiply,PScm::Primitive::SubtractorPScm::SpecialForm::If,whichgetsassignedto$op.Ofcoursebecausewe'renotdoinganyerrorchecking,first()couldreturnanything,sowe'reassumingvalidinput.BecausePScm::Expr::List'sEval()doesnotknoworcarewhethertheoperation$opitderivedonLine64isasimpleprimitiveoraspecialform,onLine65itpassestherestofitself(thelistofarguments)unevaluatedtothatoperationsApply()methodwhichappliesitselftothosearguments.Eachindividualoperation'sApply()methodwilldecidewhetherornottoevaluateitsarguments,andwhattodowiththemafterwards3.3Lisppuristsmightraiseaneyebrowatthispoint,becauseEval()issupposedtoknowwhatkindofformitisevaluatinganddecidewhetherornottoevaluatethearguments.Butthisisanobject-orientedapplication,anditmakesmuchmoresensetoleavethatdecisiontotheobjectsthatneedtoknow. 3.6.PRIMITIVEOPERATIONS27Sowe'veseenhowPScm::Exprobjectsevaluatethemselves.Inparticularwe'veseenhowalistevaluatesitselfbyevaluatingits rstcomponenttogetaprimitiveoperationorspecialform,thencallingthatobject'sApply()methodwiththerestofthelist,unevaluated,asargument.Nextwe'regoingtolookatoneofthoseApply()methods,thePScm::PrimitiveApply()method.3.6PrimitiveOperationsTheprimitivebuilt-infunctionsallliveinPScm/Primitive.pm,showninListing3.11.5onpage40.Thisclassholdsallofthecodeforsimplefunctionsthatcanbepassedalreadyevaluatedarguments.YoucanseethatitinfactinheritsfromPScm::ExprratherthandirectlyfromPScm,whichexplainsthedottedlineinthevariousPScm::Expr gures.ThisbasePScm::PrimitiveclassprovidestheApply()methodforallsimplefunctions:007subApplyf008my($self,$form)=@_;009010my@unevaluated_args=$form->value;011my@evaluated_args=mapf$_->Eval()g@unevaluated_args;012return$self->_apply(@evaluated_args);013gOnLine10itextractstheargumentstotheoperationfromthe$formbycallingthe$form'svalue()method.$formisaPScm::Expr::Listandwe'vealreadyseenthatthevalue()methodforalistobjectdereferencesandreturnstheunderlyinglist.Then,onLine11,Apply()evaluateseachargumentbymappingacalltoeachone'sEval()method.Finally,onLine12,itpassestheresultinglistofevaluatedargumentstoaprivateapply()methodandreturnstheresult.apply()isimplementeddi erentlybyeachprimitiveoperation.Soeachprimitiveoperation|eachsubclassofPScm::Primitive|onlyneedsanapply()methodwhichwillbecalledwithalistofalreadyevaluatedarguments.Theapply()inPScm::Primitive::Multiplyisverystraightforward.Itsimplymultipliesitsar-gumentstogetherandreturnstheresultasanewPScm::Expr::Number.Notethat,somewhatac-cidentally,ifonlygivenoneargumentitwillsimplyreturnit,andifgivennoargumentsitwillreturn1.028sub_applyf029my($self,@args)=@_;030031my$result=PScm::Expr::Number->new(1)->value();032033while(@args)f034my$arg=shift@args;035$self->_check_type($arg,'PScm::Expr::Number');036$result*=$arg->value;037g038039returnnewPScm::Expr::Number($result);040g 28CHAPTER3.INTERPRETERVERSION0.0.0OnLine31theratherconvolutedtricktogetaninitialvaluewillworkwhetherornottheunderlyingimplementationofPScm::Expr::NumberusesMath::BigIntornot.Thechecktype()methodinthebaseclassjustsavesussometyping,sincewearecheckingthetypeofargumenttotheprimitive:015sub_check_typef016my($self,$thing,$type)=@_;017018die"wrongtypeargument(",ref($thing),")to",ref($self),019" "020unless$thing->isa($type);021gPScm::Primitive::Subtract'sapply()methodismorecomplicatedonlybecauseitdistinguishesbe-tweenunarynegation(-x)andsubtraction.Ifitgetsonlyoneargumentitreturnsitsnegation,otherwiseitsubtractssubsequentargumentsfromthe rstone.Itwillreturn0ifcalledwithnoargu-ments.047sub_applyf048my($self,@args)=@_;049050unshift@args,PScm::Expr::Number->new(0)if@args<2;051052my$arg=shift@args;053$self->_check_type($arg,'PScm::Expr::Number');054055my$result=$arg->value;056057while(@args)f058$arg=shift@args;059$self->_check_type($arg,'PScm::Expr::Number');060$result-=$arg->value;061g062063returnnewPScm::Expr::Number($result);064gThat'salltheprimitiveoperationswesupport.Thereareawholehostofothersthatcouldtriviallybeaddedhereanditmightbeentertainingtoaddthem,butallthereallyinterestingstu ishappeningoverinthespecialforms,discussednext.3.7SpecialFormsAllthecodeforspecialformsisinPScm/SpecialForm.pminListing3.11.6onpage42.LikePScm::PrimitiveitdescendsfromPScm::Expr.Atthemomentthereisonlyonespecialform,if,sothelistingisshort.Itwillgetlongerinsubsequentversionsthough. 3.8.OUTPUT29Forspecialforms,theApply()methodisintheindividualoperation'sclass.OnLine15PScm::SpecialForm::If'sApply()methodextractsthecondition,theexpressiontoevaluateiftheconditionistrue,andtheexpressiontoevaluateiftheconditionisfalse,fromtheargument$form.ThenonLine17itevaluatesthecondition,andcallstheresult'sisTrue()methodtodeterminewhichbranchtoevaluate:012subApplyf013my($self,$form)=@_;014015my($condition,$true_branch,$false_branch)=$form->value;016017if($condition->Eval()->isTrue)f018return$true_branch->Eval();019gelsef020return$false_branch->Eval();021g022gIftheconditionistrue,PScm::SpecialForm::If::Apply()evaluatesandreturnsthetruebranch(Line18),otherwiseitevaluatesandreturnsthefalsebranch(Line20).ThedecisionofwhatistrueorfalseisdelegatedtoanisTrue()method.TheoneandonlyisTrue()methodisde nedinPScm/Expr.pmrightatthetopofthedatatypehierarchy,inthePScm::Exprclassas:007subisTruef008my($self)=@_;009scalar($self->value);010gRememberingthatvalue()justdereferencestheunderlyinglistorscalar,isTrue()thenprettymuchagreeswithPerl'sideaoftruth,namelythatzero,theemptystring,andtheemptylistarefalse,everythingelseistrue4.Thatreallyisallthereistoevaluation.Nextwe'regoingtotakealookattheprintsystem.3.8OutputAfterEval()returnstheresulttotherepl,ReadEvalPrint()callstheresult'sPrint()methodwiththeoutputhandleasargument.Thatmethodisde nedinPScm.pm048subPrintf049my($self,$outfh)=@_;050print$outfh$self->as_string," ";051gAllitdoesisprintthestringrepresentationoftheobjectobtainedbycallingitsasstring()method.Afallbackasstring()methodisprovidedinthisclassatLine53.4Thisdi ersfromatrueSchemeimplementationwherespecialbooleanvalues#tand#frepresenttruthandfalsehood,andeverythingelseistrue.ThereasonforhavinganisTrue()istoencapsulatethechosenbehaviour.Ifwewantedtochangethemeaningoftruth,weneedonlydosohere. 30CHAPTER3.INTERPRETERVERSION0.0.0053subas_stringfref($_[0]);gItjustreturnstheclassnameoftheobject.Thisisneededoccasionallyinthecasewhereinternalssuchasprimitiveoperationsmightbereturnedbytheevaluator,forexample:>*PScm::Primitive::MultiplyButthatisanunusualandusuallyunintentionalsituation.Themainasstring()methodsarestrate-gicallyplacedaroundthebynowfamiliarPScm::Exprhierarchy,asshowninFigure3.5.Figure3.5:PScm::Exprasstring()methodsPScm::ExprPscm::Expr::ListPScm::Expr::Atomas_stringas_stringPScm::Expr::LiteralPScm::Expr::SymbolPScm::Expr::StringPScm::Expr::Numberas_stringTheasstring()methodinPScm::Expr::Atomisjustacalltovalue():030subas_stringf$_[0]->valuegThatmethodworksforbothsymbolsandnumbers.PScm::Expr::List'sasstring()methodreturnsastringrepresentationofthelistbyrecursivelycallingasstring()oneachofitscomponentsandconcatenatingtheresult,separatedbyspacesandwrappedinbraces5.055subas_stringf056my($self)=@_;057return'('5Wehaven'tseenanythingyetthatmight,whenevaluated,returnalistforprinting.That'sforlater. 3.9.SUMMARY31058.join('',mapf$_->as_stringg$self->value)059.')';060gFinally,PScm::Expr::String'sasstring()methodatLines97-104overridestheoneinPScm::Expr::Atombecauseitneedstoputbackanybackslashesthattheparsertookout,andwrapitselfindoublequotes.097subas_stringf098my($self)=@_;099100my$copy=$self->value;101$copy=~s/\/\\/sg;102$copy=~s/"/\"/sg;103returnqq'"$copy"';104g3.9SummaryWe're nallyinapositiontounderstandthewholeofPScm::ExprasshowninListing3.11.7onpage43.The nalversionofourdiagram,withallofthemethodsfromPScm::ExprinplaceisshowninFigure3.6onthefollowingpage.Thatmayseemlikealotofcodeforwhatise ectivelyjustapocketcalculator6,butwhathasbeendoneistolaythegroundworkforamuchmorepowerfulsetoflanguageconstructsthatwillbeaddedinsubsequentchapters.Let'srecapwithanoverviewofthewholething.AglobalenvironmentissetupinthePScmpackage,containingbindingsforde nedoperations.Thetop-levelread-eval-printloop(repl)inthePScmpackagecreatesaPScm::ReadobjectandrepeatedlycallsitsRead()method.ThatRead()methodreturnsPScm::Exprobjectswhichthereplevaluates.ItevaluatesthembycallingtheirEval()method.{PScm::Expr::NumberandPScm::Expr::StringobjectsbothshareanEval()methodthatjustreturnstheobjectunevaluated.{PScm::Expr::SymbolobjectshaveanEval()methodthatlooksupthevalueofthesymbolintheglobalenvironment.{PScm::Expr::ListobjectshaveanEval()methodthatevaluatesthe rstcomponentofthelist,whichshouldreturnaprimitiveoperationorspecialform,thencallsthatoperationsApply()methodwiththeremainingunevaluatedcomponentsofthelistasargument.Whathappensnextdependsonthetypeoftheoperation.6especiallyonewherethe+and/keysdon'twork. 32CHAPTER3.INTERPRETERVERSION0.0.0Figure3.6:PScm::ExprmethodsPScm::ExprisTrueEvalvaluePscm::Expr::ListPScm::Expr::Atomnewnewvaluevaluefirstas_stringrestas_stringEvalPScm::Expr::LiteralPScm::Expr::SymbolEvalPScm::Expr::StringPScm::Expr::Numberas_stringnewPScm::PrimitiveobjectsshareanApply()methodthatevaluateseachoftheargumentsandthenpassesthemtotheindividualprimitive'sprivateapply()method.PScm::SpecialFormobjectseachhavetheirownApply()methodthatdecideswhether,andhow,toevaluatethearguments.ThereplthentakestheresultoftheevaluationandcallsitsPrint()method,whichisde nedinthePScmbaseclass.{ThatPrint()methodjustcalls$self->asstring()andprintstheresult.ThePScm::Expr::Atomclasshasanasstring()methodthatreturnstheunderlyingscalar,butPScm::Expr::Stringprovidesanoverridethatwrapstheresultindoublequotes.ThePScm::Expr::Listclasshasanasstring()methodthatrecursivelycallsas-string()onitscomponentsandreturnstheresultwrappedinbraces.AttheheartofthewholeinterpreteristhedynamicbetweenEval()whichevaluatesexpressions,andApply()whichappliesoperationstotheirarguments. 3.10.TESTS333.10TestsThetestmoduleforour rstversionoftheinterpreterisinListing3.11.8onpage46.ThePScm::TestpackageshowninListing3.11.9onpage47providesanevalok()subwhichtakesastringexpression,writesitouttoa le,andcallsReadEvalPrint()onit,withtheoutputredirectedtoanother le.Itthenreadsthatoutputbackinandcomparesittoitssecondargument7.Thevarioussimpletestsjustexercisethesystem.Toallowuserstoplayalittlemorewiththeinterpreter,there'satinyinteractiveshellthatrequiresTerm::ReadLine::Gnuandthelibreadlinelibrary.It'sint/interactiveandcanberun,withoutinstallingtheinterpreter,bydoing:$perl-Ilib./t/interactivefromtherootofanyversionofthedistribution.It'sshortenoughtoshowhereinitsentirety,inListing3.11.10onpage48.7Ok,IshouldhaveusedIO::String,sosueme. 34CHAPTER3.INTERPRETERVERSION0.0.03.11Listings3.11.1PScm.pm001packagePScm;002003usestrict;004usewarnings;005usePScm::Read;006usePScm::Env;007usePScm::Primitive;008usePScm::SpecialForm;009useFileHandle;010011requireExporter;012013our@ISA=qw(Exporter);014our@EXPORT=qw(ReadEvalPrint);015016=head1NAME017018PScm-Scheme-likeinterpreterwritteninPerl019020=head1SYNOPSIS021022usePScm;023ReadEvalPrint($infilehandle[,$outfilehandle]);024025=head1DESCRIPTION026027Justmessingabout,Atoylispinterpreter.028029=cut030031our$GlobalEnv=newPScm::Env(032'*'=>newPScm::Primitive::Multiply(),033'-'=>newPScm::Primitive::Subtract(),034if=>newPScm::SpecialForm::If(),035);036037subReadEvalPrintf038my($infh,$outfh)=@;039040$outfh||=newFileHandle(">-");041my$reader=newPScm::Read($infh);042while(defined(my$expr=$reader->Read))f043my$result=$expr->Eval();044$result->Print($outfh);045g046g047048subPrintf049my($self,$outfh)=@; 3.11.LISTINGS35050print$outfh$self->asstring," ";051g052053subasstringfref($[0]);g054055subnewfblessfg,$[0]g0560571; 36CHAPTER3.INTERPRETERVERSION0.0.03.11.2PScm/Env.pm001packagePScm::Env;002003usestrict;004usewarnings;005usebaseqw(PScm);006007subnewf008my($class,%bindings)=@;009010blessfbindings=>f%bindingsg,g,$class;011g012013subLookUpf014my($self,$symbol)=@;015016if(exists($self->fbindingsgf$symbol->valueg))f017return$self->fbindingsgf$symbol->valueg;018gelsef019die"nobindingfor@f[$symbol->value]g",020"in@f[ref($self)]g ";021g022g0230241; 3.11.LISTINGS373.11.3PScm/Read.pm001packagePScm::Read;002003usestrict;004usewarnings;005usePScm::Expr;006usePScm::Token;007usebaseqw(PScm);008009subnewf010my($class,$fh)=@;011blessf012FileHandle=>$fh,013Line=>'',014g,$class;015g016017subReadf018my($self)=@;019020my$token=$self->nexttoken();021returnundefunlessdefined$token;022023return$tokenunless$token->isopentoken;024025my@res=();026027while(1)f028$token=$self->Read;029die"unexpectedEOF"030if!defined$token;031lastif$token->isclosetoken;032push@res,$token;033g034035returnnewPScm::Expr::List(@res);036g037038subnexttokenf039my($self)=@;040041while(!$self->fLineg)f042$self->fLineg=$self->fFileHandleg->getline();043returnundefunlessdefined$self->fLineg;044$self->fLineg=~s/^s+//s;045g046047for($self->fLineg)f048s/^(s*//&&returnPScm::Token::Open->new();049s/^)s*//&&returnPScm::Token::Close->new();050s/^([-+]?d+)s*//051&&returnPScm::Expr::Number->new($1); 38CHAPTER3.INTERPRETERVERSION0.0.0052s/^"((?:(?:\.)|([^"]))*)"s*//&&dof053my$string=$1;054$string=~s/\//g;055returnPScm::Expr::String->new($string);056g;057s/^([^s()]+)s*//058&&returnPScm::Expr::Symbol->new($1);059g060die"can'tparse:$self->fLineg";061g0620631; 3.11.LISTINGS393.11.4PScm/Token.pm001packagePScm::Token;002003usestrict;004usewarnings;005usebaseqw(PScm);006007subisopentokenf0g008subisclosetokenf0g009010##########################011packagePScm::Token::Open;012013usebaseqw(PScm::Token);014015subisopentokenf1g016017###########################018packagePScm::Token::Close;019020usebaseqw(PScm::Token);021022subisclosetokenf1g0230241; 40CHAPTER3.INTERPRETERVERSION0.0.03.11.5PScm/Primitive.pm001packagePScm::Primitive;002003usestrict;004usewarnings;005usebaseqw(PScm::Expr);006007subApplyf008my($self,$form)=@;009010my@unevaluatedargs=$form->value;011my@evaluatedargs=mapf$->Eval()g@unevaluatedargs;012return$self->apply(@evaluatedargs);013g014015subchecktypef016my($self,$thing,$type)=@;017018die"wrongtypeargument(",ref($thing),")to",ref($self),019" "020unless$thing->isa($type);021g022023##################################024packagePScm::Primitive::Multiply;025026usebaseqw(PScm::Primitive);027028subapplyf029my($self,@args)=@;030031my$result=PScm::Expr::Number->new(1)->value();032033while(@args)f034my$arg=shift@args;035$self->checktype($arg,'PScm::Expr::Number');036$result*=$arg->value;037g038039returnnewPScm::Expr::Number($result);040g041042##################################043packagePScm::Primitive::Subtract;044045usebaseqw(PScm::Primitive);046047subapplyf048my($self,@args)=@;049050unshift@args,PScm::Expr::Number->new(0)if@args<2;051 3.11.LISTINGS41052my$arg=shift@args;053$self->checktype($arg,'PScm::Expr::Number');054055my$result=$arg->value;056057while(@args)f058$arg=shift@args;059$self->checktype($arg,'PScm::Expr::Number');060$result-=$arg->value;061g062063returnnewPScm::Expr::Number($result);064g0650661; 42CHAPTER3.INTERPRETERVERSION0.0.03.11.6PScm/SpecialForm.pm001packagePScm::SpecialForm;002003usestrict;004usewarnings;005usebaseqw(PScm::Expr);006007##############################008packagePScm::SpecialForm::If;009010usebaseqw(PScm::SpecialForm);011012subApplyf013my($self,$form)=@;014015my($condition,$truebranch,$falsebranch)=$form->value;016017if($condition->Eval()->isTrue)f018return$truebranch->Eval();019gelsef020return$falsebranch->Eval();021g022g0230241; 3.11.LISTINGS433.11.7PScm/Expr.pm001packagePScm::Expr;002003usestrict;004usewarnings;005usebaseqw(PScm::Token);006007subisTruef008my($self)=@;009scalar($self->value);010g011012subEvalf013my($self)=@;014return$self;015g016017subvaluef$[0]g018019#########################020packagePScm::Expr::Atom;021usebaseqw(PScm::Expr);022023subnewf024my($class,$value)=@;025bless$value,$class;026g027028subvaluef$f$[0]gg029030subasstringf$[0]->valueg031032#########################033packagePScm::Expr::List;034usebaseqw(PScm::Expr);035036subnewf037my($class,@list)=@;038039$class=ref($class)||$class;040bless[@list],$class;041g042043subvaluef@f$[0]gg044045subfirstf$[0][0]g046047subrestf048my($self)=@;049050my@value=$self->value;051shift@value; 44CHAPTER3.INTERPRETERVERSION0.0.0052return$self->new(@value);053g054055subasstringf056my($self)=@;057return'('058.join('',mapf$->asstringg$self->value)059.')';060g061062subEvalf063my($self)=@;064my$op=$self->first()->Eval();065return$op->Apply($self->rest);066g067068###########################069packagePScm::Expr::Symbol;070usebaseqw(PScm::Expr::Atom);071072subEvalf073my($self)=@;074return$PScm::GlobalEnv->LookUp($self);075g076077############################078packagePScm::Expr::Literal;079usebaseqw(PScm::Expr::Atom);080081###########################082packagePScm::Expr::Number;083usebaseqw(PScm::Expr::Literal);084085useMath::BigInt;086087subnewf088my($class,$value)=@;089$value=newMath::BigInt($value)unlessref($value);090$class->SUPER::new($value);091g092093###########################094packagePScm::Expr::String;095usebaseqw(PScm::Expr::Literal);096097subasstringf098my($self)=@;099100my$copy=$self->value;101$copy=~s/\/\\/sg;102$copy=~s/"/\"/sg;103returnqq'"$copy"'; 3.11.LISTINGS45104g1051061; 46CHAPTER3.INTERPRETERVERSION0.0.03.11.8t/PScm.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>10;006007BEGINfuseok('PScm')g008009evalok('1','1','numbers');010evalok('+1','1','explicitpositivenumbers');011evalok('-1','-1','negativenumbers');012evalok('"hello"','"hello"','strings');013evalok('(*234)','24','multiplication');014evalok('(-1023)','5','subtraction');015evalok('(-10)','-10','negation');016evalok('(if(*01)1020)','20','simpleconditional');017evalok(<new;011012subimportf013my($self)=shift;014my$pack=caller;015$Test->exportedto($pack);016$Test->plan(@);017018$self->exporttolevel(1,$self,'evalok');019$self->exporttolevel(1,$self,'evaluate');020g021022subevalokf023my($expr,$expected,$name)=@;024my$result=evaluate($expr);025$result.=" "if$expected=~/ /;026$Test->iseq($result,$expected,$name);027g028029subevaluatef030my($expression)=@;031032my$fh=newFileHandle(">junk");033$fh->print($expression);034$fh=newFileHandle('junk2");036PScm::ReadEvalPrint($fh,$outfh);037$fh=0;038$outfh=0;039my$res=`catjunk2`;040chomp$res;041unlink('junk');042unlink('junk2');043044#warn"#[$res] ";045return$res;046g0470481; 48CHAPTER3.INTERPRETERVERSION0.0.03.11.10t/interactive001usePScm;002003packageGetLine;004005useTerm::ReadLine;006007subnewf008my($class)=@;009blessf010term=>newTerm::ReadLine('PScheme'),011g,$class;012g013014subgetlinef015my($self)=@;016$self->ftermg->readline('>');017g018019packagemain;020021my$in=newGetLine();022023ReadEvalPrint($in);024025#vim:ft=perlFullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.0.0.tgz Chapter4Implementingletletallowstheextensionoftheenvironment,temporarily,toincludenewbindingsofsymbolstodata.letwasintroducedinSection2.6onpage12butasaquickreminder,hereitisinaction:>(let((x10)>(y20))>(*xy))200>xError:nobindingforxinPScm::EnvOfcoursetheEnvironmentthathasbeendescribedsofarisnotextensible,sothe rstthingtodoistolookathowwemightchangetheenvironmentpackagetoallowextension.4.1TheEnvironmentRemembertheoriginalenvironmentimplementationfromSection3.2onpage16,wherewejustcreatedanobjectwrapperaroundaPerlhash?Wecanbuildonthisidea,butweneedtothinkabitharderaboutwhatenvironmentextensionactuallymeans.Itwouldbeagoodideatokeeptheenvironmentextensionsseparatefromwhatisalreadyintheenvironment,sothattheycanbeeasilyundonewhenthetimecomes.It'sareallyBadIdeatojustpokemorekey-valuepairsintothathash;thecostofworkingouthowtoundothosechangescouldbeprohibitive.Therefore,eachextensionshouldhaveitsownobjecthash.Ausefuldistinctiontomakeatthispointisbetweenanyindividualhash,andtheenvironmentasawhole.WhenthetextreferstotheenvironmentasawholeIt'lljustsaytheenvironment",butwhenIt'stalkingaboutaparticularobjecthashcomponent,It'llsayenvironmentframe",orjustframe".4.1.1AStack-basedEnvironmentAsimpleextensionthen,andonewhichanumberofprogramminglanguagesdoinfactimplement,isastackofenvironmentframes.Anewframecontainingthenewbindingsispushedontopoftheold,andtheLookUp()methodstartsatthetopofthestackandworksitswaydownuntiliteither ndsabindingfortheargumentsymbol,orhitsthebottomofthestackandsignalsanerror.Torestorethepreviousenvironment,thetopframeissimplypoppedo thestackagain.49 50CHAPTER4.IMPLEMENTINGLETPerllistshavepushandpopoperations,sowecouldeasilyusethosewithasimplearrayrepresentingthestack.Alternativelywecouldkeepacurrenttopofstack"index,andincrementthattopush,ordecrementittopop,somethinglike:subpushfmy($self,$frame)=@_;$self->fstackg[$self->findexg]=$frame;++$self->findexg;gsubpopfmy($self)=@_;--$self->findexg;die"stackunderflow"if$self->findexg<0;return$self->fstackg[$self->findexg];gThishastheminoradvantageofnotimmediatelyloosingwhatwaspreviouslyonthetopofthestackafterapop().Themajordrawbackofastackisthatitisalinearstructure,andextendingthestackagainnecessarilyobliterateswhatwaspreviouslythere,seeFigure4.1.Ifweplanatalaterstagetosupportclosure,wherefunctionshangontotheirenvironmentsaftercontrolhasleftthem,thenastackisobviouslyinadequateunlesssomepotentiallycomplexadditionalcodeprotectsandcopiesthosevunerableenvironmentframes.Figure4.1:StacksDestroyOldEnvironmentFramesgrowshrinkgrowFrame2Frame2Frame3Frame1Frame1Frame1Frame14.1.2ALinkedListEnvironmentEnterthelinkedlist.Alinkedlistisjustacollectionofobjects,hashesorwhatever,whereeachonecontainsareferencetothepreviousoneonthelist.Ifanenvironment,ratherthanbeingastackofframes,wasalinkedlistofframes,thenjustaswithastack,PScm::Env::LookUp()needonlywalkthroughthechainuntilit ndsthe rst(mostlocal)occurrenceofthesymbolandreturnthat.Theadvantagesofalinkedlistarethatmanyenvironmentframescansharethesameparent,andthereforecreatinganewextensionframedoesnotdestroythepreviousextension,seeFigure4.2onthenextpage.AslongassomethingcontinuestoholdareferencetotheoldFrame2inthis gure,thenitwillnotbegarbagecollectedandremainsasvalidasanyotherenvironment.Here'sPScm::Env::LookUp()modi edtousealinkedlist. 4.1.THEENVIRONMENT51Figure4.2:LinkedListsDon'tDestroyOldEnvironmentFramesgrowshrinkgrowFrame2Frame2Frame2Frame3Frame1Frame1Frame1Frame1025subLookUpf026my($self,$symbol)=@_;027028if(exists($self->fbindingsgf$symbol->valueg))f029return$self->fbindingsgf$symbol->valueg;030gelsif($self->fparentg)f031return$self->fparentg->LookUp($symbol);032gelsef033die"nobindingfor@f[$symbol->value]g",034"in@f[ref($self)]g ";035g036gTheonlychangeisonLines30{31(inbold)whereifLookUp()can't ndthesymbolinthecurrentenvironmentframeitlooksinitsparentframe,ifithasone.ThePScm::Env::new()methodislittlechanged,itadditionallycheckstheargumentclassincasenew()isbeingcalledasanobjectmethod(whichitwillbe),andaddsaparent eldtotheobject,withaninitialzerovaluemeaning oparent".007subnewf008my($class,%bindings)=@_;009010$class=ref($class)||$class;011blessfbindings=>f%bindingsg,parent=>0g,$class;012gFinallyweneedanExtend()methodofPScm::Envthatwillcreateanewenvironmentfromanexistingonebycreatinganewframewiththenewbindings,andsettingthenewframe'sparenttobetheoriginalenvironment.014subExtendf015my($self,$ra_symbols,$ra_values)=@_;016017my%bindings=();018my@names=mapf$_->valueg@$ra_symbols;019@bindingsf@namesg=mapf$_->Eval($self)g@$ra_values;020my$new=$self->new(%bindings); 52CHAPTER4.IMPLEMENTINGLET021$new->fparentg=$self;022return$new;023gBecausetheExtend()methodwillbeusedbyletandotherconstructslater,ittakesareferencetoanarrayofsymbolsandareferencetoanarrayofvalues,ratherthanthesimple%initialhashthatnew()takes.OnLine18Itmapsthesymbolstoalistofstrings,thenonLine19itusesthosestringsaskeysinahashmappingthemtotheirequivalentvalues.OnLine20,createsanewenvironmentwiththathash.FinallyonLine21itsetsthatnewenvironment'sparenttobetheoriginalenvironment$selfandreturnsthenewenvironment.4.2GlobalEnvironmentshaveaProblemBeforeproceedingtoimplementlet,weneedtoaddressanincipientproblemwiththeglobalenviron-ment.Tounderstandthis,assumeourinterpreteralreadyhasletinstalledasPScm::SpecialForm::Let,andisabouttoevaluatethe(+ab)partoftheexpression(let((a10)(b20))(+ab))ItwillhavealreadyextendedtheenvironmentwiththebindingsforaandbsotheglobalenvironmentatthatpointwilllooklikeFigure4.3.Figure4.3:Environmentduringevaluationofexamplelet"PScm::Env"a"PScm::Expr::Number10"b"PScm::Expr::Number20parentPScm::Env"let"PScm::SpecialForm::Let"if"PScm::SpecialForm::If"*"PScm::Primitive::Multiply"-"PScm::Primitive::SubtractNow,considerwhatletmighthavehadtodotoextendtheenvironmentandmighthavetodotorestoreitagainafterwards: 4.3.ENVIRONMENTPASSING531.Savethecurrentvalueof$PScm::GlobalEnv;2.CallExtend()on$PScm::GlobalEnvtogetanewonewithaandbappropriatelybound;3.Assignthatnewenvironmentto$PScm::GlobalEnv;4.CallEval()ontheexpression(+ab)andsavetheresult;5.Restorethepreviousvalueof$PScm::GlobalEnv;6.Returntheresultofevaluatingthebody.There'ssomethingnotquiterightthere,somethingugly.We'vemadeittheresponsibilityoflettorestorethatpreviousenvironment,andifwegodownthatroad,alloftheotheroperationsthatextendenvironmentswillsimilarlyberequiredtorestoretheenvironmentfortheircallers.There'sanotherbitofuglinesstoo,thesimpleexistenceofaglobalvariable.It'stheonlyoneinourapplication.Doesithavetobethere?Whatcouldreplaceit?4.3EnvironmentPassingYouareaskedtotakealeapoffaithherewhenitissuggestedthatabettermechanismistopasstheenvironmentaroundbetweenEval()andApply()withintheinterpreter.JustsupposethatEval()wasgiventhecurrentenvironmentasargumentalongwiththeexpressiontoevaluate.FurthermoresupposethatitpassedtheenvironmenttoApply().Nowwe'vealreadyseenthatSpecialForms(ofwhichletisone,)eachhavetheirownApply()method,sohowwouldlet'sApply()lookiftheenvironmentwerepassedin?Itwould:1.CallExtend()ontheargumentenvironmenttogetanewonewithaandbappropriatelybound;2.ReturntheresultofcallingEval()ontheexpression(+ab)withthenewenvironmentasargu-ment.Isn'tthatbetter!1Becauseenvironmentswouldbelocal(my)variables,andbecausePScm::Env::Extend()doesnotaltertheoriginalenvironmentinanyway,wecanrelyPerl'sowngarbagecollectiontakecareofoldunwantedenvironmentsforus.Thechangestoourinterpretertomakethishappenareinfactquitelimited.Fistofall,becauseEval()nowexpectsanenvironmentasargument,thetop-levelPScm::ReadEvalPrint()mustcreateoneandpassitin.Notethatitisthesameasthe$PScm::GlobalEnvfromourpreviousinterpreter,withtheadditionofabindingforlet.031subReadEvalPrintf032my($infh,$outfh)=@_;033034$outfh||=newFileHandle(">-");035my$reader=newPScm::Read($infh);1Iftheimprovementtothedesignofletdoesnotwarrantsuchanapparentlydrasticchange,itshouldbenotedthatclosureismucheasiertoimplementwiththismodelandcouldbeegregiouslydiculttoimplementotherwise. 54CHAPTER4.IMPLEMENTINGLET036while(defined(my$expr=$reader->Read))f037my$result=$expr->Eval(038newPScm::Env(039let=>newPScm::SpecialForm::Let(),040'*'=>newPScm::Primitive::Multiply(),041'-'=>newPScm::Primitive::Subtract(),042if=>newPScm::SpecialForm::If(),043)044);045$result->Print($outfh);046g047gNowyoushouldrememberfromSection3.5onpage25thattherearethreeimplementationsofEval(),oneinPScm::Expr,oneinPScm::Expr::SymbolandoneinPScm::Expr::List.Eachofthesemustdealwiththeextraenvironmentargumenttheyarenowbeingpassed.ThedefaultEval()methodforliteralatoms(strings,numbersandothers)isfunctionallyunchanged.Itignoresanyargumentenvironmentbecauseliteralsevaluatetothemselves.012subEvalf013my($self,$env)=@_;014return$self;015gTheEval()methodforsymbolsnowusestheargumentenvironmentratherthanaglobaloneinwhichtolookupitsvalue:069packagePScm::Expr::Symbol;070usebaseqw(PScm::Expr::Atom);071072subEvalf073my($self,$env)=@_;074return$env->LookUp($self);075gTheEval()methodforlists(expressions)islittlechangedeither,itevaluatestheoperationinthecurrent(argument)environmentthencallstheoperation'sApply()method,passingthecurrentenvironmentasanadditionalargument.062subEvalf063my($self,$env)=@_;064my$op=$self->first()->Eval($env);065return$op->Apply($self->rest,$env);066gSoApply()mustchangetoo.Asshownearlier,ThereisoneApply()methodforallPScm::Primitiveclasses,whichevaluatesalloftheargumentstotheprimitiveoperationthencallstheoperation'spri-vateapply()methodwithitspre-evaluatedarguments.Thatneedstochangeonlytoevaluatethoseargumentsintheargumentenvironment: 4.4.LETITSELF55007subApplyf008my($self,$form,$env)=@_;009010my@unevaluated_args=$form->value;011my@evaluated_args=mapf$_->Eval($env)g@unevaluated_args;012return$self->_apply(@evaluated_args);013gNoteparticularlythatthereisnoneedtopasstheenvironmenttotheprivateapply()method:sinceallitsargumentsarealreadyevaluatedithasnoneedofanenvironmenttoevaluateanythingin.Thereforetheprimitivemultiplyandsubtractoperationsareunchangedfromthepreviousversionoftheinterpreter.TheApply()methodforspecialformsisseparatelyimplementedbyeachspecialform.Inourpreviousinterpretertherewasonlyonespecialform:if,solet'stakealookathowthathaschanged.028packagePScm::SpecialForm::If;029030usebaseqw(PScm::SpecialForm);031032subApplyf033my($self,$form,$env)=@_;034035my($condition,$true_branch,$false_branch)=$form->value;036037if($condition->Eval($env)->isTrue)f038return$true_branch->Eval($env);039gelsef040return$false_branch->Eval($env);041g042g0430441;Prettysimple,TheonlychangeisthatPScm::SpecialForm::If::Apply()passesitsadditionalargu-ment$envtoeachcalltoEval().4.4letItselfNowwe'redone,wecanlookatthatimplementationofPScm::SpecialForm::Let::Apply()inourenvironmentpassinginterpreter.Rememberthatlethasthegeneralform:(let(hbindingi...)hexpressioni)wherehbindingiis:(hsymbolihexpressioni)So,withthatinmind,here'stheApply()method. 56CHAPTER4.IMPLEMENTINGLET008packagePScm::SpecialForm::Let;009010usebaseqw(PScm::SpecialForm);011012subApplyf013my($self,$form,$env)=@_;014015my($bindings,$body)=$form->value;016my(@symbols,@values);017018foreachmy$binding($bindings->value)f019my($symbol,$value)=$binding->value;020push@symbols,$symbol;021push@values,$value;022g023024return$body->Eval($env->Extend(@symbols,@values));025gItstartso atLine15extractingthebindingsandbodyfromtheargumentform.Thenitsetsuptwoemptyliststocollectthesymbolsandthevalues(Line16)separately.TheninthelooponLines18-22ititeratesovereachbindingcollectingtheunevaluatedsymbolinonelistandtheevaluatedargumentintheother.FinallyonLine24itcallsthebody'sEval()methodwithanextendedenvironmentwherethosesymbolsareboundtothosevalues.Line24encapsulatesournewsimplede nitionforletquiteconcisely.TheenvironmentcreatedbyExtend()ispasseddirectlytoEval()andtheresultofcallingEval()isreturneddirectly.4.5SummaryInordertogetletworkingeasilywehadtomaketwochangestotheoriginalimplementation.FirstlyaddinganExtendmethodtothePScm::Envclasstocreatenewenvironments,andsecondlyalteringthevariousEval()andApply()methodstopasstheenvironmentasargumentratherthanusingaglobalenvironment.Havingdonethat,theactualimplementationofletwastrivial.4.6TestsRatherthanaddingmoreteststot/PScm.t,There'sanewt/PScmLet.twhichyoucanseeinList-ing4.7.1onthefacingpage.Itaddstwotests,the rstjustteststhatavalueboundbyaletexpressionisavailableinthebodyofthelet,andthesecondprovesthatthebodyoftheletcanbeanarbitraryexpression. 4.7.LISTINGS574.7Listings4.7.1t/PScmLet.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>3;006007BEGINfuseok('PScm')g008009evalok('(let((x2))x)','2','simplelet');010011evalok(<(definetimes2>(let((n2))>(lambda(x)(*nx))))times2>(times24)8Thelambdaexpressionisbeingexecutedinanenvironmentwherenisboundto2.Theresultofthatlambdaexpression,aclosure,isalsotheresultoftheletexpressionandthereforegetsboundtothesymboltimes2intheglobalenvironment.1Formalargumentsarethenamesthatafunctiongivestoitsarguments.Actualargumentsarethevaluespassedtoafunction.59 60CHAPTER5.IMPLEMENTINGLAMBDAFigure5.1:Functionsextendanenvironmentjustlikeletdoes(*xx)Env2x4(square4)Env1letlambdaif*-squareNowwhentimes2iscalled,theclosurebody(*nx)mustexecuteinanenvironmentwherenisstillboundto2,asinFigure5.2.Figure5.2:Closuresextendthelexicalenvironment(*nx)Env3x4(lambda(x)(*nx))Env2n2(times24)Env1letlambdaif*-times2Soreferringtothat gure:letextendedtheglobalenvironmenttoEnv2withabindingofnto2.Thentheclosure,whenitwascreatedbylambdainEnv2,musthavesomehowheldonto,orcaptured"Env2,sothatwhentheclosureislaterexecutedEnv2istheonethatitextendstoEnv3withitsownargumentx. 5.1.LAMBDA61Afunctionwhichisnotaclosurewouldhavetopickadi erentenvironmenttoextend.Itcouldchoosetheenvironmentitisbeingexecutedinbutthatwouldcausehorrendousconfusion:anyvariablesreferredtointhefunctionbodythatwerenotdeclaredbythefunctionmightpickupvaluesrandomlyfromthecallersenvironment.Alternativelyitcouldextendtheglobalenvironment.Thelatterchoiceisthestandardonefornon-closureimplementations,butasalreadynotedallfunctionsinPSchemeareclosures(andtherearenoadvantagestothemnotbeingclosures)sowedon'thavetoworryaboutthat.Sowe'regoingtocontinuetousethewordsfunctionandclosureprettymuchinterchangeably,butwhenweusethewordfunctionwe'reemphasizingthefunctionalaspectsoftheobjectunderdiscussion,andwhenweusethewordclosure,we'reemphasizingitsenvironmentalbehaviour.Whenconsideringtheactualimplementationofclosures(functions)therearetwopartstothestory.The rstpartishowlambdacreatesaclosure,andthesecondishowtheclosuregetsevaluatedwhenitiscalled.Inthenextsectionwe'lllookatthe rstpart,howlambdacreatesaclosure.5.1lambdaWeneedagood,simpleexampleofclosuresinaction.Thefollowingexample tsourpurposes,butisabitmorecomplicatedthantheexampleswe'veseensofar:>(let((a4)>(times2>(let((n2))>(lambda(x)>(*xn)))))>(times2a))8Thisexampleisnotmuchdi erentfromourearliertimes2example,exceptthatanouterletprovidesbindingsforbothtimes2andavariableathatwillbeargumenttotimes2.Itishoweverjustalittletricky,soindetail:Theouterletreads:letabe4andtimes2betheresultofevaluatingtheinnerletintheexpression(times2a)."Theinnerletreadsletnbe2intheexpression(lambda...)."Thevalueofthatinnerletistheresultofevaluatingthatlambdaexpressionandthusaclosure,andthatiswhatgetsboundtothesymboltimes2bytheouterlet.When(times2a)isevaluated,theclosureboundtotimes2canstillsee"thevariablenfromtheenvironmentthatwascurrentwhenitwascreated,andsothebodyoftheclosure,(*xn),wilthxboundto4andnboundto2,producestheexpectedresult8.Justtobeabsolutelysurethatsemanticsofthatexpressionarewellunderstood,hereisanequivalentinPerl:fmy$a=4;my$times2=dof 62CHAPTER5.IMPLEMENTINGLAMBDAmy$n=2;subfmy($x)=@_;$x*$n;gg;$times2->($a);gNowwe'regoingtowalkthroughtheexecutionofthePSchemestatementinalotmoredetail,consideringwhattheinterpreterisactuallydoingtoproducethe nalresult.Thevery rstthingthathappenswhenevaluatingourPSchemeexampleisthattheouterletevaluatesthenumber4intheglobalenvironment.Itdoesnotyetbindthatvaluetoa,it rstmustevaluatetheexpressionthatwillbeboundtotimes2.>(let((a4)>(times2>(let((n2))>(lambda(x)>(*xn)))))>(times2a))8Thenextthingthathappensistheouterletinitiatestheevaluationoftheinnerlet.Theinnerletextendstheglobalenvironmentwithabindingofnto2,ashilightedinthefollowingcodeandshowninFigure5.3onthenextpage.>(let((a4)>(times2>(let((n2))>(lambda(x)>(*xn)))))>(times2a))8Then,inthatnewenvironment,labelledEnv2,theletevaluatesthelambdaexpression:>(let((a4)>(times2>(let((n2))>(lambda(x)>(*xn)))))>(times2a))8Evaluatingalambdaexpressionisjustthesameasevaluatinganyotherlistexpression,its(unevaluated)argumentsarepassedtoitsApply()method,alongwiththecurrentenvironment.Inourexampletheargumentstothelambda'sApply()wouldbe: 5.1.LAMBDA63Figure5.3:letbindsnto2Env2n2creates(let((a4)(times2Env1(let((n2))(lambda(x)let(*xn)))))lambda(times2a))if*-1.Alistoftheunevaluatedargumentscontaining(a)theformalargumentstothefunction:(x)(b)thebodyofthefunction:(*xn)2.thecurrentenvironment,Env2thattheletjustcreated,withabindingofnto2.Tostarttomakethishappenwe rstneedtoaddanewsubclassofPScm::SpecialForm,ratherunsurprisinglycalledPScm::SpecialForm::Lambda,andweneedtoaddabindingfromthesymbollambdatoanobjectofthatclassintheinitialenvironment.Firstly,here'sReadEvalPrint()withtheadditionalbinding:031subReadEvalPrintf032my($infh,$outfh)=@_;033034$outfh||=newFileHandle(">-");035my$reader=newPScm::Read($infh);036while(defined(my$expr=$reader->Read))f037my$result=$expr->Eval(038newPScm::Env(039let=>newPScm::SpecialForm::Let(),040'*'=>newPScm::Primitive::Multiply(),041'-'=>newPScm::Primitive::Subtract(),042if=>newPScm::SpecialForm::If(),043lambda=>newPScm::SpecialForm::Lambda(),044)045);046$result->Print($outfh);047g048g 64CHAPTER5.IMPLEMENTINGLAMBDATheonlychangeistheadditionofLine43withthenewbindingforlambda.NowwecanlookatthatnewpackagePScm::SpecialForm::Lambda.AllitsApply()methodhastodoistostorethedetailsofthefunctionde nitionandthecurrentenvironmentinanothernewtypeofobjectrepresentingtheclosure:045packagePScm::SpecialForm::Lambda;046047usebaseqw(PScm::SpecialForm);048usePScm::Closure;049050subApplyf051my($self,$form,$env)=@_;052053my($args,$body)=$form->value;054returnPScm::Closure::Function->new($args,$body,$env);055g0560571;OnLine53itunpackstheformalarguments(i.e.(x))andbody((*xn))ofitsargument$form(theargumentstothelambdaexpression)andonLine54itreturnsanewPScm::Closure::Functionobjectcontainingthosevaluesand,mostimportantly,alsocontainingthecurrentenvironment(Env2inourexample.)ThatPScm::Closure::Function::new()method(actuallyinPScm::Closure)doesnomorethanbundleitsarguments:007subnewf008my($class,$args,$body,$env)=@_;009010blessf011args=>$args,012body=>$body,013env=>$env,014g,$class;015gSoinourexampleitisEnv2thatiscaptured,alongwiththeargumentsandbodyofthefunction,intheresultingclosure.ThisisshowninFigure5.4onthefacingpage.Aswe'venoted,thevalueoftheinnerletexpressionisthatnewClosureobject,andnexttheouterletrecievesthevalueoftheinnerlet,andextendstheglobalenvironmentwithabindingoftimes2tothat.Italsobindsato4:>(let((a4)>(times2>(let((n2))>(lambda(x)>(*xn)))))>(times2a))8 5.1.LAMBDA65Figure5.4:ClosureCapturestheLocalEnvironmentClosurecreatesargs(x)(let((a4)body(*xn)Env2(times2env(let((n2))n2(lambda(x)(*xn)))))creates(times2a))(let((a4)(times2Env1(let((n2))(lambda(x)let(*xn)))))lambda(times2a))if*-TheresultingenvironmentislabelledEnv3inFigure5.5.Figure5.5:letbindstimes2andaEnv3Closurecreatesa4times2args(x)(let((a4)body(*xn)Env2(times2env(let((n2))n2(lambda(x)(*xn)))))(times2a))createscreates(let((a4)(let((a4)(times2Env1(times2(let((n2))(let((n2))(lambda(x)let(lambda(x)(*xn)))))(*xn)))))lambda(times2a))(times2a))if*-NowatthispointtheonlythinghangingontotheoldEnv2,wherenhasavalue,isthatClosure,andtheonlythinghangingontotheClosureisthebindingfortimes2inEnv3(thecodefortheApply()methodoftheouterletiscurrentlyholdingontoEnv3.)HavingcreatedEnv3,theouterletevaluatesitsbody,(times2a)inthatenvironment. 66CHAPTER5.IMPLEMENTINGLAMBDA>(let((a4)>(times2>(let((n2))>(lambda(x)>(*xn)))))>(times2a))8Thatbringsustothesecondpartofourstory,howafunction(aclosure)getsevaluated.5.2EvaluatingaClosureTorecap,we'vereachedthestagewherethesubexpression(times2a)isabouttobeevaluated.ItwillbeevaluatedinthecontextofEnv3fromFigure5.5ontheprecedingpagewhichtheouterlethasjustsetupwithabindingofato4andtimes2totheclosure.Since(times2a)isalist,theEval()methodforlistscomesintoplayagain.Itevaluatesthe rstcomponentofthelist,thesymboltimes2,inthecontextofEnv3resultingintheClosure.Thenitpassestherestoftheform(alistcontainingthesymbola)unevaluated,alongwiththecurrentenvironmentEnv3,totheclosure'sApply()method.Closures,beingoperations,havetohaveanApply()method,andhereitis:043subApplyf044my($self,$form,$env)=@_;045046my@evaluated_args=mapf$_->Eval($env)g$form->value;047return$self->_apply(@evaluated_args);048gFirstofall,onLine46itevaluateseachcomponentoftheform(eachargumenttothefunction)withmap,passingtheargument$env(Env3)toeachcalltoEval().Afterall,closuresarefunctions,andfunctionstaketheirargumentsevaluated.AtLine47ourclosure'sApply()returnstheresultofcallingaseparateapply()methodonthoseevaluatedarguments,muchasprimitiveoperationsdo.Noteparticularilythatitdoesnotpassitsargument$envtotheprivateapply()method.Theprivateapply()methodisintheparentPScm::Closureclass2:021sub_applyf022my($self,@args)=@_;023024my$extended_env=025$self->env->ExtendUnevaluated([$self->args],[@args]);026return$self->body->Eval($extended_env);027gThisapply()methoddoesnotneedanargumentenvironmentbecausethecorrectenvironmenttoextendistheonethatwascapturedwhenthetheclosureobjectwascreated.OnLine24Itextendsthat2Why?Becausealaterversionoftheinterpreterwillsupportmorethanonetypeofclosure. 5.2.EVALUATINGACLOSURE67previouslycapturedenvironmentwithbindingsfromitsformalarguments,alsocollectedwhentheclosureobjectwascreated(i.e.x),totheactualargumentsitwaspassed(i.e.4,alreadyevaluated).Becausetheargumentsarealreadyevaluated,itmustcallanewvariantofPScm::Env::Extend()calledExtend-Unevaluated(),whichdoesjustthat.Lastlyapply()evaluatesitsbody(thebodyofthefunction,(*xn))passingthatextendedenvironmentasargumentandreturnstheresult(Line26).Returningtoourexample,we'restillconsideringtheevaluationofthesubexpression(times2a).Aswe'vesaidtheclosure'sApply()methodevaluatesitsargumentaintheenvironmentitwaspassed,Env3,resultingin4.Butitisthecapturedenvironment,Env2,thattheclosureextendswithabindingofxto4,resultinginEnv4(Figure5.6).ItisinEnv4,withxboundto4andnstillboundto2,thattheclosureexecutesthebodyofthefunction(*xn).Figure5.6:ClosureExtendsCapturedEnv(5)(let((a4)Env4(times2(let((n2))createsx4(lambda(x)(4)(*xn)))))(let((a4)(times2a))(times2Env3(let((n2))Closurecreates(lambda(x)a4(*xn)))))times2args(x)(2)(times2a))(let((a4)body(*xn)Env2(times2env(let((n2))n2(lambda(x)(*xn)))))(times2a))createscreates(3)(let((a4)(let((a4)(1)(times2Env1(times2(let((n2))(let((n2))(lambda(x)let(lambda(x)(*xn)))))(*xn)))))lambda(times2a))(times2a))if*-Figure5.6prettymuchtellsthewholestory.Here'sourexampleonelasttimesoitcanbewalkedthroughreferringtothe gure:(let((a4)(times2(let((n2))(lambda(x)(*xn)))))(times2a))At(1)inthe gure,theinnerletextendstheglobalenvEnv1withabindingofnto2producingEnv2. 68CHAPTER5.IMPLEMENTINGLAMBDAAt(2)theinnerletthenevaluatesthelambdaexpressioninthecontextofEnv2,creatingaClosurethatcapturesEnv2.At(3),theouterletextendstheglobalenvironmentEnv1withbindingsofato4andtimes2tothevalueoftheinnerlet:theClosure.At(4)theouterletevaluatesthesubexpression(times2a)inthecontextofEnv3.Inthisenvironmenttimes2evaluatestotheclosure,anditsApply()evaluatesainthesameenvironmentEnv3whereitevaluatesto4.Finally,at(5),theclosureextendstheoriginallycapturedenvironmentEnv2withabindingofxto4producingEnv4andevaluatesitsbody,(*xn),inthisenvironment.5.3PrintingaClosureItwouldbeniceif,whengivenaclosuretoprint,PSchemecouldproducesomethingabitmoreinforma-tivethantheunhelpfulPScm::Closure::Function"thatresultsfromthedefaultasstring()methodinthetop-levelPScmpackage.Wecouldinsteadprintarepresentationofthelambdaexpressionthatcreatedtheclosure.Forexample.>(let((square>(lambda(x)>(*xx))))>square)(lambda(x)(*xx))Thisistriviallyaccomplishedbyoverridingthedefaultasstring()methodinPScm::Closure.Here'sthatoverride.029subas_stringf030my($self)=@_;031returnPScm::Expr::List->new(032$self->_symbol,033$self->fargsg,034$self->fbodyg035)->as_string;036gAllitdoesistoconstructanewPScm::Expr::Listcontainingthesymbolthatconstructedtheclosure(lambda)theformalargumentstotheclosureandthebodyoftheclosure.Itthencallsthatlist'sas-string()methodandreturnstheresult.Theaquisitionofthelambdasymbolisdeferredtoaseparatemethodsymbol()inPScm::Closure::Function(againbecauselaterlaterversionsoftheinterpreterwillhavedi erentkindsofclosures).Here'ssymbol().050sub_symbolf051PScm::Expr::Symbol->new('lambda');052gJobdone.Closures,whenprinted,willnowproduceausefulrepresentationofthefunctiontheyperform. 5.4.SUMMARY695.4SummaryHopefullythepower, exibilityandeleganceofanenvironment-passinginterpretercombinedwithalinked-listenvironmentimplementationisbecomingapparent.Theenormousadvantageoverastackdisciplineisthatindividualenvironmentsneednotgoawayjustbecauseaparticularconstructreturns.Theycanhangaroundaslongastheyareneededandgarbagecollectionwillremovethemwhenthetimecomes.Withoutfurtheradothen,here'sthefullsourceforournewPScm::ClosurepackageinListing5.6.1onthenextpage.5.5TestsYoucanseethetestsforthelambdaforminanew le,t/PScmLambda.t,inListing5.6.2onpage72.The rsttestexercizesthesimplecreationofalambdaexpression,itsbindingtoasymbol,anditsapplicationtoarguments.Thesecondworksthroughprettymuchexactlytheexamplewe'vebeenworkingthrough.Thethirdstartsto exthemusclesofournascentinterpreteralittlemore.Itcreatesalocalmakemultiplierfunctionthatwhencalledwithanargumentnwillreturnanotherfunctionthatwillmultiplynbyitsargument.Itthenbindstheresultofcalling(makemultiplier3)totimes3andcalls(times35),con rmingthattheresultis15,asexpected.Incidentally,thisdemonstratesthattheenvironmentcreatedbyalambdaexpressionisequallyameanabletocapturebyaclosure.WecouldrewritethebodyofthatlasttestinPerlasfollows:fmy$times3=dofmy$makemultiplier=subfmy($n)=@_;returnsubfmy($x)=@_;return$n*$x;gg;$makemultiplier->(3);g;$times3->(5);g 70CHAPTER5.IMPLEMENTINGLAMBDA5.6Listings5.6.1PScm/Closure.pm001packagePScm::Closure;002003usestrict;004usewarnings;005usebaseqw(PScm);006007subnewf008my($class,$args,$body,$env)=@;009010blessf011args=>$args,012body=>$body,013env=>$env,014g,$class;015g016017subargsf$[0]->fargsg->valueg018subbodyf$[0]->fbodygg019subenvf$[0]->fenvgg020021subapplyf022my($self,@args)=@;023024my$extendedenv=025$self->env->ExtendUnevaluated([$self->args],[@args]);026return$self->body->Eval($extendedenv);027g028029subasstringf030my($self)=@;031returnPScm::Expr::List->new(032$self->symbol,033$self->fargsg,034$self->fbodyg035)->asstring;036g037038################################039packagePScm::Closure::Function;040041usebaseqw(PScm::Closure);042043subApplyf044my($self,$form,$env)=@;045046my@evaluatedargs=mapf$->Eval($env)g$form->value;047return$self->apply(@evaluatedargs);048g049 5.6.LISTINGS71050subsymbolf051PScm::Expr::Symbol->new('lambda');052g0530541; 72CHAPTER5.IMPLEMENTINGLAMBDA5.6.2t/PScmLambda.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>5;006useFileHandle;007008BEGINfuseok('PScm')g009010evalok(<(let((factorial>(lambda(n)>(ifn>(*n(factorial(-n1)))>1))))>(factorial3))Error:nobindingforfactorialinPScm::EnvItdidn'twork.Thereasonthatitdidn'tworkisobvious,consideringhowletworks.letevaluatestheexpressionhalfofitsbindingsintheenclosingenvironment,beforeitbindsthevaluestothesymbolsinanewenvironment,soitistheenclosingenvironment(theglobalenvironmentinthiscase)thatthelambdacaptures.Nowthatenvironmentdoesn'thaveabindingforfactorial,factorialisonlyvisiblewithinthebodyofthelet,soanyrecursivecalltofactorialfromthebodyofthefunction(closure)isboundtofail.Puttingitanotherway,letwillcreateabindingforfactorial,butonlybyextendingtheglob-alenvironmentafterthelambdaexpressionhasbeenevaluated,intheglobalenvironment,thereforecapturingtheglobalenvironment.Sotheerrorisnotcomingfromthecallto(factorial3),it'scomingfromtheattemptedrecursivecallto(factorial(-n1))insidethebodyofthefactorialde nition.TheenvironmentdiagraminFigure6.1onthenextpageshouldhelptomakethatclear.Theletevaluatesthelambdaexpressionintheinitialenvironment,Env1at(1),sothat'stheenvironmentthatgetscapturedbytheClosure.ThentheletbindstheclosuretothesymbolfactorialinanextendedenvironmentEnv2,andthat'swherethebodyofthelet,(factorial3),getsevaluatedat(2).Nowafterevaluatingitsargument3inEnv2,theclosureproceedstoextendtheenvironmentitcaptured,theglobalenvironmentEnv1,withabindingofnto3producingEnv3.It'sinEnv3thatthebodyofthefactorialfunctiongetsevaluatedat(3).Nownhasabindinginthatenvironment,butunfortunatelyfactorialdoesn't,sotherecursivecallfails.1Factorial(n),oftenwrittenn!,isn(n1)(n2)1.73 74CHAPTER6.RECURSIONANDLETRECFigure6.1:Whyrecursiondoesn'twork(3)(2)(factorial(-n1))(factorial3)Env3Env2n3factorialClosureargs(n)body(ifn...env(1)(let((factorialEnv1(lambda(n)..6.1letrecWhatweneedisavariationofletthatarrangestoevaluatethevaluesforitsbindingsinanenvironmentwherethebindingsarealreadyinplace.EssentiallytheenvironmentswouldappearasinFigure6.2.Figure6.2:Recursiveenvironments(3)(factorial(-n1))Env3n3(2)(factorial3)Env2Closurefactorialargs(n)body(ifn...env(1)(let((factorial(lambda(n)..Env1Inthis guretheclosurehasbeenpersuadedtocaptureanenvironmentEnv2containingabindingthatrefersbacktotheclosureitself(acircularreferenceine ect.)InthiscircumstanceanyrecursivecalltofactorialfromthebodyoftheclosurewouldworkbecausetheclosurewouldhaveextendedEnv2anditsbodywouldexecuteinacontext(Env3)wherefactorialdidhaveavalue.Thespecialformwe'relookingforiscalledletrec(shortforletrecursive")anditisn'ttootricky, 6.1.LETREC75althoughabitofahack.Let's rstremindourselveshowletworks.1.Evaluatethevaluecomponentofeachbindinginthecurrent,passedinenvironment;2.Createanewenvironmentasanextensionofthecurrentone,withthosevaluesboundtotheirsymbols;3.Evaluatethebodyoftheletinthatnewenvironment.Ourvariant,letrec,isn'tallthatdi erent.Whatitdoesis:1.Createanewextendedenvironment rst,withdummyvaluesboundtothesymbols;2.Evaluatethevaluesinthatnewenvironment;3.Assignthevaluestotheirsymbolsinthatnewenvironment;4.Evaluatethebodyinthatnewenvironment.Obviouslyifanyofthevaluesinaletrecareexpressionsotherthanlambdaexpressions,andtheymakereferencetootherletrecvaluesinthesamescope,thentherewillbeproblems.Rememberthatalllambdadoesistocapturethecurrentenvironmentalongwithformalargumentsandfunctionbody.Itdoesnotimmediatelyevaluateanythinginthatcapturedenvironment.Forthatreasonrealletrecimplementationsmaytypicallyonlyallowlambdaexpressionsasvalues.PSchemedoesn'tbothermakingthatcheck2.6.1.1AssignmentToimplementletrecthen,we rstneedtoaddamethodtotheenvironmentclassPScm::Envthatwillallowassignmenttoexistingbindings.Hereisthatmethod.059subAssignf060my($self,$symbol,$value)=@_;061062if(defined(my$ref=$self->_lookup_ref($symbol)))f063$$ref=$value;064gelsef065die"nobindingfor@f[$symbol->value]g",066"in@f[ref($self)]g ";067g068gAssign()usesahelperfunctionlookupref()toactuallydothesymbollookup.Iflookupref() ndsabinding,thenAssign()putsthenewvalueinplacethroughthereferencethatlookupref()returns.Itisanerrorifthere'snotcurrentlysuchasymbolintheenvironment.Thismakessensebecauseitkeepsthedistinctionbetweenvariablebindingandassignmentclear:variablebindingcreatesanewbinding;assignmentchangesanexistingone.lookupref()issimpleenough,itdoesprettymuchwhatLookUp()does,exceptitreturnsarefer-encetowhatit nds,andreturnsundef,ratherthandie()-ingifitdoesn't ndavalue:2Onepossiblewaytodetectthistypeoferrorwouldbetobinddummyobjectstothesymbols.ThesedummyobjectswouldhaveanEval()methodthatwoulddie()withaninformativeerrormessageifitwasevercalled. 76CHAPTER6.RECURSIONANDLETREC070sub_lookup_reff071my($self,$symbol)=@_;072073if(exists($self->fbindingsgf$symbol->valueg))f074return$self->fbindingsgf$symbol->valueg;075gelsif($self->fparentg)f076return$self->fparentg->_lookup_ref($symbol);077gelsef078returnundef;079g080gIncidentally,LookUp()itselfhasbeenmodi edandsimpli edtomakeuseofit.048subLookUpf049my($self,$symbol)=@_;050051if(defined(my$ref=$self->_lookup_ref($symbol)))f052return$$ref;053gelsef054die"nobindingfor@f[$symbol->value]g",055"in@f[ref($self)]g ";056g057g6.1.2PSCM::LetRecitselfAllthatremainstobedoneistoaddaLetRecsubclassofPScm::SpecialFormwithanApply()methodthatimplementsthealgorithmwe'vediscussed,thenaddabindingintheinitialenvironmentfromletrec"toaninstanceofthatclass.WecansimplifythingsabitbysubclassingPScm::SpecialForm::LetinsteadofPScm::SpecialForm,andfactoringcommoncodeoutofPScm::SpecialForm::Let'sApply()methodin-toanewUnPack()methodinthatclass.So rsthere'sthenewversionofPScm::SpecialForm::Let::Apply()012subApplyf013my($self,$form,$env)=@_;014015my($ra_symbols,$ra_values,$body)=$self->UnPack($form,$env);016017return$body->Eval($env->Extend($ra_symbols,$ra_values));018gThecommoncodeinPScm::SpecialForm::Let::UnPack()justunpacksthesymbols,bindingsandbodyfromtheargument$formandreturnsthem: 6.1.LETREC77020subUnPackf021my($self,$form,$env)=@_;022023my($bindings,$body)=$form->value;024my(@symbols,@values);025026foreachmy$binding($bindings->value)f027my($symbol,$value)=$binding->value;028push@symbols,$symbol;029push@values,$value;030g031032return(@symbols,@values,$body);033gNow,ournewApply()inPScm::SpecialForm::LetRecmakesuseofthatsameUnPack()method(byinheritingfromPScm::SpecialForm::Let).Itdi ersfromtheoriginalApply()onlyinthatitcallstheenvironment'sExtendRecursively()method,ratherthanExtend().036packagePScm::SpecialForm::LetRec;037038usebaseqw(PScm::SpecialForm::Let);039040subApplyf041my($self,$form,$env)=@_;042043my($ra_symbols,$ra_values,$body)=$self->UnPack($form,$env);044045return$body->Eval(046$env->ExtendRecursively($ra_symbols,$ra_values));047gSoweneedtotakealookatthatExtendRecursively()methodinPScm::Env.031subExtendRecursivelyf032my($self,$ra_symbols,$ra_values)=@_;033034my$newenv=$self->ExtendUnevaluated($ra_symbols,$ra_values);035$newenv->_eval_values();036return$newenv;037gItcreatesanewenvironmentbyextendingthecurrentenvironment,$selfwiththesymbolsboundtotheirunevaluatedvalues.Thenitcallsanew,privatemethodevalvalues()onthenewenvironment.Here'sthatmethod: 78CHAPTER6.RECURSIONANDLETREC039sub_eval_valuesf040my($self)=@_;041mapf042$self->fbindingsgf$_g=043$self->fbindingsgf$_g->Eval($self)044g045keys%f$self->fbindingsgg;046gAllthatdoesistoloopoverallofitsbindings,replacingtheunevaluatedexpressionwiththeresultofevaluatingtheexpressioninthecurrentenvironment.Sinceallofthoseexpressionsareexpectedtobelambdaexpressions,theresultingclosurescapturetheenvironmentthattheyarethemselvesboundin.QED.Acarefulreadermayhaverealisedthatavalidalternativeimplementationofletrecwouldjustcreateanemptyenvironmentextension,thenpopulatetheenvironmentafterwardswithanalternativeversionofAssign()whichdidnotrequirethesymbolstopre-existintheenvironment.ThemainreasonitisnotdonethatwayisthatthecurrentbehaviourofAssign()ismoreappropriateforlaterextensionstotheinterpreter.Justtobecomplete,here'sthenewversionofPScm::ReadEvalPrint()withthebindingforletrec.031subReadEvalPrintf032my($infh,$outfh)=@_;033034$outfh||=newFileHandle(">-");035my$reader=newPScm::Read($infh);036while(defined(my$expr=$reader->Read))f037my$result=$expr->Eval(038newPScm::Env(039let=>newPScm::SpecialForm::Let(),040'*'=>newPScm::Primitive::Multiply(),041'-'=>newPScm::Primitive::Subtract(),042if=>newPScm::SpecialForm::If(),043lambda=>newPScm::SpecialForm::Lambda(),044letrec=>newPScm::SpecialForm::LetRec(),045)046);047$result->Print($outfh);048g049g6.2SummaryLetevaluatesthevaluesofitsbindingsintheenclosingenvironment.Thenitcreatesanextendedenvironmentwitheachsymbolboundtoitsvalue,inwhichtoevaluatethebodyoftheletexpression.Thismeansthatrecursivelambdaexpressionsde nedbyletwon'twork,becausethere'snotyetabindingfortherecursivefunctionwhenthelambdaexpressionisevaluatedtocreatetheclosure.In 6.3.TESTS79ordertogetrecursiontowork,weneededtocreateavariantoflet,calledletrec(letrecursive)whichsetsupadummyenvironmentwithstubvaluesforthesymbolsinwhichtoevaluatethelambdaexpressions,sothatthelambdaexpressionscouldcapturethatenvironmentintheirresultingclosures.Havingevaluatedthoseexpressions,letrecassignstheirvaluestotheexistingbindingsinthenewenvironment,replacingthedummyvalues.Thuswhentheclosureexecuteslater,theenvironmentithascaptured,andwhichitwillextendwithitsformalargumentsboundtoactualvalues,willcontainareferencetotheclosureitself,soarecursivecallissuccessful.6.3TestsThetestsfortheletrecformareint/PScmLetrec.twhichyoucanseeinListing6.4.1onthenextpage.Therearethreetests.The rsttwo,justprovewhatwealreadyknow,thatletdoesnot(andshouldnot)supportrecursion.Theothernewtestreplacesletwithletrecandprovesthatletrecontheotherhanddoessupportrecursivefunctionde nitions. 80CHAPTER6.RECURSIONANDLETREC6.4Listings6.4.1t/PScmLetrec.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>4;006007BEGINfuseok('PScm')g008009ok(010!defined(evalf011evaluate(<(let((a5)>(b(*a2))>(c(-b3)))>c)Error:nobindingforainPScm::EnvItdidn'twork.Theerroroccurswhenattemptingtoevaluate(*a2).Why?Wellinmuchthesamewayasletfailsforrecursivede nitions:becauseletbindsitsargumentsinparallel,atthepointthatitistryingtoevaluate(*a2),itisstilldoingsointheenvironmentpriortobindinga.letreccan'thelphere,becauseitsetsupanenvironmentwithdummyvaluestoevaluateitsvaluesin,whichisokifthosevaluesareclosuresthatjustcapturethatenvironmentforlater,butverybadifthey'reactuallygoingtotrytodoanyimmediateevaluationswiththosedummyvalues.7.1SequentialBindingOfcoursethesolutionisquitesimpleevenusingourexistingletform:wejustnesttheletexpressionssothateachvalueexpressiongetsevaluatedinanenvironmentwherethepreviousvalueisalreadyboundtoitssymbol:>(let((a5))>(let((b(*a2)))>(let((c(-b3)))>c)))7ThatwouldgiveusasetofenvironmentsasinFigure7.1onthefollowingpage.Theouterletbindsato5tocreateEnv2.Thenextletevaluates(*a2)inthecontextofEnv2andcreatesEnv3withabindingofbtotheresult10.Theinnermostletevaluates(-b3)inEnv3andbindsctotheresult,creatingEnv4.InEnv4the nalevaluationofcresultsin7,whichistheresultoftheexpression.81 82CHAPTER7.ANOTHERVARIATIONONLETFigure7.1:NestedenvironmentscEnv4c7creates(let((c(-b3)))...Env3b10creates(let((b(*a2)))...Env2a5creates(let((a5))...Env1Whilethatworks ne,it'sratheruglyandverbosecode.Wouldn'titbebetteriftherewasavariantofletthatdidallthatforus,bindingitsvariablessequentially?Thisvariantofletiscalledlet*(let-star)andisfoundinmostlispimplementations.7.2let*Toimplementlet*,inthesamewayasweimplementedletrec,wecreateanewsub-classofPScm::SpecialForm::LetandgiveitanApply()method,thenbindasymbol(let*)toaninstanceofthatclassintheinitialenvironment.OurnewclasswillbecalledPScm::SpecialForm::LetStarandhere'sthatApply()method.050packagePScm::SpecialForm::LetStar;051052usebaseqw(PScm::SpecialForm::Let);053054subApplyf055my($self,$form,$env)=@_;056057my($ra_symbols,$ra_values,$body)=$self->UnPack($form);058059return$body->Eval(060$env->ExtendIteratively($ra_symbols,$ra_values));061g 7.3.SUMMARY83Againitonlydi ersfromtheletandletrecimplementationsofApply()inthewayitextendstheenvironmentitpassestotheEval()ofitsbody.InthiscaseitcallsthenewPScm::EnvmethodExtendIteratively().039subExtendIterativelyf040my($self,$ra_symbols,$ra_values)=@_;041042my@symbols=@$ra_symbols;043my@values=@$ra_values;044my$newenv=$self;045046while(@symbols)f047my$symbol=shift@symbols;048my$value=shift@values;049$newenv=$newenv->Extend([$symbol],[$value]);050g051052return$newenv;053gThismethodimplementsthealgorithmwediscussedearlier,creatinganewenvironmentframeforeachindividualbinding,andevaluatingthevaluepartinthecontextofthepreviousenvironmentframe.Thelastenvironmentframe,theheadofthelistofframesrootedintheoriginalenvironment,isreturnedbythemethod1.7.3SummaryThismayallseemabitacademic,butlet'srememberthatPerlsupportsbothtypesofvariablebinding,letandlet*,inthefollowingway.Parallelassignmentlikeletisdoneinalistcontext:my($a2,$b2)=($a*$a,$b*$b);Sequentialbindinglikelet*isdonebysequentialassignment:my$a=5;my$b=$a*2;my$c=$b-3;lethasitsuses,justasassignmentinalistcontextdoes.Forinstancewithparallelassignmentitbecomespossibletoswapthevaluesofvariableswithoutneedinganadditionaltemporaryvariable.InPerl:($b,$a)=($a,$b);...andinPScheme:1Analternativeimplementationwouldbetoonlycreateonenewenvironmentframe,theniterativelyevaluateandbindeachvalueinturn,inthecontextofthatnewenvironment. 84CHAPTER7.ANOTHERVARIATIONONLET(let((ab)(ba))...)Again,justforcompleteness,here'sour0.0.4versionofReadEvalPrint()withtheadditionallet*binding.031subReadEvalPrintf032my($infh,$outfh)=@_;033034$outfh||=newFileHandle(">-");035my$reader=newPScm::Read($infh);036while(defined(my$expr=$reader->Read))f037my$result=$expr->Eval(038newPScm::Env(039let=>newPScm::SpecialForm::Let(),040'*'=>newPScm::Primitive::Multiply(),041'-'=>newPScm::Primitive::Subtract(),042if=>newPScm::SpecialForm::If(),043lambda=>newPScm::SpecialForm::Lambda(),044letrec=>newPScm::SpecialForm::LetRec(),045'let*'=>newPScm::SpecialForm::LetStar(),046)047);048$result->Print($outfh);049g050g7.4TestsTheadditionaltestsfor0.0.4areint/PScmLetStar.twhichyoucanseeinListing7.5.1onthenextpage.The rsttestprovesthatordinaryletbindsinparallel,bydoingthevariableswappingtrick.Thesecondtestdemonstrateslet*bindingsequentiallysincetheinnermostbindingofbtoaseestheimmediatelypreviousbindingofatotheouterb. 7.5.LISTINGS857.5Listings7.5.1t/PScmLetStar.t001usestrict;002usewarnings;003useTest::More;004uselib't/lib';005usePScm::Testtests=>3;006007BEGINfuseok('PScm')g008009evalok(<(quotea)a>(quote(+12))(+12)Theimplementationisrathertrivial:Quoteisusedtoturno evaluation.Sincespecialformsdon'thavetheirargumentsevaluatedforthem,allthattheApply()methodinPScm::SpecialForm::Quoteneeddoistoreturnits rstargument,stillunevaluated.Here'sPScm::SpecialForm::Quote.106packagePScm::SpecialForm::Quote;107108usebaseqw(PScm::SpecialForm);109110subApplyf111my($self,$form,$env)=@_;112return$form->first;113g1141151;87 88CHAPTER8.LISTPROCESSING8.2listAnotherusefuloperationiscalledlist.Ittakesalistofargumentsandconstructsanewlistfromthem.ItisjustaPScm::Primitiveandsoitsargumentsareevaluated:>(list(-81)"hello")(7"hello")ItdoesnothingitselfbutreturnthelistofitsevaluatedargumentstothecallerasanewPScm::Expr::List,soit'salsotrivialtoimplement.Torecap,allPScm::PrimitiveclassesshareacommonApply()methodthatevaluatestheargumentsthencallstheclass-speci capply()method.SoallwehavetodoistosubclassPScm::PrimitivetoPScm::Primitive::Listandgivethatnewsubclassanappropriateapply()method.066packagePScm::Primitive::List;067068usebaseqw(PScm::Primitive);069070sub_applyf071my($self,@args)=@_;072073returnnewPScm::Expr::List(@args);074gAshasbeensaid,it'strivialbecauseitjustreturnsitsargumentsasanewPScm::Expr::List.8.3carandcdrSowecancreatelists,butwhatcanwedowiththem?Wealreadyhavetwoprimitiveinternaloperationsonlists,namelythePScm::Expr::List::first()andrest()methods.Theyaresomethinglikethecomplementofthelistprimitiveinthattheytakeapartalistintoitscomponents.Bowingtohistoricalprecedenthowever,Scheme,andhencePScheme,doesn'tcallthem rst"andrest",insteadtheyarecalledcarandcdr1.Againtheirimplementationissimple,weaddanewsubclassofPScm::Primitiveforeachofthem,andgiveeachnewclassanapply()methodthatcallstherelevantinternalmethod.Here'sPScm::Primitive::Car.077packagePScm::Primitive::Car;078079usebaseqw(PScm::Primitive);080081sub_applyf082my($self,$arg)=@_;0831Obligatoryfootnote.CARstandsfortheContentsoftheAddresspartoftheRegister"andCDRstandsfortheContentsoftheDecrementpartoftheRegister."Thisisareferencetotheoriginalhardwareonwhichthe rstLispsystemwasimplemented,andmeansnothingnowbutthenameshavestuck. 8.4.CONS89084$self->_check_type($arg,'PScm::Expr::List');085return$arg->first;086gItuseschecktype()toverifythatitsargumentisalist,thencallsitsfirst()method,returningtheresult.Here'stheequivalentPScm::Primitive::Cdrclass.089packagePScm::Primitive::Cdr;090091usebaseqw(PScm::Primitive);092093sub_applyf094my($self,$arg)=@_;095096$self->_check_type($arg,'PScm::Expr::List');097return$arg->rest;098g8.4consWe'reonlymissingonepiecefromoursetofbasiclistoperationsnow,butbeforeaddingthatitisnecessarytoexplainandrectifyasigni cantdeviationthatPSchemehassofarmadefromotherLispandSchemeimplementations.InourPSchemeimplementationlistshavebeenimplementedasobjectwrappersaroundPerllists.ThishadtheadvantagethatthePerlimplementationwasassimpleasitcouldbe.HoweverrealLispsystemsimplementlistsaschainsofwhatarecalledconscells,ormorecommonlypairs.Aconscell,isastructurewithtwocomponents,bothreferencestootherdatatypes.Foratruelist,the rstcomponentpointsatthecurrentlistelementandthesecondcomponentpointsattherestofthelist.The rstcomponentiscalledthecarandthesecondcomponentthecdr,hencetheeponymousfunctionsthataccessthosecomponents.Soforexamplethelispexpression(foo("bar"10)baz)IsnotproperlyimplementedasinFigure3.1onpage17,butasshowninFigure8.1onthefollowingpage.Theun lledcdrpointersinthe gurerepresentnullpointersandterminatetheliststructure.ThismeansthatatrueLisplistisinfactalinkedlist.Aprimaryadvantageofthisisthattheinternalfirst()andrest()(carandcdr)operationsareequallyecient:thereisnoneedforrest()toconstructanewlistobject,itjustreturnsit'srestcomponent.Asecondadvantageisthatconscellsaremore exiblethanlists.Atruelistisachainofconscellslinkedbytheircdrcomponent,endinginaconscellwithanemptycdr.Ingeneralthecdrneednotpointtoanotherconscell,itcouldequallywellpointtoanyotherdatatype.Aconscellisconstructedwiththeprimitiveoperatorcons.8.4.1DotNotationSincewecannowbuildstructuresthatcannotberepresentedinsimple(abc)"listnotation,weneedtoextendthatnotationtocope.Theextensionisthankfullyverysimple:ifthelastcomponentofalistisprependedbyaperiod(separatedbyaspace)forexample(a.b)itistakentobethecdrofthelist.Thisiscalleddotnotation.SeeFigure8.2onthenextpage.Dotnotationwillbesupportedforbothinputandoutput. 90CHAPTER8.LISTPROCESSINGFigure8.1:ConsCellRepresentationofanestedlist(foo("bar"10)baz)CellCellcdrCellcdrcarcarCellCellcdrcarcarcarSymbolStringfooNumber"bar"Symbol10bazFigure8.2:Thepair(a.b)CellSymbolcdrbcarSymbolaDotnotationisnotlimitedtojustdottedpairs,forexampleinFigure8.3onthefacingpageyoucanseethatmorecomplexstructurescanalsoberepresented.Dotnotationisactuallycapableofrepresentinganystructurewecanenvisage2.Infactitisreasonabletothinkofthenormallistnotationwehavebeenusingsofarasmerelyaconvenientshorthandfordotnotation.Forexamplethelist(a)isthesameasthepair(a.())(because()istheemptylist.)Likewisethelist(abc)canberepresentedas(a.(b.(c.()))).Obviouslythisunwieldynotationistobeavoidedunlessnecessary,butyoushouldatleastbeawareofit.Dotnotationhasanumberofuses.Mostimportantlyitallowsustoeasilyspecifyvariablenumbersofargumentstoourlambdaexpressions:iftheformalargumentsinalambdaexpressionaredotted,then2Well,actuallyitispossibletoimaginecircularstructuresthatwoulddefeatanynotation.AtrueSchemewillevenallowthecreationofsuchcircularlists,withsuchdubiousexpressionsas(set-cdr!xx),butthat'saworldofpainthatwewillstaywellawayfrom. 8.5.IMPLEMENTATION91Figure8.3:Thestructure(ab.c)CellCellcdrSymbolcdrcarccarSymbolSymbolabthatcanbetakentomeanthedottedsymbolistobeboundtoalistoftheremainingactualarguments.ForExample:>(let((test>(lambda(ab.c)>(listabc))))>(test12345))(12(345))Theaandbareboundto1and2asalways,butthec,becauseitoccupiestheentirityoftherestoftheformalargumentlistgetsboundtotheremaininglistofadditionalarguments.Interestinglythisalsoallowsentirelyarbitrarylistsofarguments.Ifyouthinkaboutitthedottedpairnotation(.a)canbemadeperfectlylegalforinput,andisequivalenttothesymbola:theopeningbraceimpliesalist,butthe rstsymbolencounteredisthecdrofalistthathasnotstartedtoformyet,sotheresultisjustthatsymbol.Sincewemustacceptlambdaexpressionswithsuchanargumentdeclaration,wemustalsoacceptlambdaexpressionswithasinglesymbolinsteadofalistofarguments.Forexamplewecouldde neourlistprimitiveinPSchemelike:>(let((list(lambdaargsargs)))>(list12345))(12345)listtakesanynumberofargumentsasasinglelistargsandreturnsthem.8.5ImplementationLet'smakethechangetousethatalternativeimplementation.SincethePScm::Expr::Listclasshidesitsinternalstructureandprovidesaccessormethods,technicallythatshouldbetheonlypackagethatneedstochange.HoweveritisworthwhilemakinguseofthenewliststructureinotherpartsofthePSchemesystem.Thelanguageishighlyrecursive,andthisnewlinkedliststructurelendsitselftorecursionmuchmorenaturallythanaplainperl@listdoes. 92CHAPTER8.LISTPROCESSING8.5.1ChangestoExpressionsTogetusstarted,here'sthenewimplementationofPScm::Expr::List:059packagePScm::Expr::List;060usebaseqw(PScm::Expr);061062subnewf063my($class,@list)=@_;064065$class=ref($class)||$class;066if(@list)f067my$first=shift@list;068$class->Cons($first,$class->new(@list));069gelsef070newPScm::Expr::List::Null();071g072g073074subConsf075my($class,$first,$rest)=@_;076returnPScm::Expr::List::Pair->new($first,$rest);077g078079subas_stringf080my($self)=@_;081return'('.join('',$self->strings).')';082gThenew()methodonLines62-72isalittlemorecomplicatedthanitwas,becauseithastorecurseonitsargumentlistbuildingalinkedlist.IfthelistisnotemptythenonLine68itcallsanancilliarymethodCons()(de nedonLines74-77)toactuallyconstructanewPScm::Expr::List::Pairnode(aconscell).SothePScm::Expr::Listclassisnowinfactabstract.Althoughithasanew()method,thatmethodactuallyreturnsinstancesofeitherPScm::Expr::List::Pairoranothernewobject,PScm::Expr::List::Null,whichrepresentstheemptylist.ifyouremembertheoldimplementationofnew()justwrappeditsargumentlist:036subnewf037my($class,@list)=@_;038039$class=ref($class)||$class;040bless[@list],$class;041gTheasstring()methodofPScm::Expr::Listhaschangedtoo.Ratherthanjustmappingas-string()overthecomponentsofthelist,itcallsaseparatestrings()methodthatwillreturnanarrayofstrings,andjoinsandwrapsthat.Themainreasonforthatistocopewithdottedpairnotation.We'llseehowstrings()workssoon. 8.5.IMPLEMENTATION93MuchofthefunctionalitythatwasinPScm::Expr::ListhasbeenmovedoutintoPScm::Expr::List::Pair,shownnext:085packagePScm::Expr::List::Pair;086usebaseqw(PScm::Expr::List);087088useconstantf089FIRST=>0,090REST=>1,091g;092093subnewf094my($class,$first,$rest)=@_;095bless[$first,$rest],$class;096g097098subvaluef099my($self)=@_;100my@value=($self->[FIRST],$self->[REST]->value);101return@value;102g103104subfirstf$_[0][FIRST]g105106subrestf$_[0][REST]g107108substringsf109my($self)=@_;110return($self->[FIRST]->as_string,111$self->[REST]->strings);112g113114subEvalf115my($self,$env)=@_;116my$op=$self->[FIRST]->Eval($env);117return$op->Apply($self->[REST],$env);118g119120submap_evalf121my($self,$env)=@_;122return$self->Cons($self->[FIRST]->Eval($env),123$self->[REST]->map_eval($env));124g125126subis_pairf1g 94CHAPTER8.LISTPROCESSINGAsaminoroptimization,PScm::Expr::List::Pairwillstoreitsfirstandrestcomponentsinanarrayrefratherthanahash.ForthatreasonitdeclarestwoconstantsFIRSTandRESTtoactasindexesintothatstructure.PScm::Expr::List::Pairhasitsownnew()methodwhichwe'vealreadyseenbeingcalledbyCons().Itjustcollectsitstwoargumentsintoanewobject.TheothermethodsinPScm::Expr::List::Pairarefairlystraightforward.Thevalue()methodonLines98-102convertsthelinkedlistbackintoaPerllist3.Becausethestructureisnolongernecessarilyatruelistwemustsupplyanalternativevalue()methodinPScm::Exprwhichjustreturns$self(ignoringthedotnotation).Thereisanothervalue()methodinPScm::Expr::List::Nullthatreturnstheempty(Perl)listandlikewiseterminatestherecursionofPScm::Expr::List::value().Thefirst()andrest()methodsaresimpli ed,theyarenowjustaccessorstotheirequivalent elds.Asmentionedabove,theasstring()methodfromPScm::Expr::Listhastodealwithdotnotation,socannotsimplymapanasstring()overthelist'svalue().Insteaditcallsahelpermethodstrings().strings(),onLines108-112,returnsaperllistofthestringrepresentationofthe rstitemonthelist,plustheresultofcallingitselfontherestofthelist.Thereisanimplementationofstrings()inPScm::Expr::List::Nullthatjustreturnstheempty(Perl)list:141substringsf();gandanotherattherootoftheheirarchyinPScm::Exprwhichcatchesthesituationwhereatypeotherthanalistornullisthecdrofalist:017substringsf018my($self)=@_;019return('.',$self->as_string);020gItreturnsalistofthestring'.'plustheresultofcallingasstring()onitself.Sinceitknowsitmustbeterminatingalist,itneednotrecurse.BacktoPScm::Expr::List::Pair.TheEval()methodonLines114-118isfunctionallyunchangedfromthepreviousimplementation,butitdirectlyaccessestheFIRSTandREST eldsratherthanusingthefirst()andrest()methodcallsforaslightperformanceimprovement.3Thereasonforthetemporary@valuevariableisnotredundantclari cationofthecode,itisaworkaroundforaratherobscurepieceofPerlbehaviour.Ifthecodehadsimplysaid:return($self->[FIRST],$self->[REST]->value);thenthescalarcontextimposedbytheisTrue()methodinPScm::ExprwouldcausePerltotreatthecommaasthecommaoperator,throwingawaythelefthandsideandreturningonlytheright,recursively,soalllistswouldendupbeingtreatedasfalse. 8.5.IMPLEMENTATION95Anewmapeval()methodonLines120-124willcomeinveryhandylater.Ittakesanenvironmentasargumentandbuildsacopyofitselfwitheachcomponentreplacedbytheresultofevaluatingthatcomponentintheargumentenvironment.Because,likestrings()itmustdealwiththepossibilitythatthestructureisnotatruelist,anadditionalmapeval()methodisprovidedinthebasePScm::ExprclassthatjustreturnstheresultofcallingEval()on$self.Finally,anidentifyingispair()methodisde nedtobetrueinthisclassonly.Itisde nedfalsebydefaultinPScm::Expr.AnotherpartofouralternativeimplementationoflistsisthatnewPScm::Expr::List::Nullclass.ItrepresentsthePSchemeemptylist,andalsoquitereasonablydescendsfromthelistclass.Itprovidesasimplenew()methodwithnoarguments,andoverridesthevalue()andstrings()methodstojustreturnanemptyperllist.129packagePScm::Expr::List::Null;130usebaseqw(PScm::Expr::List);131132subnewf133my($class)=@_;134135$class=ref($class)||$class;136blessfg,$class;137g138139subvaluef();g140141substringsf();g142143subfirstf$_[0]g144145subrestf$_[0]g146147subis_nullf1gInterestingly,italsooverridesfirst()andrest()toreturn$self,sothecarorcdroftheemptylististheemptylist,anditoverridesEval()tojustreturn$selftoo,soanemptylistevaluatestoitself4.Backtoourconsfunction.Schemeimplementationshaveaconsoperationthattakestwoargumentsandcreatesaconscellwithitscarreferencingthe rstargument,anditscdrreferencingthesecond.>(cons10(list2030))(102030)Thusthecarandcdroperationsaretheprecisecomplementofcons:consconstructsacell,andcarandcdrtakethecellapart.4Thisinterestingtrickofhavinganobjecttorepresenttheabsenceofanotherobjectisawell-knowndesignpatterncalledtheNullObjectPattern. 96CHAPTER8.LISTPROCESSING>(car(cons10(list2030)))10>(cdr(cons10(list2030)))(2030)Providedthesecondargumenttoconsisalist,theresultwillalsobealist,butthereisnorequirementforthesecondargumenttobealist.Thatmakesconsasecondwaytocreatedottedpairs,otherthaninputtingthemdirectly:>(quote(a.b))(a.b)>(cons(quotea)(quoteb))(a.b)>(cons(quotea)(quote(b)))(ab)consisimplementedinthenormalway,bysubclassingPScm::Primitiveandgivingthenewclass,PScm::Primitive::Consinthiscase,anapply()method.Here'sthatmethod.101packagePScm::Primitive::Cons;102103usebaseqw(PScm::Primitive);104105sub_applyf106my($self,$car,$cdr)=@_;107108returnPScm::Expr::List->Cons($car,$cdr);109g1101111;ItcanbeseenthatallitdoesistocallPScm::Expr::List'sCons()method.8.5.2ChangestoPrimitivesandSpecialFormsAsI'vealreadysaid,ItwillpaytomakeuseofthisnewliststructurewhereverpossiblethroughoutthePSchemeimplementation.The rstplaceweshalldosoisinPScm::Primitive::Apply(),whichcannowmakeuseofthatnewmapeval()method:007subApplyf008my($self,$form,$env)=@_;009010my@evaluated_args=$form->map_eval($env)->value();011return$self->_apply(@evaluated_args);012gThequestionarisesastowhyI'mthenjustcallingvalue()ontheresultandpassinganordinaryperllisttotheindividualprimitives,afterIjustsaidthatitwasworthwhilepassingaroundthenewlinked 8.5.IMPLEMENTATION97liststructures.It'sjustapersonalchoice,butIfeelthattheindividualprimitivesshouldpresentanabstractionlayer"suchthatanythingbelowthatlayerispurePerlanddoesnotdependonthedetailsofthePSchemeimplementationabovethatlayer.Anyway,that'showIseeit.Specialforms,ontheotherhand,areverymuchpartofthePSchemeimplementationanddomakefulluseofthenewlinkedlists.FirstupisPScm::SpecialForm::Let.IfyourememberletandfriendsmakeuseofasharedUn-Pack()methodwhichusedtoreturnalistoftworeferencestoperllists,oneforthesymbolsandoneforthevalues,plusthebodyoftheletexpression.NowitwillreturnPSchemelistsinstead,andthoselistswillbepassedtothevariousExtend*()methodsintheenvironmentimplementation,whichwillalsohavetochange.anywayhere'sthenewPScm::SpecialForm::Let::Apply():012subApplyf013my($self,$form,$env)=@_;014015my($symbols,$values,$body)=$self->UnPack($form);016017return$body->Eval($env->Extend($symbols,$values));018gOnlythevariablenameshavechanged:$symbolsand$valuesusedtobecalled$rasymbolsand$ravaluestoindicatethattheywerereferencestoarrays.Thisisnolongerthecase.TheUnPack()stilljustcallsvalue()onthe$formtogetthebindingsandthebody,butnowitmakesuseofanadditionalunpackbindings()methodtobuildthenewPSchemelists:020subUnPackf021my($self,$form)=@_;022023my($bindings,$body)=$form->value;024my($symbols,$values)=$self->unpack_bindings($bindings);025return($symbols,$values,$body);026gunpackbindings()itselfiswheretherealworkgetsdone:028subunpack_bindingsf029my($self,$bindings)=@_;030if($bindings->is_null)f031my$null=newPScm::Expr::List::Null();032return($null,$null);033gelsef034my($symbols,$values)=035$self->unpack_bindings($bindings->rest);036return(037PScm::Expr::List->Cons($bindings->first->first,038$symbols),039PScm::Expr::List->Cons($bindings->first->rest->first, 98CHAPTER8.LISTPROCESSING040$values)041);042g043gIfithasreachedtheendofthebindingsitcreatesanewnullobjectandreturnstwoofit.otherwiseitcallsitselfontherestofthebindings,collectstheresults,thenprependsthesymbolfromthecurrentbindingontothe rstlistandthevaluefromthecurrentbindingontothesecondlist.Finallyitreturnsthosetwonewlists.Essentiallyitrecursestotheendofthebindingsthenbuildsapairoflistsonthewaybackup.Thisguaranteesthatthesymbolsandvaluesareinthesameorderintheresultsastheywereinthebindings.GiventhenewUnPack()method,theApply()methodsforPScm::SpecialForm::LetRec:050subApplyf051my($self,$form,$env)=@_;052053my($symbols,$values,$body)=$self->UnPack($form);054055return$body->Eval(056$env->ExtendRecursively($symbols,$values));057gandPScm::SpecialForm::LetStar:064subApplyf065my($self,$form,$env)=@_;066067my($symbols,$values,$body)=$self->UnPack($form);068069return$body->Eval(070$env->ExtendIteratively($symbols,$values));071garesimilarilyunchangedexceptforvariablerenaming.I'vetakentheopportunitytomakeasmallbutsigni cantchangetoPScm::SpecialForm::If:078subApplyf079my($self,$form,$env)=@_;080081my$condition=$form->first;082my$true_branch=$form->rest->first;083my$false_branch=$form->rest->rest->first;084085if($condition->Eval($env)->isTrue)f086return$true_branch->Eval($env);087gelsef088return$false_branch->Eval($env);089g090g 8.5.IMPLEMENTATION99Becauseitextractsthecondition,trueandfalsebranchesfromthe$formbyusingcombinationsoffirst()andrest()ratherthanjustusingvalue(),andbecausethefirst()andrest()oftheemptylististheemptylist,andbecausetheemptylistevaluatestoitself,ournewifnolongerrequiresathirdargument.Ifthetestfailsandafalsebranchisnotsuppliedtheresultwillbetheemptylist.PScm::SpecialForm::Lambda::Apply()isunchanged,buttheclosurethatitconstructswillmakeuseofthenewlistswhenitinteractswiththechangedenvironmentimplementation.TheonlyremainingspecialformisthenewPScm::SpecialForm::Quote,butwe'veseenthatalready.8.5.3ChangestoClosuresInfactthereisverylittlechangeinthispackage.Thedi erencesarethatthespeci cPScm::Closure::Function::Apply()method(whichappliesalambdaclosuretoitsarguments)nowusesmapeval()toconstructaPSchemelistofevaluatedargumentsandpassthattothesharedapply()ratherthanaperllist:039subApplyf040my($self,$form,$env)=@_;041042my$evaluated_args=$form->map_eval($env);043return$self->_apply($evaluated_args);044gThesharedapply()methodrecievesaPSchemelistofactualargumentsratherthanareferencetoanarrayanditpassesthat,plusitsPSchemelistofformalargumentsdirectlytotheenvironment'sExtendUnevaluated()method:017sub_applyf018my($self,$args)=@_;019020my$extended_env=021$self->fenvg->ExtendUnevaluated($self->fargsg,$args);022return$self->fbodyg->Eval($extended_env);023g8.5.4ChangestotheEnvironmentThevariousExtend*()methodsofPScm::EnvnowtakePScm::Expr::Listobjectsasargumentsratherthanperllistrefs.Thismakesthemtherightplacetoimplementthenewvariableargumentsfunctionalityoflambda.FirstofallExtend()itself:014subExtendf015my($self,$symbols,$values)=@_;016017return$self->ExtendUnevaluated($symbols,018$values->map_eval($self));019g 100CHAPTER8.LISTPROCESSINGVariablenameshavechangedtore ectthefactthattheyarenolongerreferencestoarrays,andExtend()usesmapeval()toevaluatethelistofvaluesbeforepassingthemtoExtendUnevaluated().ExtendUnevaluated()issimilarilychanged:021subExtendUnevaluatedf022my($self,$symbols,$values)=@_;023024my%bindings;025$self->_populate_bindings(%bindings,$symbols,$values);026my$newenv=$self->new(%bindings);027$newenv->fparentg=$self;028return$newenv;029gItusesanewprivatepopulatebindings()methodtopopulateahashofbindingsfromthe$symbolsand$valueslists.Afterthatitdoeswhatitalwaysdid,creatinganewPScm::Envandsettingitsparent eldto$selfbeforereturningit.Theprivatepopulatebindings()methodactuallydoestheparsing"oftheargumentlistofsym-bolsandtheirbindingtoequivalentvalues.031sub_populate_bindingsf032my($self,$bindings,$symbols,$values)=@_;033if($symbols->is_null)f034die"toomanyarguments "unless$values->is_null;035gelsif($symbols->is_pair)f036if($values->is_pair)f037my$symbol=$symbols->first;038if($symbol->is_symbol)f039$bindings->f$symbol->valueg=$values->first;040$self->_populate_bindings($bindings,041$symbols->rest,042$values->rest);043gelsef044die"badformalarguments[1]:",045$symbol->as_string()," ";046g047gelsef048die"notenougharguments ";049g050gelsif($symbols->is_symbol)f051$bindings->f$symbols->valueg=$values;052gelsef053die"badformalarguments[2]:",054$symbols->as_string()," ";055g056g 8.5.IMPLEMENTATION101OnLine33itcheckstoseeifithasreachedtheendofthelistofsymbols.Ifithas,thenitthrowsanerrorifithasnotalsoreachedtheendofthelistofvalues(toomanyarguments).If$symbolsisnotnull,thenonLine35itcheckstoseeifitisapair.Ifitis,thenitgetsthecurrentsymbol,checksthatitisasymbol,bindsittotheequivalentvalue,andrecursesontherestofthetwolists.If$symbolsisnotapair,thenonLine38itcheckstoseeifitisitselfasymbol.Thiswouldcorrespondtoeitherasingleargsinalambdastatementlike(lambdaargs...),oradottedpairterminatingalistofarguments.Ineithercaseif$symbolsisasymbol,itbindsittotheentirityoftherestofthe$valueslistandterminatestherecursion.If$symbolsisnoneoftheemptylist,apairorasymbolthensomethingisseriouslywrongandpopulatebindings()throwsanexception.NextupisExtendRecursively().Itisunchangedexceptthat,asinothercases,itsargumentshavebeenrenamedbecausetheyarenolongerarrayreferences:058subExtendRecursivelyf059my($self,$symbols,$values)=@_;060061my$newenv=$self->ExtendUnevaluated($symbols,$values);062$newenv->_eval_values();063return$newenv;064gFinally,ExtendIteratively()bene tsfromthenewliststoo.Itreturns$selfifthelistofsymbolsisempty,otherwiseitcallsExtend()onthe rstofeachlistthencallsitselfontheextendedresultwiththerestofthelist:066subExtendIterativelyf067my($self,$symbols,$values)=@_;068069if($symbols->is_null)f070return$self;071gelsef072return$self->Extend($symbols->first,$values->first)073->ExtendIteratively($symbols->rest,074$values->rest);075g076gNotethatExtendIteratively()isonlyusedbylet*,andsodoesnotneedtodealwithnon-lists.8.5.5ChangestotheReaderThechangesinthrReadersupporttheinputofdotnotation,andtheymakedirectuseofPScm::Expr::List::Cons()tofacilitatethis.FirstofallRead()itselfhasbeensomewhatsimpli ed:017subReadf018my($self)=@_;019 102CHAPTER8.LISTPROCESSING020my$token=$self->_next_token();021returnundefunlessdefined$token;022023if($token->is_open_token)f024return$self->read_list();025gelsef026return$token;027g028gItmaylookverydi erentbutallthathasreallyhappenedisthattheoldloopingcodewhichcollectsalisthasbeenmovedoutintoaseparatemethod,andthatmethod,readlist(),isnowrecursiveinsteadofiterative:030subread_listf031my($self)=@_;032my$token=$self->read_list_element();033if($token->is_close_token)f034returnnewPScm::Expr::List::Null();035gelsif($token->is_dot_token)f036$token=$self->read_list_element();037die"syntaxerror"unless$token->is_expr;038my$close=$self->read_list_element();039die"syntaxerror"unless$close->is_close_token;040return$token;041gelsef042returnPScm::Expr::List->Cons($token,$self->read_list());043g044greadlist()collectsitscomponentsusinganothernewmethodreadlistelement().OnLine33ifitdetectsaclosingbraceitreturnstheemptylist.Ifthetokenisnotaclosingbrace,thenonLine35ifthetokenisadot,itreadsthenextelement,checksthatitisanexpression,checksthattheelementafterthatisaclosingbrace,andreturnstheelementjustreadastheentirelist.Inthe nalcase,ifthetokenisneitheraclosingbraceoradot,itusesCons()toconstructalistwiththecurrenttokenasthe rstelementandtheresultofcallingreadlist()astherest.Hopefullyyoucanconvinceyourselfthatthiswilldealcorrectlywithbothordinarylistsanddottedpairnotation.readlistelement()justcentralisestheotherwisetediousandrepetitivecheckingforeofwhichisasyntaxerror(unterminatedlist)whilecollectinglistelements:046subread_list_elementf047my($self)=@_;048my$token=$self->Read();049die"unexpectedEOF"050if!defined$token;051return$token;052g 8.6.SUMMARY103Finallynexttoken()hasanextraclausetodetectastandaloneperiod(dot)andifitdoesitreturnsaninstanceofanewtokenclassPScm::Token::Dot:054sub_next_tokenf055my($self)=@_;056057while(!$self->fLineg)f058$self->fLineg=$self->fFileHandleg->getline();059returnundefunlessdefined$self->fLineg;060$self->fLineg=~s/^s+//s;061g062063for($self->fLineg)f064s/^(s*//&&returnPScm::Token::Open->new();065s/^)s*//&&returnPScm::Token::Close->new();066s/^.s*//&&returnPScm::Token::Dot->new();067s/^([-+]?d+)s*//068&&returnPScm::Expr::Number->new($1);069s/^"((?:(?:\.)|([^"]))*)"s*//&&dof070my$string=$1;071$string=~s/\//g;072returnPScm::Expr::String->new($string);073g;074s/^([^s()]+)s*//075&&returnPScm::Expr::Symbol->new($1);076g077die"can'tparse:$self->fLineg";078gThenewclassisinPScm/Token.pmalongsidetheothers:027packagePScm::Token::Dot;028029usebaseqw(PScm::Token);030031subis_dot_tokenf1g0320331;Thereisadefaultisdottoken()methodinPScm::Tokenthatreturnsfalse.8.6SummaryIninterpreter0.0.5 verelatedoperationsforcreatingandmanipulatingliststructureshavebeenadded.We'llputthosetogooduseinthenextversionoftheinterpreterwhenwelookatmacros.Intheprocessofimplementingoneofthoseoperations,cons,thebasiclistimplementationwaschangedtobecloserto 104CHAPTER8.LISTPROCESSINGastandard"schemeimplementation,andoutofthatwewontheabilitytoconstructdottedpairs,andfromthatwegotvariableargumentstolambdaexpressions.Justforthesakeofcompletenesshere'sthechangestoourtop-levelPScm::ReadEvalPrint()method,whereweaddthenewbindingsforthesefunctionsintheinitialenvironment.031subReadEvalPrintf032my($infh,$outfh)=@_;033034$outfh||=newFileHandle(">-");035my$reader=newPScm::Read($infh);036while(defined(my$expr=$reader->Read))f037my$result=$expr->Eval(038newPScm::Env(039let=>newPScm::SpecialForm::Let(),040'*'=>newPScm::Primitive::Multiply(),041'-'=>newPScm::Primitive::Subtract(),042if=>newPScm::SpecialForm::If(),043lambda=>newPScm::SpecialForm::Lambda(),044list=>newPScm::Primitive::List(),045car=>newPScm::Primitive::Car(),046cdr=>newPScm::Primitive::Cdr(),047cons=>newPScm::Primitive::Cons(),048letrec=>newPScm::SpecialForm::LetRec(),049'let*'=>newPScm::SpecialForm::LetStar(),050quote=>newPScm::SpecialForm::Quote(),051)052);053$result->Print($outfh);054g055g8.7TestsTestsforversion0.0.5oftheinterpreterareint/PScmList.tandt/PScmdot.t,whichyoucanseeinListing8.8.1onthenextpageandListing8.8.2onpage106.Thetestsint/PScmList.texerciseournewlistfunctions.The rsttestshowsthatlistevaluatesitsargumentsthenreturnsalistofthem.Thesecondtestprovesthatthecarofthelist(123)isthevalue1.Thethirdtestprovesthatthecdrofthelist(123)isthelist(23).Thefourthtestprovesthat(cons1(list23))is(123).The fthtestprovesthatquoteprotectsitsargumentfromevaluation:(quote(list123))is(list123).Lastly,threetestsverifythatdotnotationoninputandoutputbehavesasexpected.ThetestsinListing8.8.2onpage106verifythenewvariableargumentstolambdaexpressions,muchasourpreviousexamplesdemonstrated. 8.8.LISTINGS1058.8Listings8.8.1t/PScmList.t001usestrict;002usewarnings;003useTest::More;004uselib't/lib';005usePScm::Testtests=>9;006007BEGINfuseok('PScm')g008009evalok(<3;006useFileHandle;007008BEGINfuseok('PScm')g009010evalok(<value;124returnPScm::Closure::Macro->new($args,$body,$env);125gIt'svirtuallyidenticaltoPScm::SpecialForm::LambdaexceptthatitcreatesanewPScm::Closure::MacroinsteadofaPScm::Closure::Function.Sowe'velefttheproblemofhowtomakeamacroactuallyworkuntillast,inthePScm::Closure::Macro'sApply()method.9.2EvaluatingMacrosConsiderhowPScm::Closure::Function::Apply()works.Itevaluatesitsargumentsinthepassed-inenvironmentthengivestheresultstoitsparentPScm::Closure::apply()method.Thatapply()methodextendstheenvironmentthatwascapturedwhentheclosurewascreatedwithbindingsofthoseactualargumentstoitsformalarguments.Thenitevaluatesitsbodyinthatextendedenvironmentandreturnstheresult.HereagainisPScm::Closure::Function'sApply()method:039subApplyf040my($self,$form,$env)=@_;041042my$evaluated_args=$form->map_eval($env);043return$self->_apply($evaluated_args);044gAndhereagainistheprivateapply()methodinthebasePScm::Closureclass: 9.2.EVALUATINGMACROS109017sub_applyf018my($self,$args)=@_;019020my$extended_env=021$self->fenvg->ExtendUnevaluated($self->fargsg,$args);022return$self->fbodyg->Eval($extended_env);023gAnyimplementationofmacroswillsharesomethingincommonwiththisimplementationoffunctions,buttherewillbedi erences.Obviouslyamacroshouldbepasseditsargumentsunevaluated.Thatwayitcanperformwhatever(list)operationsitlikesonthatstructure.Thenwhenitreturnsanewform,itisthatformthatgetsevaluated.Infactit'sassimpleasthat,andhere'stheApply()methodforPScm::Closure::Macro:055subApplyf056my($self,$form,$env)=@_;057058my$new_form=$self->_apply($form);059return$new_form->Eval($env);060gComparethatwiththeApply()methodfromPScm::Closure::Functionabove.Functionsevaluatetheirarguments,thenevaluatetheirbodywiththoseargumentsbound.Macrosdon'tevaluatetheirarguments,theyevaluatetheirbodywiththeirunevaluatedargumentsbound,thentheyre-evaluatetheresult.Thisisquitesubtle.Macrosperformsubstitutionsontheirarguments,buttheresultofthosesubstitutionsmustbesubsequentlyevaluatedforthemacrotohavehadthedesirede ect.To nisho thispartoftheimplementation,wemustrememberthatinSection5.3onpage68wemadeclosuresprintable,andsincemacrosareanewkindofclosure,wemustsupplythesupportingsymbol()methodinPScm::Closure::MacroforthePScm::Closure::asstring()methodto nd.Thissymbol()methodreturnsthesymbolmacrosothatifamacroisprinteditwilldisplayas(macrohargsihbodyi).Here'sthenewPScm::Closure::Macro::symbol()method.062sub_symbolf063PScm::Expr::Symbol->new('macro');064g9.2.1TryingitoutJustforfun,let'stakealookathowwemightattacktheproblemwhichintroducedthissection:imple-mentingletintermsoflambda.Rememberthatanyletexpressionhasanequivalentlambdaform,sohere'sauseofmacrothattranslatesoneintotheother:(let*((mylet(macro(bindingsbody)(let*((names(carsbindings))(values(cadrsbindings)))(cons(list(quotelambda)namesbody)values))))) 110CHAPTER9.MACROS(mylet((a1)(b2))(listab)))Thiscodeuseslet*(rememberwe'repretendingthatwedon'thavelet)tobindmylettoamacrode nition,thenitusesmyletinthebodyofthelet*.Itmakesuseofsomesupportingfunctionsthatwe'llde nepresently,but rstlet'strytogetafeelforwhatitisdoing.Asstatedabove,thesymbolmacrointroducesamacrode nition.Theargumentstomyletwillbethesameasthosetolet,namelyalistofbindingsandabodytoexecutewiththosebindingsinplace.Ithastoseparatethebindings(symbol-valuepairs)intotwolists,oneofthesymbolsandoneofthevalues.ItmightbeusefulinthefollowingdiscussiontorefertoFigure9.1whichshowstheinternalstructureofthemyletformthatwe'llberearranging.Figure9.1:Examplemyletinternalstructureopbindingsbodymyletbindingbindinglistaba1b2Themyletmacrousesafunctioncarstoextractthecarofeachbinding(thesymbol)intothelistcallednames.Here'sthede nitionofcars:(letrec(...(cars(lambda(lst)(mapcarlst)))...)...)Itusesanotheryettobede nedfunctionmap,whichdoesthesameasPerl'sbuiltinmap:itappliesafunctiontoeachelementofalistandreturnsanewlistoftheresults1.mapissurprisinglyeasytoimplementinPScheme:(letrec((map(lambda(oplst)1PerlactuallyborrowsitsmapfunctionfromLisp,whichhashadoneformanyyears. 9.2.EVALUATINGMACROS111(iflst(cons(op(carlst))(mapop(cdrlst)))())))...)It'sarecursivefunction,hencetheneedforletrectobindit.Passedafunctionandlistofzeroormorebindings,ifthelistisemptyitreturnstheemptylist,otherwiseitcons-estheresultofcallingthefunctiononthecarofthelistwithtotheresultofcallingitselfontherest(cdr)ofthelist.Soforexampleiflstis((a1)(b2)),then(mapcarlst)wouldreturnthelist(ab),andthatisexactlywhatthecarsfunctiondoes.cadrs2isverysimilar.Itwalksthelistcollectingthesecondcomponentofeachsublist(thevaluesofthebindings).Soforexamplegiventhelist((a1)(b2)),cadrswillreturnthelist(12).(letrec(...(cadrs(lambda(lst)(map(lambda(x)(car(cdrx)))lst)))...)...)Againitmakesuseofmapthistimepassingitananonymousfunctionthatwilltakethecarofthecdrofitsargument.ThisisverymuchinthestyleofrealSchemeprogrammingnow:constructinglambdaexpressionsonthe yandpassingthemtootherfunctionsasarguments,Ihopeyouareaquiringatasteforit.Anywayhere'sthewholemyletde nitionplussomecodethatcallsit.(let*((mylet(letrec((map(lambda(oplst)(iflst(cons(op(carlst))(mapop(cdrlst)))())))(cars(lambda(lst)(mapcarlst)))(cadrs(lambda(lst)(map(lambda(x)(car(cdrx)))lst))))(macro(bindingsbody)(let*((names(carsbindings))(values(cadrsbindings)))(cons(list(quotelambda)namesbody)values))))))(mylet((a1)2Thetermcadrisacontractionofcarofthecdr"e.g.(cadrx)==(car(cdrx)).thissortofcontractionisoftenseeninschemecode,sometimesnestedasmuchasfouror velevelsdeep,i.e.cadadr. 112CHAPTER9.MACROS(b2))(listab)))Aftercollectingthenamesintoonelistandthevaluesintoanother,themyletmacrobuilds:((lambda(hnamesi)(hbodyi))hvaluesi)Wherehnamesi,hbodyiandhvaluesiareexpandedusingtheappropriatemagic:(cons(list(quotelambda)namesbody)values)Apointworthnotingisthattheconstructedmyletmacroisatrueclosure,sinceithascapturedthede nitionsofthecarsandcadrsfunctionsandexecutesinanouterenvironment(thelet*)wherethosefunctionsarenotvisible.9.2.2AnImprovementThemacrosubstitutionsystemdemonstratedsofarisprettycrude,afterallitrequirestheprogrammertodirectlymanipulatelow-levelliststructures,ratherthanjustsupplyinganexample"ofhowthetransformationistobeperformed.InfactthetopicofmacroexpansionasprovidedbyafullSchemeimplementationisdeservingofabooktoitself.Apartfromthetemplatingability,therearealsoissuesofavoidingvariablecollision(so-calledhygenicmacros)sothatfullSchememacrosaremuchclosertotheideaofC++'sinlinefunctionsthantheyaretoC's#define3.Howeverthereisonesimpleadditionthatwecanmake,whichwillgreatlyimprovetheusefulnessofmacros,andthatinvolvesanextensiontothequotespecialformthatweintroducedinSection8.1onpage87.Ifyourememberquotejustreturnsitsargument,preventingunwantedevaluation.Thisalreadyhasprovedusefulintheconstructionofmacros,aswehaveseenabove.Nowoneperfectuseofquotewouldbetoprovidetemplatesformacros,ifwecouldarrangethatpartsofthequotedtemplatecouldbesubstitutedbeforethequotedtemplateisreturned.Tothatpurposeweintroduceakeywordunquotewhichmarksasectionofaquotedformforevaluation.Perhapsanexamplemightmakethisclear:>(let((x"rain")>(y"spain")>(z"plain"))>(quote>(the(unquotex)>in(unquotey)>fallsmainlyonthe(unquotez))))(the"rain"in"spain"fallsmainlyonthe"plain")3Afullschemeimplementationprovidesanextend-syntaxspecialform.Usingextend-syntax,de ningmyletisassimpleas:(extend-syntax(mylet)(mylet((varval)...)body)((lambda(var...)body)val...)) 9.2.EVALUATINGMACROS113Theletbindingsbindxtothestring"rain"etc.Thatisnottheimportantpart.Theimportantpartisthebodyoftheletwheretheuseoftheunquotekeywordallowsevaluationofthecontainedexpressions(xetc.)despitetheirbeinginsideaquote.Howcanthishelpuswithmacrode nitions?Wellinabigway!considerthismacrode nitionofawhileloop:(definewhile(macro(testbody)(quote(letrec((loop(lambda()(if(unquotetest)(begin(unquotebody)(loop))()))))(loop)))))Itusesafewfeaturesthataren'tavailableyet,likedefineandbegin(whichjustexecutesoneexpressionafteranother),anditwouldseemtobeindangerofrunningoutofstack,butIhopeyoucanseethatessentiallythequoteandunquotearedoingalloftheworkbuildingthebodyofthemacro.Thequotedresultisshowninbold,withtheinternalsubstitutionsunboldedagain.Implementingunquoteiseasy,butit'salittledi erentfromthenormalspecialformsandprimitiveswe'veseenuptonow.I'vebeencarefultoonlyrefertoitasakeyword",becauseitmeansnothingspecialoutsideofaquotedexpression.We'llobviouslyhavetochangethewayquoteworkstomakethishappen,soletsstartbylookingatthechangedPScm::SpecialForm::Quote::Apply().132subApplyf133my($self,$form,$env)=@_;134return$form->first->Quote($env);135gRatherthanjustreturningits rstargument,itnowcallsanewmethodQuote()onit,passingQuote()thecurrentenvironment.Quote()essentiallyjustmakesacopyoftheexpressionsconcerned,butitkeepsaneyeoutforunquotesymbols.NowthismethodwillbeimplementedinthePScm::Exprclassesasfollows:ThedefaultQuote()inPScm::Exprjustreturns$self:041subQuotef$_[0]gTheQuote()inPScm::Expr::List::Pairiswheremostofthedecisionmakinghappens.133subQuotef134my($self,$env)=@_;135if($self->[FIRST]->is_unquote)f136return$self->[REST]->first->Eval($env);137gelsef 114CHAPTER9.MACROS138return$self->quote_rest($env);139g140gOnLine135itcheckstoseeifthe rstelementofthelististhesymbolunquote(isunquote.)Ifitisthenitevaluatesthesecondelementinthecurrentenvironmentandreturnsit.Ifthe rstelementisnotunquotethenithandsovercontroltoahelperroutinequoterest().Here'squoterest().142subquote_restf143my($self,$env)=@_;144return$self->Cons(145$self->[FIRST]->Quote($env),146$self->[REST]->quote_rest($env)147);148gItjustwalksthelist,recursively,constructingacopyasitgoesbycallingQuote()oneachelementandcallingCons()onthequotedsubexpressionandtheresultoftherecursivecall4.ThePScm::Expr::List::NullpackageinheritsQuote()fromPScm::Expr,whichjustreturns$self,andPScm::Expralsohasaquoterest()methodwhichalsojustreturns$selfandusefullyterminatestherecursionofthenon-emptyPScm::Expr::Listquoterest()method.043subquote_restf$_[0]gThatjustleavesthatisunquote()method.Wellsinceonlyasymbolcouldpossiblybeunquote,wecanputadefaultisunquote()methodatthetopoftheexpressiontypehierachy,inPScm::Expr,whichjustreturnsfalse:012subis_unquotef0gThenforPScm::Expr::Symbolonly,weoverridethatwithamethodthatcheckstoseeifitsvalue()isthestring"unquote":182subis_unquotef183my($self)=@_;184return$self->valueeq"unquote";185gThatcompletesourre-implementationofquotetoallowtherecognitionoftheunquotekeyword,butwe'renotquitedoneyet.quoteandunquoteturnouttobesousefulinthede nitionofmacrosthatPSchemeprovidesshorthandsyntacticsugarfortheseforms.Theconstruct'hexpressioni(notethesinglequote)getsexpandedto(quotehexpressioni),andsimilarilytheconstruct,hexpressioniwithaleadingcommagetsexpandedto(unquotehexpressioni).Thisisfairlyeasytodo,solet'sseewhatchangesweneedtomaketothereadertomakethishappen.Firsthere'sthechangestoPScm::Read::nexttoken().4Notethesimilaritybetweenthismethodandthede nitionofmapinPschemeabove. 9.2.EVALUATINGMACROS115066sub_next_tokenf067my($self)=@_;068069while(!$self->fLineg)f070$self->fLineg=$self->fFileHandleg->getline();071returnundefunlessdefined$self->fLineg;072$self->fLineg=~s/^s+//s;073g074075for($self->fLineg)f076s/^(s*//&&returnPScm::Token::Open->new();077s/^)s*//&&returnPScm::Token::Close->new();078s/^'s*//&&returnPScm::Token::Quote->new();079s/^,s*//&&returnPScm::Token::Unquote->new();080s/^.s*//&&returnPScm::Token::Dot->new();081s/^([-+]?d+)s*//082&&returnPScm::Expr::Number->new($1);083s/^"((?:(?:\.)|([^"]))*)"s*//&&dof084my$string=$1;085$string=~s/\//g;086returnPScm::Expr::String->new($string);087g;088s/^([^s()]+)s*//089&&returnPScm::Expr::Symbol->new($1);090g091die"can'tparse:$self->fLineg";092gThechangeisverysimple.YoucanseethatonLines78{79ifitstripsaleadingquoteorcomma,itreturnsanequivalenttokenobject.ThosenewtokentypesarebothinPScm/Token.pm,here'sPScm::Token::Quote.029packagePScm::Token::Quote;030031usebaseqw(PScm::Token);032033subis_quote_tokenf1gItinheritsfromPScm::Tokenandadefaultisquotetoken()therereturnsfalse.TheequivalentPScm::Token::UnquotedeliberatelyinheritsfromPScm::Token::QuoteratherthanjustPScm::Tokensoitgetstheoverriddenisquotetoken()method,andsuppliesanadditionalisunquotetoken()methodreturningtrue.Againadefaultisunquotetoken()inPScm::Tokenreturnsfalse.036packagePScm::Token::Unquote;037038usebaseqw(PScm::Token::Quote); 116CHAPTER9.MACROS039040subis_unquote_tokenf1gTheupshotofthisisthatbothPScm::Token::QuoteandPScm::Token::Unquotereturntrueforisquotetoken(),butonlyPScm::Token::Unquotereturnstrueforisunquotetoken().Finally,let'sseehowthereaderPScm::Read::Read()makesuseofthesenewtokenobjects.017subReadf018my($self)=@_;019020my$token=$self->_next_token();021returnundefunlessdefined$token;022023if($token->is_quote_token)f024my$expr=$self->Read;025die"syntaxError"026unlessdefined($expr)&&$expr->is_expr;027returnnewPScm::Expr::List(028$token->is_unquote_token029?newPScm::Expr::Symbol('unquote')030:newPScm::Expr::Symbol('quote'),031$expr032);033g034035if($token->is_open_token)f036return$self->read_list();037gelsef038return$token;039g040gTheadditionalcodeonLines23{33checkstoseeifthetokenisaquoteorunquotetoken,andifsoreadsthenextexpression,checksthatitisvalidandreturnsanewPScm::Expr::Listcontainingtheappropriatequoteorunquotesymbolandtheexpressionreadafterwards.Theisexpr()methodisde nedtobetrueinPScm::ExprandfalseinPScm::Token,anditsuseherestopsdubiousconstructslike')".Sowenowhaveaconvenientshorthandforquoteandunquote.Todemonstrateitinaction,here'sthatwhilemacroagain,thistimeusingthenewtokens.(definewhile(macro(testbody)'(letrec((loop(lambda()(if,test(begin 9.2.EVALUATINGMACROS117,body(loop))()))))(loop))))We'llbemakingsigni cantuseofmacro,quoteandunquoteinsubsequentchapters,soit'sworthfamiliarizingyourselfnowwiththisnewidiom5.9.2.3OneLastAdditionSometimesgivenaquotedexpression,you'dreallyjustliketohaveitevaluated.Itmayhavebeenpassedasanargument,oritmayhavebeenconstructedinsomeotherway.Whatyouneedisanotherroundofevaluation.Thisissuppliedbythespecialformeval.evalreallydoesdojustthat.Forexample:>(eval'(*22))4Thequotestoppedthe rstroundofevaluation,butevalgotanothertryatit.Here'sanotherexample:>(eval(list'*44))16evalisquitesimple.Itisaspecialformbecauseitneedsaccesstoanenvironmentinwhichtoperformtheevaluation(rememberprimitiveshavetheirargumentsevaluatedforthemandsodon'tneedanenvironment.)Itevaluatesits rstargumentinthecurrentenvironment(specialformsdon'thavetheirargumentsevaluatedforthem,)thenitevaluatestheresultasecondtime,thistimeinthetop-levelenvironment.Here'sPScm::SpecialForm::Eval:093packagePScm::SpecialForm::Eval;094095usebaseqw(PScm::SpecialForm);096097subApplyf098my($self,$form,$env)=@_;099$form->first()->Eval($env)->Eval($env->top);100gYoucanseethatthesecondroundofevaluationisdoneinthecontextofthetop-levelenvironmentobtainedbycallinganewmethodtop()onthecurrentenvironment.Thattop()methodisalsoverysimple:5Thequoteandunquotedescribedherearedonedi erentlyintrueScheme.AtrueSchemeimplementationdistinguishesbetweenasimplequotewhichdoesnotrecognizeunquote,andanalternativequasiquotewhichdoes.Thismeansquoteisasecientasouroriginalimplementation,butwestillhaveaccesstoanunquotemechanism.Thequoteformstillhasthe'"syntacticsugar,andquasiquoteusesthealternative`"(backtick)shorthand.AdditionallyafullSchemeprovidesanunquote-splicing(,@")whichexpectsalistandsplicesitintotheexistingformatthatpoint. 118CHAPTER9.MACROS014subtopf015my($self)=@_;016if($self->fparentg)f017return$self->fparentg->top;018gelsef019return$self;020g021gItjustcheckstoseeifithasaparent,callingtop()recursivelyonthatifithas,andreturningitselfifithasn't.Onethingtowatchoutforwitheval:thecodethatisevaluatedisnotaclosure.Anyvariablesinthatcodewillbelookedupinthetop-levelenvironment,nottheonewheretheexpressionwasconstructed,northeonethatiscurrentwhenevaliscalled.Forexample:>(let((*-))>(*33))0>(let((*-))>(eval'(*33)))9Nonethelessevalisausefultoolinyourkit,we'llseeitinactioninlaterchapters.9.3SummaryHere'stheadditionstoReadEvalPrint()whichbindournewmacrofeatureandevalintheinitialenvironment.Thequotebindingwasalreadythere,andasshownabove,unquoteisonlyakeywordanddoesnotneedabinding:031subReadEvalPrintf032my($infh,$outfh)=@_;033034$outfh||=newFileHandle(">-");035my$reader=newPScm::Read($infh);036while(defined(my$expr=$reader->Read))f037my$result=$expr->Eval(038newPScm::Env(039let=>newPScm::SpecialForm::Let(),040'*'=>newPScm::Primitive::Multiply(),041'-'=>newPScm::Primitive::Subtract(),042if=>newPScm::SpecialForm::If(),043lambda=>newPScm::SpecialForm::Lambda(),044list=>newPScm::Primitive::List(),045car=>newPScm::Primitive::Car(),046cdr=>newPScm::Primitive::Cdr(),047cons=>newPScm::Primitive::Cons(), 9.4.TESTS119048letrec=>newPScm::SpecialForm::LetRec(),049'let*'=>newPScm::SpecialForm::LetStar(),050eval=>newPScm::SpecialForm::Eval(),051macro=>newPScm::SpecialForm::Macro(),052quote=>newPScm::SpecialForm::Quote(),053)054);055$result->Print($outfh);056g057g9.4TestsThetestsformacroandunquoteareinListing9.5.1onthenextpage.The rsttestjustimplementsandteststhemyletexampleweworkedthroughinthetext,andthesecondtestshowsunquoteinactionwithavariationonanotherexamplewe'vealreadyseen.Thethirdtestexercisesthesyntaxextensionsinthereader,andthefourthtestdemonstratesthatmacros,likeclosures,produceatextualrepresentationofthemselveswhenprinted.ThetestsforevalareinListing9.5.2onpage122.Thisjustdoesasimpleevaluationofaquotedform. 120CHAPTER9.MACROS9.5Listings9.5.1t/PScmMacro.t001usestrict;002usewarnings;003useTest::More;004uselib't/lib';005usePScm::Testtests=>5;006007BEGINfuseok('PScm')g008009evalok(<3;006007BEGINfuseok('PScm')g008009evalok(<(let*((a5)>(dummy(set!a10))>a))10Thisisabitcontrived,weuseadummyvariabletoallowtheset!expressiontobeevaluatedbeforethebodyofthelet*returnsthenewvalueofa.Howeveritshouldbeclearfromtheexamplethattheset!didtakee ect.Sohowdowegoaboutimplementingset!?Wellasluckwouldhaveit,wealreadyhaveamethodforchangingexistingbindingsinanenvironment,wehavetheAssign()methodthatwasdevelopedfortheletrecspecialforminapreviousincarnation.Ithaspreciselytherightsemanticstoo(whataco-incidence!)Itsearchesthroughthechainofenvironmentframesuntilit ndsanappropriatebinding,assigningthevalueifitdoesanddie()-ingifitdoesn't.Hereitisagain:059subAssignf060my($self,$symbol,$value)=@_;061062if(defined(my$ref=$self->_lookup_ref($symbol)))f063$$ref=$value;064gelsef065die"nobindingfor@f[$symbol->value]g",066"in@f[ref($self)]g ";067g068gSoallwehavetodoiswireitup.Theprocessofcreatingnewspecialformsshouldbefamiliarbynow.FirstlywesubclassPScm::SpecialFormandgiveournewclassanApply()method.ThenewclassinthiscaseisPScm::SpecialForm::Set.ItsApply()methodasusualtakesaformandthecurrentenvironmentasarguments.Inthiscasetheformshouldcontainasymbolandavalueexpression.ThisApply()willevaluatetheexpressionandcalltheenvironment'sAssign()methodwiththesymbolandresultingvalueasarguments:138packagePScm::SpecialForm::Set;139140usebaseqw(PScm::SpecialForm);141142subApplyf143my($self,$form,$env)=@_;144my($symbol,$expr)=$form->value;145$env->Assign($symbol,$expr->Eval($env));146gAndthat'sallthereistoit,apartfromaddingaPScm::SpecialForm::Setobjecttotheinitialenvi-ronment,boundtothesymbolset!. 126CHAPTER10.SIDEEFFECTS10.3SequencesSequencesareanotherfairlytrivialadditiontothelanguage.Ratherthanasingleexpression,asequencecontainszeroormoreexpressions.Eachexpressionisevaluatedinorder,andthevalueofthelastexpressionisthevalueofthesequence.Thevalueofanemptysequenceisnull,theemptylist.Thisissuchacommonthinginmanyotherlanguages(suchasPerl)thatitgoeswithoutnotice,butthinkingaboutit,sequencesonlybecomerelevantandusefulinthepresenceofsidee ects.Sinceonlythevalueofthelastexpressionisreturned,precedingexpressionscanonlya ectthecomputationiftheyhavesidee ects.ThekeywordintroducingasequenceinPScmisbegin.begintakesalistofzeroormoreexpressions,evaluateseachone,andreturnsthevalueofthelastexpression,ornulliftherearenoexpressionstoevaluate.ItfunctionssomethinglikeablockinPerl,exceptthataPerlblockalsoenclosesavariablescopelikeletdoes,butbegindoesnotimplyanyscope.(begin...)Withbegin,wecanwritethatratherawkwardset!examplefromtheprevioussectionalotmoreclearly:>(let((a5))>(begin>(set!a10)>a))10begincould,infact,beimplementedasafunction,providedthatfunctionsareguaranteedtoevaluatetheirargumentsinleft-to-rightorder,asthisimplementationdoes.Howeveritissafernottomakethatassumption(rememberthenetworkedlanguagewhereevaluationwasenvisagedinparallel,)sobeginisbetterimplementedasaspecialformwhichcanguaranteethatlefttorightbehaviour.Asmightbeimaginedthecodeisquitetrivial:evaluateeachcomponentoftheformandreturnthelastvalue.Wepickasaninitialvaluetheemptylist,sothatifthebodyofthebeginisempty,thatistheresult.AsusualwesubclassPScm::SpecialForm,inthiscasetoPScm::SpecialForm::Begin,andgivethenewclassanApply()method:149packagePScm::SpecialForm::Begin;150151usebaseqw(PScm::SpecialForm);152153subApplyf154my($self,$form,$env)=@_;155my(@expressions)=$form->value;156157my$ret=newPScm::Expr::List();158159while(@expressions)f160$ret=shift(@expressions)->Eval($env);161g162 10.4.SUMMARY127163return$ret;164g1651661;OnLine155itextractstheexpressionsfromtheargument$form.ThenonLine157itinitialisesthereturnvalue$rettoaninitialvalue.OnLines159-161itloopsovereachexpression,evaluatingitinthecurrentenvironment,andassigningtheresultto$ret,replacingthepreviousvalue.LastlyonLine163itreturnsthe nalvalue.10.4SummaryInthissectionwe'veseenvariableassignmentaddedtothelanguage,butalsotakensometimetoconsidersomedrawbacksofthatfeature.We'vealsolookedathowsequencesbecomeusefulinthepresenceofvariableassignment.Infactsequencesservenopurposewithoutside-e ectsandside-e ectsarediculttodowithoutsequences.Asusualweneedtoaddournewformstotheinterpreterbybindingthemintheinitialenvironment.Here'sReadEvalPrint()withtheadditionalbindings.031subReadEvalPrintf032my($infh,$outfh)=@_;033034$outfh||=newFileHandle(">-");035my$reader=newPScm::Read($infh);036while(defined(my$expr=$reader->Read))f037my$result=$expr->Eval(038newPScm::Env(039let=>newPScm::SpecialForm::Let(),040'*'=>newPScm::Primitive::Multiply(),041'-'=>newPScm::Primitive::Subtract(),042if=>newPScm::SpecialForm::If(),043lambda=>newPScm::SpecialForm::Lambda(),044list=>newPScm::Primitive::List(),045car=>newPScm::Primitive::Car(),046cdr=>newPScm::Primitive::Cdr(),047cons=>newPScm::Primitive::Cons(),048letrec=>newPScm::SpecialForm::LetRec(),049'let*'=>newPScm::SpecialForm::LetStar(),050eval=>newPScm::SpecialForm::Eval(),051macro=>newPScm::SpecialForm::Macro(),052quote=>newPScm::SpecialForm::Quote(),053'set!'=>newPScm::SpecialForm::Set(),054begin=>newPScm::SpecialForm::Begin(),055)056);057$result->Print($outfh); 128CHAPTER10.SIDEEFFECTS058g059gIt'sworthpointingoutamajordi erencebetweenPSchemeandstandardschemeimplementationshere.Instandardscheme,functionbodiesandthebodiesofletexpressionsandtheirvariantsareallimplicitsequences.Soinfactinarealschemeimplementationyoucouldjustwrite:>(let((a5))>(set!a10)>a)10InPSchemefunctionandletbodiesaresinglestatementsandrequireanexplicitbegintocreateasequence,becauseIwantedtokeepthedistinctionbetweensingleexpressionsandsequencescleartothereader.10.5TestsAtestofset!andbegincanbeseeninListing10.6.1onthefacingpage.Thetestbindsato1inalet,theninthebodyoftheletitperformsabeginwhichset!sato2thenreturnsthenewvalueofa. 10.6.LISTINGS12910.6Listings10.6.1t/PScmSideEffects.t001usestrict;002usewarnings;003useTest::More;004uselib't/lib';005usePScm::Testtests=>2;006007BEGINfuseok('PScm')g008009evalok(<fbindingsgf$symbol->valueg=$value;135return$symbol;136gIttakesasymbolandavalue(alreadyevaluated)asarguments.OnLine134itdirectlyaddsthebindingfromthesymboltothevalue,reguardlessofanypreviousvalue,andonLine135itreturnsthesymbolbeingde ned,togivetheprintsystemsomethingsensibletoprint.11.2ThedefineSpecialFormNowweneedtofollowtheusualproceduretoaddanotherspecialformtothelanguage:wesubclassPScm::SpecialFormandgiveournewclassanApply()method.InthiscasethenewclassiscalledPScm::SpecialForm::De ne.167packagePScm::SpecialForm::Define;168169usebaseqw(PScm::SpecialForm);170171subApplyf131 132CHAPTER11.DEFINE172my($self,$form,$env)=@_;173my($symbol,$expr)=$form->value;174$env->Define($symbol,$expr->Eval($env));175g1761771;AllitdoesisonLine173itextractsthesymbolandtheexpressionfromtheargument$formthenonLine174itcallstheDefine()environmentmethoddescribedabovewiththesymbolandevaluatedexpression(value)asargument.11.3PersistantEnvironmentsThereisonemorechangetomake.Inorderfordefinetobee ectivefromoneexpressiontoanother,itnolongermakessensetocreateafreshenvironmentforeachexpressiontobeevaluatedin,asRead-EvalPrint()hasdonesofar,becausethatwoulderadicatethee ectofanydefineperformedbyapriorexpression.Thesolutionisofcoursetrivial,wecreatetheinitialenvironmentoutsideoftheread-eval-printloopitself,andpassittoeachtop-levelEval():031subReadEvalPrintf032my($infh,$outfh)=@_;033034$outfh||=newFileHandle(">-");035my$reader=newPScm::Read($infh);036my$initial_env=newPScm::Env(037let=>newPScm::SpecialForm::Let(),038'*'=>newPScm::Primitive::Multiply(),039'-'=>newPScm::Primitive::Subtract(),040if=>newPScm::SpecialForm::If(),041lambda=>newPScm::SpecialForm::Lambda(),042list=>newPScm::Primitive::List(),043car=>newPScm::Primitive::Car(),044cdr=>newPScm::Primitive::Cdr(),045cons=>newPScm::Primitive::Cons(),046letrec=>newPScm::SpecialForm::LetRec(),047'let*'=>newPScm::SpecialForm::LetStar(),048eval=>newPScm::SpecialForm::Eval(),049macro=>newPScm::SpecialForm::Macro(),050quote=>newPScm::SpecialForm::Quote(),051'set!'=>newPScm::SpecialForm::Set(),052begin=>newPScm::SpecialForm::Begin(),053define=>newPScm::SpecialForm::Define(),054);055056while(defined(my$expr=$reader->Read))f057my$result=$expr->Eval($initial_env); 11.4.TESTS133058$result->Print($outfh);059g060gNowwecanactuallywritesomeoftheearliestexamplesfromthisbookinthelanguageathand.>(definefactorial>(lambda(x)>(ifx>(*x(factorial(-x1)))>1)))factorial>(factorial4)24Thefactorialfunctionwaschosentodemonstratethatclosurescreatedbydefinecancallthemselvesrecursively.Afterall,theenvironmenttheycapturemust,byvirtueofhowdefineoperates,containabindingfortheclosureitself.definecanbeusedforotherthingstoo.BecauseofthesimplesemanticsofthePSchemelanguage,defineisperfectlysuitedforcreatingaliasestoexistingfunctions.Forinstanceifaprogrammerdoesn'tliketheratherobscurenamesforthefunctionscarandcdr,theycanprovidealiases:>(definefirstcar)first>(definerestcdr)rest>(first(list1234))1>(rest(list1234))(234)Thesearecompletelyequivalenttotheoriginalfunctionsexceptinname.Theprimitivede nitionsareboundtothenewsymbolsfirstandrestinthetoplevelenvironmentexactlyastheyarestillboundtotheiroriginalsymbols.11.4TestsTestsfordefinecanbeseeninListing11.5.1onthenextpage.The rsttestdemonstratesglobalde nition,andalsothatclosuresboundbydefinearenaturallycapableofrecursionsincedefineassignstheminthecurrentenvironment.Thesecondtestshowsthatdefinecanbeusedinanyenvironmentalcontext,eveninthebodyofaclosure,tocreatenewbindings.Thissecondtestisquiteinterestingbecauseitdemonstratesafunctionthatcreatesahelper"function(h)thatisonlyvisibletothecontainingtimes2closure. 134CHAPTER11.DEFINE11.5Listings11.5.1t/PScmDefine.t001usestrict;002usewarnings;003useTest::More;004uselib't/lib';005usePScm::Testtests=>3;006007BEGINfuseok('PScm')g008009evalok(<SUPER::init($amount);Thesuperobjectisanimplicit eldoftheclass,andisautomaticallyinitialisedwhenanobjectiscreated.Itrepresentstheparentobject.Thespecialvariablethisisanimplicitargumenttomethods,itrepresentstheobjectonwhichthemethodwasoriginallyinvoked,justas$selfconventionallydoesforperlmethods2.TheInterestAccountclasscannotseeitsparent's elds,onlyitsmethods.Ithastocall(thisbalance)togetthevalueofbalanceand(thisdeposithargi)tochangeit.Methodsarealwayscalledfromanobject.Therearenoshorthands.Evenwithinamethodbodyitisnecessarytouseoneofthespecialobjectsthisorsupertocallamethodonthecurrentobject.TheInterestAccountclasscanbeusedasfollows:>(definemy-account(InterestAccount202))my-account>(my-accountdeposit10)30>(my-accountbalance)30>(my-accountaccumulate)90>(my-accountbalance)90Quiteanicerateofinterestthatis.2Wecouldhavechosenthenameselfinstead,tomaketheexampleseasierforperlprogrammerstoread,buttheperlunderthehoodmightstarttogetugly. 138CHAPTER12.CLASSESANDOBJECTS12.1.2ClassVariablesandMethodsAlthoughthereisnodirectsupportforclassvariablesandmethods,thefactthatclasses,andhencemethods,capturethecurrentenvironmentallowsustofakethem.Here'savariantontheAccountclassthatkeepstrackofthetotalamountofmoneyinallaccountobjects:(defineAccount(let((total0))(make-classroot(balance)(set-balance(opamount)(begin(set!balance(opbalanceamount))(set!total(optotalamount))))(init(amount)(thisset-balance+amount))(deposit(amount)(thisset-balance+amount))(withdraw(amount)(thisset-balance-amount))(balance()balance)(total()total))))Theletbindstotaltoaninitialvalueof0thenevaluatesthemake-classconstructinthisnewenvironment.Thenewlycreatedclasscapturesthatenvironment.ThatnewclassisthenreturnedbytheletexpressionandboundtoAccount.EveryinstanceofAccountwillsharethelexicallyscopedvariabletotal.Ratherthanchangeeachofinit,depositandwithdrawtoindividuallymaintainboththevalueoftotalandbalance,anewmethodset-balancehasbeenadded.Ittakesanoperationop(+or-)andanamountandappliestheoperationwiththeamountseparatelytoboththebalanceandthetotal.Theinit,depositandwithdrawmethodshavebeenmodi edtousethisnewmethod,andanothernewmethod,total,providesread-onlyaccesstothevalueoftotal.12.1.3superCallsWe'renotquite nishedwiththedetails.There'sonelastwrinkletodealwithreguardingthatsuperobjectmentionedearlier.Althoughonthesurfaceitmayappeartobehavejustlikeanyotherobject,acalltoamethodonthesuperobjectcannotjustbeasimplemethodinvocation.Ifitwas,thenthisinthecalledmethodwouldbetheobjectreferredtobysuperinthecallingmethod.Insteadthethisinthecalledmethodshouldcontinuetorefertothethisthatwascurrentbeforethesupercall.Tomakethatclear,considerarathercontrivedexample:>(definecat>(make-class>root>()>(poke()(thisrespond))>(respond()'purr)>))cat>(definelion 12.2.IMPLEMENTATION139>(make-class>cat>()>(poke()(superpoke))>(respond()'roar)>))lion>(defineleo(lion))leo>(leopoke)roarleoisaninstanceoflionandthecall(leopoke)invokeslion'spokemethod.lion'spokemethoddoes(superpoke),whichinvokescat'spokemethod.cat'spokemethoddoes(thisrespond)butsince,evenafterthesupercall,thisisstilltheoriginatingobjectleoofclasslion,itislion'srespondmethodthatgetsinvoked,resultinginroar"ratherthanthepurr"fromcat'srespondmethod.12.1.4FeatureSummaryHere'sasummary,then,ofthemainfeaturesofourimplementationThereisnomultipleinheritance.Therearenoclassvariablesormethods,butthecaptureofthelexicalenvironmentbyaclassde nitionallowsustofakethem.Onlymethodsareinherited: eldsarevisibleonlytomethodsoftheclassinwhichtheyarede ned.Fieldsbehavelikenormalvariableswithinmethodbodies.Thespecialvariablethisisalwaysavailableasanimplicitargumenttoeachmethodandreferstotheobjectthatthemethodwasoriginallyinvokedon.Thespecialvariablesuperisalwaysavailableasanimplicit eldofeveryobjectandreferstotheparentobjectoftheobjectthatownsthemethod.Callingamethodonthesuperobjectpassesthis,notsuper,astheimplicitobjectargumenttothecalledmethod.Thespecialvariableclassisalwaysavailableasanimplicit eldineveryobjectandreferstotheclassoftheobject.12.2ImplementationSohowdowegoaboutimplementingthisextension?Well,tobefrank,inafairlyadhocway.ItshouldbeobviousthatPSchemeobjectshavealotincommonwithenvironments,namelythattheystorethevaluesofvariables.Ourexistingenvironmentimplementationcaneasillybepressedinto 140CHAPTER12.CLASSESANDOBJECTSserviceasaPSchemeobject.Infactinthisimplementationobjectsareenvironments,orrather,chainsofenvironmentslinkedviaasuper eldwhereeachindividualenvironmentrepresentsaninstanceoftheequivalentclassintheclasshierachy.Weneedonlyaddacoupleofmethodstoourexistingenvironmentimplementationtogetallweneed.The rstofthosenewPScm::EnvmethodswouldofcoursebeanApply(),sincenowenviron-mentsareexposedinthelanguageasobjectsandareinvokedasoperators:(hobjectihmethodihargi...).Thesecondmethodwe'llneedissomesortoflookupmethod()methodbecausewe'vesaidthatPSchememethodsliveinaseparatenamespacefromnormal elds.WecanalwaysrecognisePSchememethodinvocationandhencethePSchememethodnamebycontext:itisalwaysthe rstargument"toanobject.WecheategregiouslyhereandjustusetheclassbindingineachPSchemeobjecttolocatethePSchemeclass,andchecktoseeifthePSchememethodisinit.Ifnotfoundthenlookupmethod()recursesdownthechainofobjectsviathesuperbindingandtriesagain.OfcourseweneedanobjecttorepresentPSchemeclasses,butthatisjustgoingtocontaintheparentPSchemeclass,the eldsandmethodsoftheclass,andtheenvironmentthatwascurrentatthepointofitscreation.ThatnascentPSchemeClasspackagewillneedanApply()methodthatwillcreatePSchemeobjectsondemand,passinganyargumentstothatobject'snearestinitmethod.Thatisprettymuchallweneedtodo.Methodscanactjustlikeclosuresbutwillextendtheenvironmentrepresentingtheobjectwhentheyarecalled.Thereareafew ddlydetailsaroundmethodinvocationonasuperobject,butwe'lldealwiththatlater.12.2.1ClassCreationwithmake-classStartingfromthetop,thespecialform(make-classhparent-expri(hfieldi...)hmethodi...)issimpletoimplement,itjustreturnsaninstanceofthatyettobedescribedPScm::Classobject.Hope-fullyit'snottoocounterintuitivethatPSchemeclassesareinfactPerlobjects.Here'stheimplementationofmake-classinanewPScm::SpecialForm::MakeClasspackage:178packagePScm::SpecialForm::MakeClass;179180usebaseqw(PScm::SpecialForm);181182subApplyf183my($self,$form,$env)=@_;184185my$parent_expr=$form->first;186my$fields=$form->rest->first;187my$methods=$form->rest->rest;188my$parent_class=$parent_expr->Eval($env);189returnPScm::Class->new($parent_class,190$fields,191$methods,192$env); 12.2.IMPLEMENTATION141193g1941951;AsisusualforaspecialformitjusthasanApply()method.OnLine185thatunpackstheparentexpression, eldsandmethodsfromtheargument$form,andonLine188itevaluatestheparentexpres-sioninthecurrentenvironmenttogetanactualparentclass(PScm::Class)object.FinallyonLine189itreturnsanewinstanceofPScm::Classcapturingthosevaluesandthecurrentenvironment.Thenew()methodinPScm::Classdoesn'tdoanythingtooclevereither,OnLine10itdeclaresahashref$rhmethods,andthenonLine11itcallsahelperstaticmethodpopulatemethodshash()whichwillchopupthePSchememethodsintonames,argumentsandbodies,storingeachpairofargsandbodyinthehashkeyedonthePSchememethodname.ThenstartingonLine13itreturnsanewinstancecontainingthathashalongwiththeparentPSchemeclass, eldsandcurrentenvironment:007subnewf008my($class,$parent,$fields,$methods,$env)=@_;009010my$rh_methods=fg;011$class->_populate_methods_hash($rh_methods,$methods);012013returnblessf014parent=>$parent,015fields=>[$fields->value],016methods=>$rh_methods,017env=>$env,018g,$class;019gHere'spopulatemethodshash():021sub_populate_methods_hashf022my($class,$rh_methods,$methods)=@_;023if($methods->is_pair)f024my$method=$methods->first;025my($name,$args,$body)=$method->value;026$rh_methods->f$name->valueg=027fargs=>$args,body=>$bodyg;028$class->_populate_methods_hash($rh_methods,$methods->rest);029g030gThat'sitformake-class.Followingourpreviouscourse,wenextneedtolookatPSchemeobjectcreation,whichoccurswhenaPSchemeclassisinvokedwithargumentsintendedforitsinitmethod:(hclassihargi...).12.2.2ObjectCreationIt'sprobablyworthsteppingbackatthisstagetoclarifyhowourenvironmentswillbearrangedintoobjects.AsI'vealreadymentionedeachPSchemeobject(environment)extendstheenvironmentthat 142CHAPTER12.CLASSESANDOBJECTSwascapturedbyitsclass.FurthermoreeachPSchemeobject(environment)hasasuper eldreferringtotheanonymousPSchemeobjectcreatedbyitsparentPSchemeclass.ThatmeansweendupwithasituationsomethinglikeFigure12.1Figure12.1:NotionalobjectstructurePScm::EnvPScm::EnvsuperPScm::EnvsuperAlthoughthis guredoesnottellthewholestory,itatleastemphasizesthataPSchemeobjectconsistsofanumberofenvironmentframes,oneforeachclassintheequivalentPSchemeclasshierachy,butthoseenvironmentframesareconnectednotbyadirectparent/childrelationshipbutviaanordinaryvariableineachframecalledsuper.Theenvironmentframesrepresentingeachobjectextendtheenvironmentthattheirrespectiveclassescaptured.Thisisimplied,butnotshown,bytheunterminatedarrowsinthe gure.SowewereabouttotakealookathowPSchemeclassescreatePSchemeobjects.Classescreateobjectswhentheyareinvokedas(hclassihargi...).TomakeanythinginvokeablewejustneedgiveitanApply()method,andhere'stheoneforPScm::Class:032subApplyf033my($self,$form,$env)=@_;034035my$new_object=$self->make_instance();036$new_object->call_method($new_object,"init",$form,$env);037return$new_object;038gOnLine35itcallsamakeinstance()methodtocreateanewPSchemeobject(reallyaPScm::Env).ThenonLine36itcallsthePScminitmethodofthenewobject.Thisisdoneusingacallmethod()methodofPScm::Env.ThistakesthePSchemeobjectonwhichthemethodisbeinginvoked($new-object,whichwillbepassedtothePSchememethodasthis),thenameofthePSchememethod("init"),andtheargumentstothemethoditself($formand$env.)We'lllookatcallmethod()later.Themakeinstance()methodofPScm::ClassmustrecursedowntotherootofthePSchemeclasshierachy,creatingachainofanonymousPSchemeobjectsonthewaybackup,eachlinkedtoitsparentbyasuper eld.Hereitis:040submake_instancef041my($self)=@_;042043my$parent_instance=$self->fparentg->make_instance();044 12.2.IMPLEMENTATION143045return$self->fenvg->ExtendUnevaluated(046newPScm::Expr::List(047PScm::Expr::Symbol->new("class"),#$self048PScm::Expr::Symbol->new("super"),#$parent_instance049@f$self->ffieldsgg,#0...050),051newPScm::Expr::List(052$self,#"class"053PScm::Env::Super->new(super=>$parent_instance),#"super"054((PScm::Expr::Number->new(0))x@f$self->ffieldsgg),#field...055)056);057gThe rstthingitdoes,onLine43istocallitsparentPSchemeclass'makeinstance()methodtogetaninstanceofitsparentclass.ThenstartingonLine45therestofthemethodextendstheenvironmentthatthePScm::Classobjectcapturedwhenitwascreated,withappropriatebindingsfromclassto$self,supertothe$parentinstanceandfromeachofthePSchemeclassesfieldstoinitialvaluesofzero.Itisthisnewenvironmentthatisreturnedbymakeinstance().Ifyouwerereadingtheabovecodecarefully,you'dhavenoticedthatthesuper elddoesnotlinkdirectlytotheparentinstance,butviaaderivativeofPScm::EnvcalledPScm::Env::Super.ThisissothatthesuperobjectcanhaveaseparateApply()method.Thatgivesthelietooursimplepictureofenvironments-as-objectsinFigure12.1ontheprecedingpage.InfactthetruesituationisshowninFigure12.2.Figure12.2:RealobjectstructureobjectsuperobjectPScm::EnvrootobjectPScm::Env::supersuperPScm::Envclasssuperfield0classPScm::EnvPScm::EnvPScm::ClassenvPScm::Class::Rootparentenvmethod 144CHAPTER12.CLASSESANDOBJECTSTokeepthingssimplethis gureonlyshowsanobjectwhoseimmediateparentisroot.YoucanseethatthePSchemeobjectisjoinedtoitsparentviaaPScm::Env::Superobjectboundtoitssuper eld,andthatthePScm::Env::Superobjectalsohasasuper eldprovidingthelinktotherealparent.AdditionallyeachPSchemeobjecthasaclassbindingreferringtothePSchemeclassthatcreatedit.ThatisaPScm::Classforallbuttherootobject,whichhasnosuperbindingandhasaclassbindingthatreferstoaPScm::Class::Rootobject.PScm::Class::RootisaderivativeofPScm::Class,anditisaPScm::Class::Rootinstancethatwillbeboundtorootintheinitialenvironment.Thatconvenientlybringsusbackroundtothemakeinstance()method,andhowthatrecursivecalltotheparentPSchemeclass'makeinstance()isterminated.Thathappenswhenithitsthemake-instance()methodofthePScm::Class::Rootpackage,shownnext.070packagePScm::Class::Root;071072usebaseqw(PScm::Class);073074subnewf075my($class,$env)=@_;076077returnblessf078parent=>0,079fields=>[],080methods=>fg,081env=>$env,082g,$class;083g084085submake_instancef086my($self)=@_;087088return$self->fenvg089->ExtendUnevaluated(090newPScm::Expr::Symbol("class"),091$self092);093g0940951;Thenew()methodonLines74-83isjustmeanttobeeasytocallfromthereplwheretherootclasswillbeinitialised.ItcreatesaPSchemeclasswithnoparent,no elds,nomethods,andwhateverenvispassedin3.Themakeinstance()methodonLines85-93isnotrecursive,itjustextendsthecapturedenviron-mentwithabindingofclassto$self(thePScm::Class::Rootobject,)returningtheresult.Note3It'sactuallyredundantforthatrootenvironmenttohaveaclassbindingoraparentenvironment,sincetherootclasscurrentlyhasnomethods.Howeverifwedidwanttoextendtheimplementationtoaddgenericmethodstotherootclassthenallthepiecesweneedareinplace,sowecanacceptthatredundancyfornow. 12.2.IMPLEMENTATION145thatittakesadvantageofthefactthatExtendUnevaluated()cancopewithasinglesymbolandvalueaswellaslistsofthesame.12.2.3initMethodInvocationSofarwe'velookedathowPSchemeclassesarecreatedandhowtheyinturncreatePSchemeobjects.Nextwe'regoingtolookathowPSchememethodsareinvoked,startingwiththeinitmethod.Ifyouremember,thePSchemeinitmethodiscalledbyPScm::Class::Apply()whencreatinganewobject,bycallingcallmethod()ontheinstance$newobjectreturnedbyPScm::Class::make-instance().Since$newobjectisaPScm::Env,callmethod()mustbeamethodofPScm::Env,andhereitis.168subcall_methodf169my($self,$this,$method_name,$args,$env)=@_;170171if(my$method=$self->_lookup_method($method_name))f172return$method->ApplyMethod($this,$args,$env);173g174gcallmethod()ispassedboththereal"perlobject$selfandtheobjectrepresentingPScheme'sideaofthecurrentobject,$this,thatthePSchememethodisbeinginvokedon.Normallytheseareoneandthesame.Additionallyitispassedthemethodname,argumentsandanotherenvironmentinwhichtheargumentsaretobeevaluatedifamethodisfound.OnLine171ituseslookupmethod()(discussednext)to ndthemethod,andiffoundthenonLine172itinvokesthePSchememethodbycallingitsApplyMethod()andreturnstheresult.Ifnomethodcanbefounditreturnsundef,andsinceinthecaseofPScm::Class'Apply()theresultofcallinginitisdiscardedanyway,itisnotfatalifaninitmethodisnotfound.lookupmethod()employsasimplestrategytolocateamethod.FirstitchecksinthecurrentPSchemeobject'sclass,andifitcan't ndthemethodthere,itrecursestoitssuperobject.Thatleadstotheequallysimplede nitionbelow.136sub_lookup_methodf137my($self,$method_name)=@_;138139return$self->_lookup_method_here($method_name)140||$self->_lookup_method_in_super($method_name);141gSolookupmethod()breaksdownintotwosimplermethods:lookupmethodhere()andlookup-methodinsuper(),whichittriesinturn.lookupmethodhere()issimilarilysimple.143sub_lookup_method_heref144my($self,$method_name)=@_;145146if(exists$self->fbindingsgfclassg)f147return$self->fbindingsgfclassg148->get_method($method_name,$self); 146CHAPTER12.CLASSESANDOBJECTS149g150gItcheckstoseeifthecurrentobjecthasaclassbinding,andifsoitcallsgetmethod()ontheclass,returningtheresult.getmethod()willreturnundefifitcan't ndthemethodintheclass,andlookupmethodhere()returnsundefifthereisnoclassbinding.lookupmethodinsuper()isequallysimple.152sub_lookup_method_in_superf153my($self,$method_name)=@_;154155if(exists$self->fbindingsgfsuperg)f156return$self->fbindingsgfsuperg157->_lookup_method($method_name);158g159gItcheckstoseeifthecurrentPSchemeobjecthasasuper,andifsoitcallslookupmethod()onit.Otherwiseitreturnsundef.Sincelookupmethod(),lookupmethodhere()andlookupmethodinsuper()areallmethodsofPScm::Env,theyareallavailabletoPScm::Env::Superwheretheyworkwithoutmodi cation:superobjectshaveasuper eldbutnoclass eld.Goingbacktolookupmethodhere(),ifthatfoundaclassbinding,itcalledgetmethod()onthePSchemeclass,passingitthemethodnametolookfor,andperhapslessobviously,$selfaswell.Here'swhatgetmethod()backinPScm::Classdoeswiththosearguments.059subget_methodf060my($self,$method_name,$object)=@_;061062if(exists$self->fmethodsgf$method_nameg)f063returnPScm::Closure::Method->new(064$self->fmethodsgf$method_namegfargsg,065$self->fmethodsgf$method_namegfbodyg,$object);066g067gOnLine62itlooksinitsmethodssubhashforakeymatchingthestring$methodname.Ifit ndsoneitknowsithasfoundthemethodandreturnsanewinstanceofPScm::Closure::Method,aclosurejustlikealambdaexpression,containingtherelevantmethodargsandmethodbodyfromthesubhash,andmostimportantlycapturingtheenvironment$object.Reasoningbackwards,thisiscorrect,$object(the$selffromlookupmethodhere())istheenvironmentinwhichthemethodwasfound,(viaclass)andthatistheenvironmentthatthemethodshouldextendwhenitexecutes,sothatthemethodbodycansee"the eldsoftheobject.I'djustliketoemphasizeapointhere,theobject$objectpassedtogetmethod()isnotnecessarilythesameasthethisthatwillbepassedtothemethodwhenitexecutes.Thatwouldonlybetrueifthemethodwasfoundinthe rstPSchemeobjectthatlookupmethod()lookedin.There'sverylittlelefttocovernow.WejustneedtotakealookatPScm::Closure::Method.ThisisasubclassofPScm::Closure,asyoucansee. 12.2.IMPLEMENTATION147067packagePScm::Closure::Method;068069usebaseqw(PScm::Closure);070071subnewf072my($class,$args,$body,$env)=@_;073074blessf075args=>PScm::Expr::List->Cons(076PScm::Expr::Symbol->new("this"),$args077),078body=>$body,079env=>$env,080g,$class;081g082083subApplyMethodf084my($self,$this,$form,$env)=@_;085my$evaluated_args=$form->map_eval($env);086return$self->_apply(PScm::Expr::List->Cons($this,$evaluated_args));087g0880891;Thenew()methodonLines71-81istheonewejustsawbeingcalledbygetmethod().Whatdi eren-tiatesitfromthenormalPScm::Closure::Functionnew()methodisthatonLine75itprependsthesymbolthistotheargumentlistasitconstructstheclosure.Thatimplicit"argumentwillbesuppliedbyApplyMethod()whichyoucanalsoseeinthispackage.Thede ningfeatureofaclosureisthatitcapturesanenvironmentwhenitiscreatedandextendsitwhenitisexecuted.Thesemethodclosuresarenodi erent,buttheenvironmentthattheycaptureistheobjectinwhoseclassthemethodwasfound.Hencemethodbodiescanseethe eldsoftheobjectasnormalvariables:theyarenormalvariables.ApplyMethod()alsobehavesprettymuchlikethenormalclosure'sApply(),butitdi ersinhavinganextra$thisargument.OnLine85itcallsmapeval()ontheargument$formwiththecurrentenvironmenttogetalistofevaluatedarguments,justasthenormalclosure'sApply()does.ButthenonLine86itprepends$this(thePSchemethis)tothoseactualargumentswhencallingthegenericPScm::Closureapply()method.Thistiesinwiththenew()methodhavingsuppliedanextrasymbolthistothelistofformalarguments.We'venowcoveredeverythingtodowithPSchemeobjectcreationandinitialisationinPScheme.Alongthewaywe'veseen,byfollowingtheprocessofcallinganobject'sinitmethod,mostofthemachinerybehindmethodinvocation.Thereareonlytworemainingdetailsto llin. 148CHAPTER12.CLASSESANDOBJECTS12.2.4GeneralMethodInvocationThe rstofthoseisnormalPSchememethodinvocation.Thatisdonebyinvokingtheobjectwiththemethodnameandarguments,forexample:(my-accountdeposit10)Sinceobjects(environments)arenowdirectlyinvokeable,theytoomusthaveanApply()method,shownhere:176subApplyf177my($self,$form,$env)=@_;178179my($method_symbol,$args)=($form->first,$form->rest);180my$res=181$self->call_method($self,$method_symbol->value,$args,$env);182183if(defined$res)f184return$res;185gelsef186die"method",$method_symbol->value,"notfound ";187g188gOnLine179itsplitstheargument$formintoamethodname(asymbol)andalistofargumentstothemethod.ThenonLine181itattemptstocallthemethod,andcollectstheresult.Nowtheresultwillonlybeunde nedifamethodcouldnotbefound,inwhichcaseanexceptionisraised.Otherwisetheresultisreturned.12.2.5superMethodInvocationTheonlyremainingpieceisinvocationofaPSchememethodonasuperobject.superisboundtoaPScm::Env::Superinstance,whichinturnhasasuperbindingreferringtothenextobjectinthechain.Here'sPScm::Env::Super:191packagePScm::Env::Super;192193usebaseqw(PScm::Env);194195subApplyf196my($self,$form,$env)=@_;197198my($method,$args)=($form->first,$form->rest);199my$this=$env->LookUp(PScm::Expr::Symbol->new("this"));200my$res=201$self->call_method($this,$method->value,$args,$env);202203if(defined$res)f 12.2.IMPLEMENTATION149204return$res;205gelsef206die"method",$method->value,"notfoundinsuper ";207g208g2092101;IfyoucomparetheApply()methodherewiththeoneinPScm::Envabove,youcanseetheydi erinthatonLine199theApply()looksupthisinthecurrentenvironment.ThenonLine201itpasses$thisinsteadof$selftocallmethod().Theupshotofthatisthevariable$thiswillbetheonethatgetsboundtotheimplicitthisargumenttothePSchememethodwhenitisinvoked.12.2.6WiringitupAnd nally,wejustneedtoseehowthenewobjectcodeiswiredintotherepl.Here'sReadEvalPrint()forversion0.0.9ofourinterpreter.032subReadEvalPrintf033my($infh,$outfh)=@_;034035$outfh||=newFileHandle(">-");036my$reader=newPScm::Read($infh);037my$initial_env=newPScm::Env(038let=>newPScm::SpecialForm::Let(),039'*'=>newPScm::Primitive::Multiply(),040'-'=>newPScm::Primitive::Subtract(),041'+'=>newPScm::Primitive::Add(),042if=>newPScm::SpecialForm::If(),043lambda=>newPScm::SpecialForm::Lambda(),044list=>newPScm::Primitive::List(),045car=>newPScm::Primitive::Car(),046cdr=>newPScm::Primitive::Cdr(),047cons=>newPScm::Primitive::Cons(),048letrec=>newPScm::SpecialForm::LetRec(),049'let*'=>newPScm::SpecialForm::LetStar(),050eval=>newPScm::SpecialForm::Eval(),051macro=>newPScm::SpecialForm::Macro(),052quote=>newPScm::SpecialForm::Quote(),053'set!'=>newPScm::SpecialForm::Set(),054begin=>newPScm::SpecialForm::Begin(),055define=>newPScm::SpecialForm::Define(),056'make-class'=>newPScm::SpecialForm::MakeClass(),057);058059$initial_env->Define(060PScm::Expr::Symbol->new("root"), 150CHAPTER12.CLASSESANDOBJECTS061PScm::Class::Root->new($initial_env)062);063064while(defined(my$expr=$reader->Read))f065my$result=$expr->Eval($initial_env);066$result->Print($outfh);067g068gThechangesareinbold.OnLine41I nallycavedinandaddedprimitiveadditionasabuiltin.Ileaveittothereadertodothesame.OnLine56youcanseetheadditionalbindingofmake-classtoaPScm::SpecialForm::MakeClassobject,andonLine59weattachanewPScm::Class::Roottothesymbolrootintheinitialenvironment.ThatneedstobedoneusingDefine()becauseweneedtopassthevalueof$initialenvtothenew()methodofPScm::Class::Root12.3SummaryandVariationsThisobjectextensionaddednewmethodstoPScm::Envtoallowenvironmentstobehaveasopera-torswithinthelanguage.ItaddednewclassesPScm::SpecialForm::MakeClasstoimplementthemake-classspecialform,PScm::ClassandPScm::Class::RoottohostourPSchemeclasscode,PScm::Env::SupertoprovideanalternativeApply()methodforsupermethodinvocation,andPScm::Closure::Methodforthemodi edclosurebehaviourofPSchememethods.Tosummarise:APScm::SpecialForm::MakeClassobjectboundtomake-classintheinitialenvironmentcre-atesinstancesofPScm::Classwhencalledwithargumentsparent,fieldsandmethods.APScm::Class::Rootobjectboundtorootintheinitialenvironmentprovidesabaseclassinwhichotherclassescanberooted.PScm::ClasshasanApply()method,andwhenaPScm::Classisinvokedwitharguments,thatApply()method rstcreatesaPScmobject,whichisinfactjustaninstanceofaPScm::Env,thencallsthatnewobject'sinitmethodwiththeargumentsthatwerepassedtotheclass.Tocreateanewobject(environment)PScm::Class'sApply()callsmakeinstance()whichre-cursesdownthechainofPSchemeclassestotherootandcreatesachainofobjects(environments)onthewaybackup{EachelementofthischainhasaclassbindingreferringtothePScm::Classinstancethatcreatedit.{EachelementisjoinedindirectlytothepreviousbyasuperbindingreferringtoaPScm::Env::Superobjectwhichitselfhasasuperbindingreferringtotheactualparentobject.{Eachobjectinthechainextendstheenvironmentthatitsclasscapturedwhenitwascreated.TocallaPSchememethod,initorotherwise,thecallmethod()methodofPScm::Envisused.Thisuseslookupmethod()tolocatethemethodandcreateaninstanceofPScm::Closure::Methodfromit.Ifamethodisfoundcallmethod()invokesthemethod'sApplyMethod(). 12.4.TESTS151lookupmethod()looks rstinthecurrentenvironmentforaclassbindingandiffoundcheckstheclassforthemethod,otherwiseitrecursesonthesuper eld.Apply()inPScm::Envpasses$self(theobjectonwhichthemethodisbeinginvoked)asthevalueofthistocallmethod().TheApply()methodofPScm::Env::Superinsteadlooksupthevalueofthisinthecurrentenvironmentandpassesthatasthevalueofthistocallmethod.WhenaPSchememethodisfoundinaPSchemeclass,thePScm::Classmethodgetmethod()createsaninstanceofPScm::Closure::Method,aclosurewhichcapturestheenvironment(ob-ject)inwhoseclassthemethodwasfound,andwhichhasanadditionalimplicitselfargument.WhenthemethodbodyisexecutedbyPScm::Closure::Method'sApplyMethod()thevalueofthisispassedasanadditionalargument.SincethenewPScm::Classhasa letoitselfthere'safulllistinginListing12.5.1onpage153.Torecap,let'sconsiderouroriginalexampleclasses:AccountandInterestAccountFigure12.3onthefollowingpageshowsthesituationafterthecreationoftheAccountandInterestAccountclasses,andthemy-accountinstanceofanInterestAccountthatwasdiscussedintheexamplesinSection12.1onpage135.Youcanseethatthemy-accountobjectisreallyjustaPScm::Envanditsparentenvistheglobalenvironment(impliedbytheunterninatedheavyarrows.)Themy-accountobject'sparentenvironmentistheglobalenvironmentbecausethatistheenvironmentthattheInterestAccountclasswascreatedin.IftheInterestAccountclasshadcapturedadi erentenvironment,thenthatwouldhavebeentheonethatinstancesofthatclassextended.Notethethreebindingsinthemy-accountobject.Theratevariableistheonesuppliedbytheclassde nition,theothertwo,superandclassareautomaticallyprovidedbytheimplementationwhennewobjectsarecreated.ThesupervariablereferstoaPScm::Env::Superobject,derivedfromPScm::Env,whichinturnhasasupervariable,anddi ersfromPScm::EnvonlyinitsApply()method,whicharrangestoforwardthecurrentvalueofthis(ratherthanthesuperobjectitself)tothecalledmethod.TheclassvariablereferstoaPScm::Classobjectwhichcontains eld(variable)names,methodnamesalongwiththeirde nitions,theenvironmentthatwascurrentatthetimeofthecreationofthePScm::Class,andaparent eldpointingattheparentPSchemeclass.12.4TestsTestsforourOOextensionareinListing12.5.2onpage155.The rsttestexercizesthecreationofaclass.Thesecondtestcreatesaclass(ouraccountclassfromtheexamplesabove)thencreatesanobjectfromitandcallsacoupleofitsmethods.Thethirdtestusestheinterest-accountexamplethatwe'velookedattotestinheritance.Thefourthtestdemonstratesthatlexicalvariablesoutsideofaclassarevisibletoitsmethodsandcanthereforebeusedasclassvariables.Finally,the fthtestusesanabstractformofthatleo"exampletodemonstratethatmethodcallsonasuperobjectpersistthecurrentvalueofthis. 152CHAPTER12.CLASSESANDOBJECTSFigure12.3:Exampleclassesandobjectsmy-accountPScm::Envinterest-accountrate2PScm::Env::SuperPScm::Classsuperclasssuperfields:ratemethods:accumulateinitenvparentPScm::Envaccountamount20PScm::Env::SupersuperPScm::Classclasssuperfields:amountmethods:depositwithdrawbalanceinitenvPScm::EnvparentclassrootPScm::Class::Rootenv 12.5.LISTINGS15312.5Listings12.5.1PScm/Class.pm001packagePScm::Class;002003usestrict;004usewarnings;005usebaseqw(PScm);006007subnewf008my($class,$parent,$fields,$methods,$env)=@;009010my$rhmethods=fg;011$class->populatemethodshash($rhmethods,$methods);012013returnblessf014parent=>$parent,015fields=>[$fields->value],016methods=>$rhmethods,017env=>$env,018g,$class;019g020021subpopulatemethodshashf022my($class,$rhmethods,$methods)=@;023if($methods->ispair)f024my$method=$methods->first;025my($name,$args,$body)=$method->value;026$rhmethods->f$name->valueg=027fargs=>$args,body=>$bodyg;028$class->populatemethodshash($rhmethods,$methods->rest);029g030g031032subApplyf033my($self,$form,$env)=@;034035my$newobject=$self->makeinstance();036$newobject->callmethod($newobject,"init",$form,$env);037return$newobject;038g039040submakeinstancef041my($self)=@;042043my$parentinstance=$self->fparentg->makeinstance();044045return$self->fenvg->ExtendUnevaluated(046newPScm::Expr::List(047PScm::Expr::Symbol->new("class"),#$self048PScm::Expr::Symbol->new("super"),#$parentinstance049@f$self->ffieldsgg,#0... 154CHAPTER12.CLASSESANDOBJECTS050),051newPScm::Expr::List(052$self,#"class"053PScm::Env::Super->new(super=>$parentinstance),#"super"054((PScm::Expr::Number->new(0))x@f$self->ffieldsgg),#field...055)056);057g058059subgetmethodf060my($self,$methodname,$object)=@;061062if(exists$self->fmethodsgf$methodnameg)f063returnPScm::Closure::Method->new(064$self->fmethodsgf$methodnamegfargsg,065$self->fmethodsgf$methodnamegfbodyg,$object);066g067g068069##########################070packagePScm::Class::Root;071072usebaseqw(PScm::Class);073074subnewf075my($class,$env)=@;076077returnblessf078parent=>0,079fields=>[],080methods=>fg,081env=>$env,082g,$class;083g084085submakeinstancef086my($self)=@;087088return$self->fenvg089->ExtendUnevaluated(090newPScm::Expr::Symbol("class"),091$self092);093g0940951; 12.5.LISTINGS15512.5.2t/PScmOO.t001usestrict;002usewarnings;003useTest::More;004uselib't/lib';005usePScm::Testtests=>6;006007BEGINfuseok('PScm')g008009010evalok(<(...).Fora rstexampleofcpstransformation,we'llgobacktoouroriginalfactorial()functionfromSection13.1onpage161andre-writeitincps.Tosaveyouhavingtoreferbacktoit,hereitisagain.subfactorialfmy($n)=@_;if($n==0)freturn1;gelsefreturn$n*factorial($n-1);ggprintfactorial(5)," ";#prints120Nowaswehavesaid,allcpsfunctionstakeanadditionalcontinuationargument.Thecontinuationwepassitdependsonwhatwewanttodowiththeresult.Ouroriginalexampleprintedtheresult,solet'sjustpassacontinuationtodothat:factorial(5,subfprintshift," "g);Theadditionalcontinuationargumentsubfprintshift," "gjusttakesoneargumentandprintsit.Nextupisfactorial()itself.Thiscpsfactorial()takesanadditionalcontinuationasargument,sothe rstcoupleoflinesareeasy:subfactorialfmy($n,$cont)=@_;Nextrememberthatwheneverthefunctionusedtoreturnaresult,itmustnowcallitscontinuationonthatresult,sothenextcoupleoflinesarealsoprettyeasy:wherastheoriginalreturned1if$nwas0,thecpsversioncallsitscontinuationonthevalue1instead. 13.2.CONTINUATIONPASSINGSTYLE167if($n==0)f$cont->(1);Thisworksforfactorial(0,subfprintshift," "g):thecontinuationwillgetandprinta1.Thatleavesthetrickybit.Theoriginalfunctionreads:gelsefreturn$n*factorial($n-1);gYoucanseethatrecursivecalltofactorial()hassomedeferredcomputation,namelythemultiplicationby$ntobedonewhenthecallreturns.Butaswe'vesaidacpsfunctionneverreturnssowemustsomehowwrapthatdeferredcomputationupinanewcontinuationandpassittofactorial().Ifyougetstuckonadicultcpstransform,italmostalwayspaystobreaktheexpressionintoasequenceofsimpleroperations rst.Wecandothathereeasilyenough:gelsefmy$factorial_result=factorial($n-1);my$result=$n*$factorial_result;return$result;gSothisismucheasiernow.Youcanseethatthe rstthingthathappensisthatfactorial()callsitself.Thentheresultismultipliedby$n,and nallyitisreturned.Soournewcontinuationisjustthecodethatnowfollowsthecalltofactorial(),wrappedinafunction:subfmy($factorial_result)=@_;my$result=$n*$factorial_result;return$result;gSincefactorial()willcallthiscontinuationwithitsresult,$factorialresultistheargumenttothecontinuation.Thereisoneadditionalchangethatweneedtomake.Wheretheoriginalcodedidareturn$result,ournewcontinuationmustcalltheoriginalcontinuationonthe$resultinstead.subfmy($factorial_result)=@_;my$result=$n*$factorial_result;$cont->($result);gThisisournewcontinuation.Allthatremainsistopassittoourrecursivefactorialcall: 168CHAPTER13.CONTINUATIONSgelseffactorial($n-1,subfmy($factorial_result)=@_;my$result=$n*$factorial_result;$cont->($result);g);gWecannowshortenthisconsiderablybyeliminatingthetemporaryvariables:gelseffactorial($n-1,subf$cont->($n*shift)g);gThenewcontinuationissubf$cont->($n*shift)g.Ittakesoneargument:theresultsofar(thisisthevaluethatournon-cpsfactorial()wouldhavereturned).Itmultipliestheresultbythecurrentvalueof$nthencallsthecurrentcontinuation$contonthatvalue3.Thatcompletesourinitialcpsre-implementationoffactorial():subfactorialfmy($n,$cont)=@_;if($n==0)f$cont->(1);gelseffactorial($n-1,subf$cont->($n*shift)g);ggfactorial(5,subfprintshift," "g);#stillprints120Attheriskoflabouringapoint,considerthecall:factorial(3,subfprintshift," "g);Theevolutionofthecontinuationwillproceedasfollows:3Actually,ifwewereimplementinginrabid,purecps,theneventheprimitivessuchasmultiplicationwouldtakeacontinuation.Wecouldsimulatethatherebyre-writingourcontinuationas:sub{times($n,shift,$cont)}wheretimes()issubtimes{my($x,$y,$cont)=@_;$cont->($x*$y);}whichatleastemphasizesthatthecontinuationisstillbeingpassed,thoughitisoverkillforourpurposes. 13.2.CONTINUATIONPASSINGSTYLE169subfprintshift," "gsubfsubfprintshift," "g->(3*shift)gsubfsubfsubfprintshift," "g->(3*shift)g->(2*shift)gsubfsubfsubfsubfprintshift," "g->(3*shift)g->(2*shift)g->(1*shift)gsubfsubfsubfsubfprintshift," "g->(3*shift)g->(2*shift)g->(1*shift)g->(1)#factorial0subfsubfsubfprintshift," "g->(3*shift)g->(2*shift)g->(1) 170CHAPTER13.CONTINUATIONSsubfsubfprintshift," "g->(3*shift)g->(2)subfprintshift," "g->(6)print6," "Thedeferredmultiplicationsaccumulateuntilwereachthepointwheretheentireaccumulatedcontinua-tionis nallycalledwithargument1,thentheyunwindfromtheoutsideinuntiltheoriginalcontinuationgetsinvokedontheargument6and6isprinted.Ifyouthinkaboutit,thisevolutionisfunctionallyidenticalwiththeimplicitdeferredcomputationsonthestackinouroriginalfactorial(),theonlydi erencebeingthatnowwehaveavariable$contthatexplicitlyreferstothecontinuation.Stillstickingwithourcpsfactorial(),thereismorethatwecando.Becauseincpsnofunctioneverreturns,allfunctioncallsmustbeintailposition!4.Asyoucanseeourrecursivecalltofactorial()isnowintailposition,sowecanusetcotoremovethespurioususeofstack:subfactorialfmy($n,$cont)=@_;if($n==0)f$cont->(1);gelsef@_=($n-1,subf$cont->($n*shift)g);goto&factorial;ggThisisstillarecursive"de nitionoffactorial(),butnowitisnotthestackwhichisgrowing,butthecontinuationitselfwhichconsumesmoreandmorespaceasourcomputationproceeds.Anastutereaderwillhaverealisedthat,infact,wearestillusingstackwhenthecontinuationsactuallygettriggered:thosecallsto$cont->($n*shift)willofcourseusejustasmuchstackastheoriginaldid.Howevernotethatthecontinuationsthemselvesmustbecalledintailposition,sowithalittlemoreworkwecaneliminatethatstackoverheadtoo:subfactorialfmy($n,$cont)=@_;if($n==0)f@_=(1);goto$cont;gelsef@_=($n-1,subf@_=($n*shift);goto$cont;g);4It'sobviousreally:Sincenocpsfunctioneverreturns,anydeferredcomputationmusthavebeenmovedintothecontinuation,andafunctioncallwithoutdeferredcomputationisbyde nitionintailposition. 13.2.CONTINUATIONPASSINGSTYLE171goto&factorial;ggThisisverymessy,butitworksasadvertised:itconsumesabsolutelynostackatanypoint;alldeferredcomputationsareinthecontinuations.Justbearinmindthatinalanguagethatprovidedimplicittco,wewouldn'tneedanyofthoseassignmentsto@orthegotos,andI'vepromisedthatcontinuationsthemselveswillallowanalternativeandcleanersolutioninourinterpreter.Movingon,whataboutthatiterative/recursivede nitionoffactorial()withahelperfunctionfromSection13.1onpage161?Wecanre-writethatincpstoo.Howdoesitcompare?Well rsthere'sanon-cpsvariationontheoriginalagain,thoroughlytco'dthistime:subfactorialfmy($n)=@_;@_=($n,1);goto&factorial_helper;gsubfactorial_helperfmy($n,$result)=@_;if($n==0)freturn($result);gelsef@_=($n-1,$n*$result);goto&factorial_helper;ggprintfactorial(5)," ";#prints120andhereitisre-writtenincps:subfactorialfmy($n,$cont)=@_;@_=($n,1,$cont);goto&factorial_helper;gsubfactorial_helperfmy($n,$result,$cont)=@_;if($n==0)f@_=($result);goto$cont;gelsef@_=($n-1,$n*$result,$cont);goto&factorial_helper;g 172CHAPTER13.CONTINUATIONSgfactorial(5,subfprintshift," "g);#stillprints120Ournewtailrecursivecpsfactorial()functiontakesanadditionalcontinuationargumentandpassesthattofactorialhelper().factorialhelper()eithergoestothecontinuationwiththeresult,orgoestoitselfwithnewvaluesfor$nand$result;butsinceithasnodeferredcomputation,itdoesnotneedtoconstructanewcontinuationandjustpassestheexistingcontinuationtotherecursivecall.Thetakehomemessagehereisthatthistailrecursivede nitionoffactorial()usingfactorial-helper()translatesintoacpswhereneitherthestacknorthecontinuationgrows.Thisisageneralresult:functionswrittentobetail-recursiveconsumenostackwhentco'd,anddonotbuildnewcontinuationswhenrewrittenintocps.The ormal"way,oratleasttheeasiestwaytoproducecpscodeistodowhatwedidabove:takenon-cpscodeandtranslateitintocps.Inthenextsectionwe'regoingtolookatafewexamplesofsimple,hypotheticalfunctionformsandhowtheytranslateintocps.13.3ExamplecpsTransformationsTokeeptheseexamplessimple,we'llignoreanyissuesoftco.Theseexamplesshouldallowustoproceedwithmorecon denceintothesubsequentrewriteofourinterpreter.Thesimplestkindoffunctionisonethattakesnoargumentsandreturnsaconstant:subAfreturn'hello';gThecpsformofthistakesacontinuationasargumentandcallsthecontinuationontheconstant:subAfmy($ret)=@_;$ret->('hello');gI'vecalledthecontinuation$retinsteadof$conttoemphasizeit'sequivalencewithareturnstatement.Oneoftheguidingprinciplesofconvertingtocpsisthatcallingtheargumentcon-tinuationincpsisequivalenttodoingareturninnon-cps.Infactyoucanmentallysubstitutereturn(...)for$ret->(...)inmanyoftheseexampleswithoutdisturbingthesenseofthem.Thenextsimplestformisafunctionthattakesargumentsandperformsonlyprimitiveoperationssuchasadditiononthem,returningtheresult: 13.3.EXAMPLECPSTRANSFORMATIONS173subAfmy($x,$y)=@_;return$x+$y;gInthiscase,sinceprimitiveoperationscan'ttakeacontinuation,andbecausetheyareterminal"operationsthatwon'trunawayo upthestack,weagaincanjustcallthecontinuationontheresult:subAfmy($x,$y,$ret)=@_;$ret->($x+$y);gNextcomesimplefunctionsthatjustcallanotherfunctionwithoutanydeferredcomputation:subAfmy($x)=@_;returnB($x);gHere,sincethereisnodeferredcomputation,thereneedbenonewcontinuation,wejustpasstheexistingcontinuationtothecalledfunction:subAfmy($x,$ret)=@_;B($x,$ret);gThisisjustsayingtoB()returnyourresulthere."Nowwe'restartingtogetintoareaswherethereisdeferredcomputation,andthisiswhereitstartstogetjustalittlebittricky:subAfreturnC(B());gB()getscalled rst,andthevalueitreturnsispassedasargumenttoC().IncpsB()wouldneverreturnsowemustalsocallit rst,passingitanewcontinuationthatcallsC()withB()'sresultandthecurrentcontinuation: 174CHAPTER13.CONTINUATIONSsubAfmy($ret)=@_;B(subfmy($B_result)=@_;C($B_result,$ret);g);gThenewcontinuationcallsC()withtwoarguments:theresultofthecalltoB(),andtheoriginalcontinuation$rettowhichC()shouldreturnitsresult.SincetheoriginalcalltoC()wasreturnedastheresultofthecalltoA(),C()isbeingtoldreturnyourresulthere".Sequentialfunctioncallspresentaslightlydi erentproblem.subAfB();C();D();gHerewemustconstructanestofcontinuationstoensurethatC()andD()getcalledinthecorrectorderafterB():subAfmy($ret)=@_;B(subfC(subfD($ret);g);g);gWecallB()withacontinuationthatwillcallC()withacontinuationthatwillcallD()withtheoriginalcontinuation$retsincetheresultofthecalltoD()wastheresultoftheoriginalcalltoA().AgainthisisjustsayingtoD()returnyourresulthere."Nextlet'slookataconditionalexpression: 13.3.EXAMPLECPSTRANSFORMATIONS175subAfmy($x)=@_;if(B($x))fC($x);gelsefD($x);ggThecalltoB()intheconditionwillneverreturn,sowemustpassitacontinuationthattestsitsresultanddecideswhichbranchtotakeaccordingly,passingthetheoriginalcontinuationtothechosenbranch:subAfmy($x,$ret)=@_;B($x,subfmy($B_result)=@_;if($B_result)fC($x,$ret);gelsefD($x,$ret);gg);gBoththetrueandthefalsebranchusedtomakeasinglecallintailpositiontoC()orD(),sonowwesimplypasstheoriginalcontinuationunchangedasanadditionalargumenttoC()orD().Finally,fornow,letslookataloopingfunction.subAfmy$i=0;while($i<10)fB();++$i;ggThisneedsabitmorethought.Itturnsouttobeeasiesttodoapreliminaryrewriteofthisexampleintoarecursiveformasfollows: 176CHAPTER13.CONTINUATIONSsubAfA_h(0);gsubA_hfmy($i)=@_;if($i<10)fB();A_h($i+1);ggTurningthatintocpsthenbecomesjustare-applicationofexampleswe'veseenbefore:subAfmy($ret)=@_;A_h(0,$ret);gsubA_hfmy($i,$ret)=@_;if($i<10)fB(subfA_h($i+1,$ret);g);ggA()callsAh()withthecontinuationunchanged(Ah(),returnyourresulthere.)SinceB()willnotreturn,itispassedacontinuationthatcarriesontherecursiononAh(),passingtheoriginalcontinuation$ret.Theexamplesabovegiveatasteofthesortsoftransformationsthatweshallbeapplyingtoourinterpretersoon.Thereareothermoredicultcasesthatmightappearimpossibleat rstsight(usesofmapforexample,)butagaintheycanberesolvedby rstre-writingtheexpressionsinamoretractableformbeforeconvertingtocps.We'llseeexamplesofthissortofthingwhenwegettothem.Ithappensthattheredoesexistaformalmethodologyfortransformingstatementsinanylanguagecapableofsupportingcpsintocps.Theaboveexampletransformationsaresamplesfromthatruleset.Allsuchtransformationscanbeautomated.WhenIstartedthischapterIwashopefulthatperhapssomethingintheBpackage,thePerlcompiler,wouldbeavailablethatcouldperformthetransformbutthatappearsnottobethecase.Anyhowwe'lllearnalotmoreaboutcpsbyperformingthetransformmanually,sothatisthebestapproachtotake.13.4TheTrampolineIpromisedthattherewasanalternativetoallthemessyassignmentsto@andthegotosthatconstitutetco.Wellthatfallsoutofthreecloselyrelatedpropertiesofafullyrealisedcps: 13.4.THETRAMPOLINE1771.Nofunctioncalleverreturns,therefore:2.Everyfunctioncallmustbeintailposition,andtherefore:3.Ifyouweretoreturnsomethingitwouldbeguaranteedtoreturnallthewaydownthestacktotheoriginatingcaller5.Nowjustsupposethatatwellchosenpointswedoreturnsomething,andnotjustanything.Supposewereturnanothercontinuation,thistimetakingnoarguments,thatwhencalledjustcontinuesthecalculationfromwhereitlefto !Thatisoneofthesurprisingthingsaboutcontinuations,thattheyarecompletelyself-containedandrequirenoexternalcontexttooperate.Youmayneedtoconvinceyourselfthatthiswillwork:Sincewecantcoacpsfunction,suchthatitusesnoPerlstackatall,thenevenifthecpscodeisnottco'dtherecanbenothingonthePerlstackthatitactuallyneeds,justalongchainofreturnadressesthatitpasesthroughafterthecomputationis nished.Returningacontinuationlikethismerelyinterleavesthisotherwiselaboriouschainofreturnswiththenormal owofcontrolupthestack.Sohowdowedealwiththisreturnedcontinuation?Ahandlerroutine,calledatrampoline,startso bybeingcalledwithacontinuationofnoarguments.Itloops,repeatedlycallingthecontinuationandassigningtheresult(anothercontinuationofnoarguments)backtothecontinuationitselfuntiltheresultisundef.Thecodeiseasiertowritethantodescribe:subtrampolinefmy($cont)=@_;$cont=$cont->()whiledefined$cont;gTogiveyouafeelofhowthismightwork,let'sreturnoncemoretoourcpsfactorial()functionandre-writeittomakeuseofatrampolineinsteadoftco.Firsttorefreshyourmemoryhere'sour rstcpsattemptagain(slightlymodi ed)beforewetco'dit:subfactorialfmy($n,$ret)=@_;if($n==0)f$ret->(1);gelseffactorial($n-1,subfmy($a)=@_;$ret->($n*$a)g);ggfactorial(5,subfprintshift," ";g);#stillprints120andhereitisrewrittentouseatrampoline.5Actually,we'realsorelyingonthefactthatperlimplicitlyreturnsthevalueofatailcallasthevalueofafunction. 178CHAPTER13.CONTINUATIONSsubfactorialfmy($n,$ret)=@_;if($n==0)freturnsubf$ret->(1);g;gelsefreturnsubffactorial($n-1,subfmy($a)=@_;returnsubf$ret->($n*$a)gg);g;ggsubtrampolinefmy($cont)=@_;$cont=$cont->()whiledefined$cont;gtrampoline(subffactorial(5,subfprintshift," ";returnundef;g);g);#stillprints120Changesfromtheoriginalareinboldasusual.Thekeytounderstandingthisistonoticethatwheneverafunctioncallwasdoneintheoriginal,eithertofactorial()ortothecontinuation,aclosurewhichwillmakethatcallisreturnedtothetrampolineinstead.Eachtimethishappensthestackiscompletelycleareddownandthetrampolineresumesthecomputationbycallingthereturnedclosure.Finally,attheendofthecomputation,theoriginalcontinuationpassedtofactorial()getsinvoked,printingtheresultandreturningundeftothetrampolinecausingittostop.Liketco,thetrampolinetechniqueisnotspeci ctocps,butbothtechniquesrequirethatthemodi edcallsbeintailposition,makingcpsaprimecandidateforeitherkindofoptimisation6.Wellthat'sprettyscarystu .Bothtcoandthetrampolinearesimplyalternativestrategiestoavoidunlimiteduseofthestack,andyoumaybewonderingifthetrampolinehasanyadvantagesovertcoatthispoint.I'dliketomakeafewargumentsinfavourofthetrampolinehere.1.Ourfactorial()exampleisaverytightpieceofcodewhichsomewhatoveremphasizestheroleofthetrampolinebydoingalotwithitinasmallspace.Particularilytheexplicitreturnofaclosuretomaketherecursivecalldoesnothavetobedoneforeverytailcall,wejustneedtoensureit6Thetrampolinefurtherrequiresthatallintermediatecallsalsobeintailposition,andthatthevaluesofalltailcallsarereturned,sothatareturnedvaluewouldbeguaranteedtoreachthetrampoline.Fortunately,cpssatis esthe rstoftheserequirements,andPerlsatis esthesecond. 13.5.USINGCPS179happensfairlyregularilyonourwayupthestack.Forexampleinasetofmutuallytailrecursivesubroutines,A()callingB()callingC()callingA()...,onlyoneofthosesubroutinesneeddothatreturn.Thisisincontrasttotco,whereanyunoptimisedtailcallconstitutesapermanentlyunclaimedstackframe.2.Somelanguagesdonotallowthepossibilityofdoingtco,soanycpsimplementationusingsuchalanguagewouldhavetouseatrampoline.3.Wecanhidethetrampolinefromclientcpscodebyrepresentingcontinuationsasobjectswhichcontaintheclosures,andputtingthereturntothetrampolineinthemethodthatinvokestheclosure(providedthatmethodisinvokedintailposition).Itisthethirdargumentthatswingsthecase,andthat'sexactlywhatwe'llbedoing.Ifyoudon'tgetthatargumentyet,holdonanditwillbemadeclearlater.13.5UsingcpsThinkingbacktoouroriginalexpositionofcpsfromSection13.2onpage164wherewesuggestedthatnormalproceduralprogrammingconsistedprimarilyofcallingfunctionsandreturningvalues,wesaidthatcpseliminatesthesecondofthesetwooperations.Infactthatwasaslightoversimpli cation.Thereisacertainamountofequivalencebetweentheoperationsofcall"andreturn",itisjustthedirectionofthe owofdatathatdi ers,upwards"tothecalledfunctionviaitsarguments,versusdownwards"fromthecalledfunctionviaitsreturnvalue.Continuationpassingstyleinfactuni esthesetwooperations,returningavalueisthesameascallingafunction.Sinceincpsdata owsinonlyonedirection,insomesensecpsisactuallyasimpli cation!Furthermore,anapplicationwrittenincpswithcompletetconeedsnostackatall:tcoallowsustoeliminatetheuseofstackbytailcalls,andincpsallfunctioncallsaretailcalls7.Sonowyouunderstandcontinuations,buthowdoyouusethem?Wellateachstepofacomputationyouhaveacontinuationrepresentingthecurrentfunction'sreturnstatement.Butacontinuationisavariable,areferencetoasubroutine,andyoucandowhateveryoulikewithit!.Youdon'thavetocallit(returnthroughit)justwheneveryoneisexpectingyouto,youmightcall(returnthrough)acompletelydi erentcontinuationinstead,oryoumightpassittoanotherfunctionthatcancallit(returnthroughit)ifitlikes.Andwhenacontinuationiscalled(returnedthrough),control owtransferstowhereverthereturnstatementequivalenttothatcontinuationwouldhavereturned!Putanotherway,youhavealwayshadcontroloverwhatvalueyourfunctionreturns,andwhenitreturnsit,butnotuntilnowhaveyouhadcontroloverwhereitreturnsitto!Andthere'smore.Althoughcodewrittenincpsretainsthenotionofastacksincefunctionscallfunctionsandreturnvalues(viacontinuations);aswe'vealreadynotedthestackisnotreallyrelevant,orevennecessarilypresent.Anycontinuationisasvalidasanyother.Itisperfectlypermissabletocallacontinuationthatresumescontrolinafunctionthathasalreadyreturned,ine ectjumpingacrossthecallgraphthatastackbasedlanguageisconstrainedby!Let'sgiveanexplicitexampletoillustratethislastpoint.Considerthefollowingsimpleperlscript:subAfprint"inA ";B();7ThisiswhatismeantbyStacklessPython":animplementationofthatlanguageincpswithcompletetco. 180CHAPTER13.CONTINUATIONSprint"backinA ";gsubBfprint"inB ";C();print"backinB ";gsubCfprint"inC ";gsubXfprint"inX ";Y();print"backinX ";gsubYfprint"inY ";Z();print"backinY ";gsubZfprint"inZ ";gA();X();A()callsB()whichcallsC(),andX()callsY()whichcallsZ().ThetoplevelcallsA()thenX().Youshouldn'ttaketoolongtoconvinceyourselfthatitwillproducethefollowingoutput:inAinBinCbackinBbackinAinXinYinZbackinYbackinXJusttohammerhomethesimplepoint,Figure13.4onthefacingpageshowsthethreadofcontrol owpassingthroughA(),B(),C(),X(),Y()andZ(). 13.5.USINGCPS181Figure13.4:Control owforthesimplescriptCZBYAXNowlet'sre-writethatprogramintocps,withoutchanginganyofit'sbehaviour:subAfmy($ret)=@_;print"inA ";B(subf$ret->(print"backinA ")g);gsubBfmy($ret)=@_;print"inB ";C(subf$ret->(print"backinB ")g);gsubCfmy($ret)=@_;$ret->(print"inC ");gsubXfmy($ret)=@_;print"inX ";Y(subf$ret->(print"backinX ")g);gsubYfmy($ret)=@_;print"inY ";Z(subf$ret->(print"backinY ")g);g 182CHAPTER13.CONTINUATIONSsubZfmy($ret)=@_;$ret->(print"inZ ");gA(subfX(subfg)g);Therearenonewtricksthathaven'talreadybeendescribedinSection13.3onpage172above,theonlydi erenceisthatsincenoneoftheoriginalfunctionsactuallyreturnedanythinginteresting(theyreturnedtheresultsofprintstatements),theequivalentcontinuationsdon'tbotherlookingattheirarguments.Thisproducesidenticaloutputtotheoriginalprogram,andexhibitsexactlythesamecontrol ow.Nowlet'smakejustthreetinychanges.my$C_ret;subAfmy($ret)=@_;print"inA ";B(subf$ret->(print"backinA ")g);gsubBfmy($ret)=@_;print"inB ";C(subf$ret->(print"backinB ")g);gsubCfmy($ret)=@_;$C_ret=$ret;$ret->(print"inC ");gsubXfmy($ret)=@_;print"inX ";Y(subf$ret->(print"backinX ")g);gsubYfmy($ret)=@_;print"inY ";Z(subf$ret->(print"backinY ")g);gsubZfmy($ret)=@_; 13.5.USINGCPS183$C_ret->(print"inZ ");gA(subfX(subfg)g);The rstchangeistodeclarea$Cretvariabletoholdacontinuation.ThenC(),beforeitcallsitscontinuation,storesitinthis$Cretvariable.FinallyZ(),insteadofcallingitsowncontinuation$ret,callsthesavedcontinuation$Cretinstead.Thisproducestheoutputbelow.Whetherornotyou ndthissurprisingwilldependonhowcloselyyou'vebeenfollowingthediscussion:inAinBinCbackinBbackinAinXinYinZbackinBbackinAinXinYinZbackinBbackinAinXinYinZbackinBbackinA...Allproceedsnormallyuntilwereachthe rstcalltoZ().SinceZ()callsthecontinuationthatC()saved,Z()insteadofreturningtoX(),returnstoB()instead.Thennormalserviceisresumed,startingfromthereturntoB(),untilthenextreturnfromZ(),whichagainreturnstoB()andsoon,adin nitum.whatwehaveachievedisthecontrol owshowninFigure13.5onthefollowingpage.(CuetheMonyPythonmusic.)Ifthisstillisn'tclear,whichIsuspectmaybethecase,lookatFigure13.6onthenextpage.Inthis gureI'vebrokenapart"thefunctionsfromtheircontinuations.A()callsB()callsC()whichcallsthecontinuationofB()(e.g.cB())whichcallsthecontinuationofA()etc.NowthecontinuationofB()isjustreturntoA()"(callcA())andthecontinuationofA()istocallX()etc.I'mdeliberatelydown-playingtheideaofreturn"now,thisreallyisjustfunctioncalls,andinthatcaseFigure13.7onthefollowingpageshowsthatthereisreallynothingspecialaboutZcallingcB,it'sjustarecursiveloop,andtcooratrampolinewilltakecareofthestackforus.ThisiswhatImeantbysayingthatcpsisasimpli cation.Itlinearizescontrol ow,sothatitisjustastraightlineoffunctioncalls.Onceyougetthatidea,awholeworldofpossibilitiesopensup.For 184CHAPTER13.CONTINUATIONSFigure13.5:Control owwithcontinuationsCZBYAXFigure13.6:Continuationsarejust(anonymous)subroutinesCZBcBYcYAcAXcXFigure13.7:ContinuationsreallyarejustsubroutinesABCcBcAXYZcYcXinstanceyoucanprobablyimagineatthisstagethatwithalittlemorework,addingloopsandpassingcontinuationsaround,wecouldeasilyarriveatacoroutineimplementation,wherecontroldoesjumpfromtheheartofonelooptotheheartofanotherandbackagainwithoutdisturbingthestateofeitherloop. 13.5.USINGCPS185Thereisabigdownsidetowritingincpshowever,andthatisthatitmakesyourheadhurt.Afarbetterapproachistousealanguagethathascontinuationsbuiltinunderthehood".Thenwhenyouwritereturn$val"youarereallycallingacontinuationon$val,butyoudon'thavetoworryaboutit,andwhenyouneedtogetholdofacontinuation,youcanaskforone.Alanguagelikethatprovidescontinuationsas rstclassobjectsinthattheycanbepassedaroundasvariables,muchinthesamewayasPerlprovidesanonymoussubroutines(closures)as rstclassobjects.Forexample,ifPerlhadbuilt-incontinuations,andwecouldgetatthecurrentcontinuationbyi.e.takingareferencetothereturnkeyword8,thenwecouldrewriteallofthisexamplewithoutcps,asfollows:my$cont;subAfprint"inA ";B();print"backinA ";gsubBfprint"inB ";C();print"backinB ";gsubCf$cont=return;print"inC ";gsubXfprint"inX ";Y();print"backinX ";gsubYfprint"inY ";Z();print"backinY ";gsubZfprint"inZ ";$cont->()g8ThankstoTomChristiansenforthisidea. 186CHAPTER13.CONTINUATIONSA();X();Boldtextshowsthedi erencesfromtheoriginalnon-cpsversion.WearegoingtoturnourPSchemeinterpreterintojustsuchalanguage.Thenextfewsectionswilldescribethechangesweneedtomake.13.6ImplementationRatherthanattemptingtorewritetheinterpreterofChapter12onpage135fromstartto nishincps,We'regoingtobacktracktoour rstinteresting"interpreter,fromChapter5onpage59whichhasonlyletandlambda,andre-implementthat.Thishastheadvantagethatwegetarealworkinginterpreterwithcontinuationswhichwecantestearlyon,andwecandemonstratesomeofthepowerofcontinuationswithit.ThenI'llglossthere-writingofthe nalinterpreterinstagesbyworkingthroughtheintermediateversionspausingonlytostudyanypreviouslyunencounteredconstructsthatrequirenoveltreatment.Finallywe'llhaveacontinuation-passingversionoftheinterpreterfromChapter12onpage135toplaywith.13.6.1OurTrampolineImplementationOurimplementationofthetrampolinedoesnotdi ersigni cantlyfromtheexamplethatwepresentedforfactorial()above.Butitisstillbestintroducedgradually,sothissectionisstillpseudocode,toacertainextent.Firstlyweneedtorewritetheread-eval-printloopintocps,sothatwecancallthewholethingfromthetrampoline.Thisisn'tactuallyverydiculttodo,thereplforversion0.0.2conceptuallyisassimpleassubreplfmy($reader,$outfh)=@_;while(my$expr=$reader->read())fmy$result=$expr->Eval(newenv);$result->Print($outfh);ggWe'vealreadyseeninSection13.3onpage172thattheeasiestwaytotransformawhileloopintocpsis rsttorewriteitintoarecursiveform,andthisiseasytodohere:subreplfmy($reader,$outfh)=@_;if(my$expr=$reader->read())fmy$result=$expr->Eval(newenv);$result->Print($outfh);repl($reader,$outfh);gg 13.6.IMPLEMENTATION187Nowtorecastthatintocpsisfairlytrivial,especiallyifwerememberthatthereaderPScm::Read::Read()alreadyreturnsundefoneof,anditcancontinuetodoso,tellingthetrampolinetostop,andonlycallingitscontinuationifthereissomethingtoevaluate.subreplfmy($reader,$outfh,$ret)=@_;$reader->read(subfmy($expr)=@_;$expr->Eval(newenv,subfmy($result)=@_;$result->Print($outfh,subfrepl($reader,$outfh,$ret)g)g)g)gSoapartfromthereturnofundefbythereader,wherewouldweputthesereturnstatementsthatreturnacontinuationtothetrampoline?WellasI'vesaidwecouldplacethemthroughoutthecode,butthere'sabetteridea.Insteadofcontinuationsbeingsimpleanonymoussubroutines,wemakethemintoobjectsthatcontainthoseanonymoussubroutines,withaCont()methodtoinvoketheunderlyingclosure.Theninsteadofjustwriting:$ret->($arg);toinvokeacontinuation,wesay:$ret->Cont($arg);Then,inthatCont()method,insteadofjustsayingsubContfmy($self,$arg)=@_;$self->fcontg->($arg);gweinsteadwritesubContfmy($self,$arg)=@_;returnsubf$self->fcontg->($arg)g;g 188CHAPTER13.CONTINUATIONSwehavebothe ectedthereturnofacontinuationtothetrampoline,andcompletelyhiddenthefactfromtheclientcode9!Inrealitythereareafewminorcomplicationswiththisapproach,buttheabovediscussionisveryclosetoour nalimplementation.13.6.2cpsletandlambdaInthissectionwere-implementtheinterpreterversion0.0.2fromChapter5onpage59incontinuationpassingstyle.Oncethatisdone,weintroduceanewconstruct,call/cc,whichallowsthelanguagedirectaccesstocontinuations.FirstofallweneedanewPScm::Continuationclass.Idon'twanttoshowyouallofthatclassjustyet,buthere'sitsnew()method:013subnewf014my($class,$cont)=@_;015blessfcont=>$contg,$class;016gIttakesananonymoussubroutineasargumentandstoresitinacont eld.Wedon'twanttobewritingnewPScm::Continuation(subf...g)allovertheplace,sowesweetenthingswithalittlesyntacticsugar:018subcont(&)f019my($cont)=@_;020return__PACKAGE__->new($cont);021gThisisputonPScm::Continuation's@EXPORTlist10soafterimportingitwithusePScm::Continuation;",insteadofwritingnewPScm::Continuation(subf...g)wejustwritecontf...ginstead.Ifyou'renotfamiliarwiththistechnique,see[13,pp225{231].Asdiscussedabove,weaddatrampoline()subroutinetoPScmwhichrepeatedlyinvokesthecontinuationreturnedfromitspreviousinvocation,untiltheinvocationreturnsundef,signalingtheendofthecomputation.Here'strampoline():070subtrampolinef071my($cont)=@_;072$cont=$cont->Bounce()whiledefined$cont;073gIt'sfunctionallyequivalenttotheprototypetrampoline()subroutinediscussedabove.TheBounce()methodisde nedinPScm::Continuationtoimmediatelyinvokethecontinuationwithnoarguments:9AssumingofcoursethattheCont()methodisinvokedintailposition,butwe'vebeenherebefore.10Ingeneralitisalwaysconsideredbetterformtouse@EXPORTOKratherthan@EXPORT.Howeveritisjusti ablehere rstlybecausePScm::ContinuationispartofPScheme,notastandalonelibrarymodule,andsecondlytheonlyreasonanotherclasswouldusePScm::Continuationwouldbetogainaccesstothecontconstruct. 13.6.IMPLEMENTATION189039subBouncef040my($self)=@_;041$self->fcontg->();042gOnlytrampoline()callsBounce().NowweneedtolookattheRead-Eval-PrintloopfromPScm,withthetrampolineinplace.032subReadEvalPrintf033my($infh,$outfh)=@_;034035$outfh||=newFileHandle(">-");036my$reader=newPScm::Read($infh);037trampoline(contfrepl($reader,$outfh)g);038gIt'sthesameaswe'veseenbeforeuptoLine37whereinsteadofenteringit'sloop,itinvokesthetrampolinewithacontinuation.Thatcontinuationinvokesanewhelperroutinerepl()withthereaderandthecurrentoutputhandleasarguments.Here'srepl().040subreplf041my($reader,$outfh)=@_;042$reader->Read(043contf044my($expr)=@_;045$expr->Eval(046newPScm::Env(047let=>newPScm::SpecialForm::Let(),048'*'=>newPScm::Primitive::Multiply(),049'-'=>newPScm::Primitive::Subtract(),050if=>newPScm::SpecialForm::If(),051lambda=>newPScm::SpecialForm::Lambda(),052'call/cc'=>053newPScm::SpecialForm::CallCC(),054),055contf056my($result)=@_;057$result->Print(058$outfh,059contf060repl($reader,$outfh);061g062)063g064)065g066)067g 190CHAPTER13.CONTINUATIONSSothegutsoftheoldReadEvalPrint()havebeenmovedtorepl().It'sjustanexpansionofthecpspseudocodeforrepl()intheprevioussection,andnotnearlyasbadasitmight rstappear,it'sreallyjustRead()callingEval()callingPrint()callingrepl(),allthroughpassedcontinuations.Thereisalsosomethingnewaddedtotheenvironment.we'llseewhatthatnewbindingcall/cconLine53isaboutlater.SotheRead(),Eval()andPrint()methodsnowalltakecontinuationsandmustbemodi edaccordingly.Thankfullythemodi cationstoRead()andPrint()aretrivial.FirstweneedtolookatthecpsPrint()method.075subPrintf076my($expr,$outfh,$cont)=@_;077print$outfh$expr->as_string," ";078$cont->Cont($expr);079gItjustdoeswhatitusedtodo,thencallsitscontinuationwithanarbitraryargument.Thatisthecontinuationthatwillrestartthereplanditdoesn'tactuallyexpectanargument,butCont()doessowe'rejustplayingnice.NoticethatonLine77wedon'tpassacontinuationtotheasstring()method.Thisisjustanormalnon-cpsmethodcall.Thereasoningbehindthatisthatalthoughasstring()ispotentiallyrecursive,atnopointwillitcauseevaluationofanyPSchemeexpressions.Sinceweareonlyinterestedincontinuationsthatmightbeexposedtousercode,wecanclassifyanymethodcallthatcannotresultinacalltoEval()asasimpleexpressionanddealwithitasanatomicoperation.Contrarywise,callstoEval()orcallstomethodsthatmightresultinacalltoEval()areclassi edassigni cantexpressions,andmustberewrittenintocps.Thisdistinctionmakesourrewritemuchsimpler11.Asdescribedabove,thatCont()methodactuallyreturnsacontinuationofzeroargumentswhichthetrampolinewillexecute(bycallingBounce()onit).ThisisthetrickIwasenthusingaboutearlier:toreturnacontinuationtothetrampolinethatwillcallthecurrentcontinuation,ratherthanjustdirectlycallingthecurrentcontinuation.Thereturnwillfallallthewaybacktothetrampoline,e ectingacompletecleardownofwhateverstackmighthaveaccumulateduptothispoint,thenthetrampolinewillkickthingso again:034subContf035my($self,$arg)=@_;036returncontf$self->fcontg->($arg)g;037gThereallyneatthingaboutthisisthatthecodethatiswrittentousethismethodneitherknowsnorcaresthatthecontinuationisnotsimplybeinginvokeddirectlyatthispoint.Thepresenceofthetrampolineiscompletelyinvisibletotheclientcpscode.Let'stakealookatRead()inPScm::Readnext.InfactwhatwehavedoneistorenameRead()toread(),leavingitotherwiseunchanged:11ThereasoningisthateachcalltoEval()withintheinterpretercorrespondstoavaluebeingcalculatedand,mostimportantly,returnedinthePSchemelanguage.Thesepointsofreturnareexactlythepointsthatrequirecontinuationstobeusedinstead.IfhoweverinthelatercpsrewriteoftheobjectsystemfromChapter12onpage135wewantedtoallowPSchemeobjectstosupplysomesortofto-stringmethod,andhavethatcalledinpreferencetotheunderlyingPerlasstring()method,thenwewouldhavetorewriteasstring()intocps. 13.6.IMPLEMENTATION191017sub_readf018my($self)=@_;019020my$token=$self->_next_token();021returnundefunlessdefined$token;022023return$tokenunless$token->is_open_token;024025my@res=();026027while(1)f028$token=$self->_read;029die"unexpectedEOF"030if!defined$token;031lastif$token->is_close_token;032push@res,$token;033g034035returnnewPScm::Expr::List(@res);036gThenweprovideanewRead()thathandlesthecontinuation.063subReadf064my($self,$cont)=@_;065my$res=$self->_read();066returnundefunlessdefined$res;067$cont->Cont($res);068gRead()collectstheresultofthecalltoread(),andifitisundefsignifyingeofitreturnsundeftothetrampolinetellingittostop.Otherwiseitcallsitscontinuationontheresult.NextweneedtotakealookatEval().AllEval()methodsnowalsotakeanadditionalcontinuationasargument.AlltheEval()methodsareinsubclassesofPScm::Expr.Let'sstartbylookingatthesimplestofthoseexpressions:literalsandsymbols.TheoldEval()methodinPScm::Exprjustreturned$self(numbers,stringsandanythingelsebydefaultevaluatetothemselves).Thenewversionislittledi erent,itcallsitsargumentcontinuationonitself:012subEvalf013my($self,$env,$cont)=@_;014$cont->Cont($self);015gNowforPScm::Expr::Symbol::Eval().TheoldEval()methodinPScm::Expr::Symbolreturned$env->LookUp($self).Ourcpsversioncallsitscontinuationonthatresultinstead,becausewecantreatthecalltoLookUp()asasimpleexpression: 192CHAPTER13.CONTINUATIONS103subEvalf104my($self,$env,$cont)=@_;105$cont->Cont($env->LookUp($self));106gEvaluationoflistsisalittlemoretricky,sotorefreshourmemorieshere'stheoriginalPScm::Expr::List::Eval()beforecpstransformation:062subEvalf063my($self,$env)=@_;064my$op=$self->first()->Eval($env);065return$op->Apply($self->rest,$env);066gOnLine64Itevaluatesthe rstcomponentofthelisttogettheoperator$op,thenonLine65itappliestheoperation$optotherestoftheunevaluatedlist.Here'sthecpsform:063subEvalf064my($self,$env,$cont)=@_;065returncontf066$self->first()->Eval(067$env,068contf069my($op)=@_;070$op->Apply($self->rest,$env,$cont);071g072);073g;074gThere'sratheralotgoingonhere,sobestweapproachitintwostages.Firstlythereturncontf...gblockwrapstheentiremethodbodyinacontinuationthatwereturntothetrampoline.ApartfromPScm::Continuation::Cont()thisistheonlyotherplacewhereweexplicitlyfallbackdowntothetrampoline.ThisisbecauseallrecursivecallstoEval()andApply()mustpassthroughthissinglefunction,andsowecanstopallrunawaystackconsumptionbyEval()andApply()here12.Youcanjustignorethereturncontwrapperandconsideronlythebodyofthe12Rememberourcpsfactorial()examplebeforetco,wherethestackbuiltupduringcallstofactorial()andbuiltupfurtherwhenthecontinuationsactuallytriggeredonfactorial(0).WellthereturninthePScm::Continuation::Cont()methodtakescareofthesecondofthesecontingencies,andreturningacontinuationheretakescareofthe rst.Ifyoudon'tbelieveme,waituntilwehaverewrittenthecompleteinterpreterincpsthenenterthefollowingde nitionint/interactive:>(definefactorial>(lambda(x)>(ifx>(*x(factorial(-x1)))>1)))Thentry 13.6.IMPLEMENTATION193continuationasifitwerethebodyofthefunction.Itwouldstillwork,butmightrunoutofstackinthelongrun.Secondlyinsidethemethodproperweassumethatthe rstcalltoEval(),inordertotogetthe$op,willnotreturn,sowepassitacontinuationwhichacceptstheresult$op,andappliesittotherestofthelist,passingintheoriginalcontinuation(Line70).Wemustpasstheoriginalcontinuation$conttoApply(),ratherthanjustcallingthecontinuationontheresultoftheApply(),becausetheApply()mightmakecallstoEval()toevaluateargumentstothe$op,amongotherthings,andmustthereforeberewrittenincps.Sothat'sitfortherewriteofalloftheEval()methodsinPScm::Expr.NowweneedtofollowthechainofcontinuationpassingintothevariousApply()methodswehave.Sincethisisanearlyversionoftheinterpreter,therearen'ttoomany,infacttheyarein:PScm::Primitive;PScm::SpecialForm::Let;PScm::SpecialForm::If;PScm::SpecialForm::LambdaandPScm::Closure::Function.StartingwithPScm::Primitive::Apply(),you'llrememberthatallprimitiveoperationsshareacom-monApply()method.Nowindividualprimitivesdonothavetoacceptcontinuationsbecausetheyareterminaloperations,soallthatwehavetodoistocallthecontinuationthatwaspassedtothesharedprimitiveApply()ontheresultofapplyingtheindividualprimitivetoitsarguments.UnfortunatelythisiscomplicatedbythefactthattheprimitiveApply()must rstevaluateitsarguments.TheoriginalprimitiveApply()diditwithmap:007subApplyf008my($self,$form,$env)=@_;009010my@unevaluated_args=$form->value;011my@evaluated_args=mapf$_->Eval($env)g@unevaluated_args;012return$self->_apply(@evaluated_args);013gThisisalittletrickytorewriteincps,sowe'regoingtoattackitinstages.Stageonewillbetowritearecursiveversionofthebuiltinmap,whichinsteadoftakingasubandlist,takesalistrefandanenvironment,andforeachelementofthelistref,callsthatelement'sEval()methodwiththeenvironmentasargument,accumulatingtheresultinanewlistref.Butwaitaminute,don'twealreadyhavesuch>(factorial170)7257415615307998967396728211129263114716991681296451376543577798900561843401706157852350749242617459511490991237838520776666022565442753025328900773207510902400430280058295603966612599658257104398558294257568966313439612262571094946806711205568880457193340212661452800000000000000000000000000000000000000000Perlwouldhavecomplainedlongbeforeithitthatmanylevelsofrecursion. 194CHAPTER13.CONTINUATIONSarecursivemapeval()method?Yes,wewrotejustsuchamethodwhenweimplementedtruelistprocessingforversion0.0.5backinSection8.5.1onpage92.Here'sthatmethodagain.120submap_evalf121my($self,$env)=@_;122return$self->Cons($self->[FIRST]->Eval($env),123$self->[REST]->map_eval($env));124gNowrememberthatthatiscodefrom0.0.5,andherewe'rejustrewritingversion0.0.2,sowedon'thavetruelistprocessingyet,lotsofourmethodsarestillexpectingPerlarrayreferences,wedon'thaveaCons()method,andwedon'thaveanyPScm::Expr::List::Nullclass.Nonethelesswecancastthismethodbackinto0.0.2termsquiteeasily.This0.0.2mapeval()methodisnotyetincpsform:submap_evalfmy($self,$env)=@_;if(@$self)freturn[$self->first->Eval($env),@f$self->rest->map_eval($env)g];gelsefreturn[];ggThisisprettystraightforward.Thesecondstageofourattackistore-writemapeval()incps.Itwilltakeanadditionalcontinuationargument,then,muchasourfactorial()exampledid,iftherecursionhasreacheditslimit(theargumentlistisempty)itcallsitscontinuationontheemptylist.Otherwiseithasnot nished,anditevaluatesits rstcomponent,passingacontinuationthatarrangestoevaluatetherestofthelistbyrecursing:076submap_evalf077my($self,$env,$cont)=@_;078079if(@$self)f080$self->first->Eval(081$env,082contf083my($evaluated_first)=@_;084$self->rest->map_eval(085$env,086contf087my($evaluated_rest)=@_;088$cont->Cont([$evaluated_first,089@$evaluated_rest]); 13.6.IMPLEMENTATION195090g091);092g093);094gelsef095$cont->Cont([]);096g097gThisisthetrickiestpieceofcodeintheentirecpsre-write.Fortunatelyhavingdoneit,itisusefulinanumberofotherscenarios.Nowthatwehavemapeval()wecanuseittore-writePScm::SpecialForm::Primitive::Apply():008subApplyf009my($self,$form,$env,$cont)=@_;010011$form->map_eval(012$env,013contf014my($ra_evaluated_args)=@_;015$cont->Cont($self->_apply(@$ra_evaluated_args));016g017);018gNottoobad.Themapeval()ispassedacontinuationthatappliestheprimitiveoperationtotheevaluatedargumentsandcallstheoriginalargumentcontinuationontheresult.Itisworthnotingagainthattherewasnoneedtopassanycontinuationtotheindividualprivateapply()methodsforeachprimitive,soPScm::Primitive::Multiplyetc.areunchanged.Therestofthecpstransformationsaremuchsimpler,onthewhole,andothersthatrequiretherewritingofmapcanmakeuseofmapeval().NextupisPScm::SpecialForm::Let,here'sthechanges:013subApplyf014my($self,$form,$env,$cont)=@_;015016my($bindings,$body)=$form->value;017my(@symbols,@values);018019foreachmy$binding($bindings->value)f020my($symbol,$value)=$binding->value;021push@symbols,$symbol;022push@values,$value;023g024025$env->Extend(026@symbols, 196CHAPTER13.CONTINUATIONS027@values,028contf029my($newenv)=@_;030$body->Eval($newenv,$cont);031g032);033gThechangesareinboldonLines14and27{31Ifyouremember,theoldversionattheendsimplysaidreturn$body->Eval($env->Extend(@symbols,@values));Sinceweknowthatthecallto$env->Extend()willnotreturn(those@valuesarestilltobeevaluated),weinsteadhavetopassacontinuationthatwillaccepttheresultingextendedenvironmentandevaluatethebodyinit.WehavealreadydealtwithalltheEval()methods(They'reallinPScm::Expr)andtheyalltakeacontinuation,sowepasstheoriginalcontinuationargument,sincetheEval()istheexpressionthatthisApply()waspreviouslyreturning.RememberingtoaddPScm::Env::Extend()toourlistofmethodsthatwillneedlookingat,weproceedtoPScm::SpecialForm::If::Apply().We'vealreadydiscussedhowtotransformaconditionalexpressionintocpsform,butsincethisisour rstencounterinthewild,let'srefreshourmemoryby rstlookingattheoriginalnon-cpsversion:032subApplyf033my($self,$form,$env)=@_;034035my($condition,$true_branch,$false_branch)=$form->value;036037if($condition->Eval($env)->isTrue)f038return$true_branch->Eval($env);039gelsef040return$false_branch->Eval($env);041g042gItevaluatestheconditioninthecurrentenv,andcallsisTrue()ontheresult,thenusesthattodecidewhethertoevaluatethetruebranchorthefalsebranch,bothinthecurrentenvironment.Ourcpsversionisnotthatdi erent:041subApplyf042my($self,$form,$env,$cont)=@_;043044my($condition,$true_branch,$false_branch)=$form->value;045046$condition->Eval(047$env,048contf 13.6.IMPLEMENTATION197049my($result)=@_;050if($result->isTrue)f051$true_branch->Eval($env,$cont);052gelsef053$false_branch->Eval($env,$cont);054g055g056);057gItevaluatestheconditioninthecurrentenvironmentandpassesacontinuationthatwillaccepttheresult.ThatcontinuationcallsisTrue()ontheresultandusesthattodecide,inexactlythesameway,whethertoevaluatethetruebranchorthefalsebranch.IneithercasetheoriginalcontinuationthatwasargumenttoPScm::SpecialForm::If::Apply()ispassedtothechosenbranch'sEval()method.Stayingwiththeprogram,ournexttaskistheinvocationoflambdahandledbyPScm::Special-Form::Lambda::Apply():065subApplyf066my($self,$form,$env,$cont)=@_;067068my($args,$body)=$form->value;069070$cont->Cont(PScm::Closure::Function->new($args,$body,$env));071gThere'snothingveryinterestinghere.lambdajustcreatesaclosure.TherearenocallstoEval()thatitmustmakeduringthiscreation,sowecantreatthecalltonewasasimpleexpressionandinvokeourargumentcontinuationontheresult.ThatjustleavesPScm::Closure::Function::Apply()andPScm::Env::Extend().Let'sstartwithPScm::Closure::Function.TheoriginaljustmappedEval()overitsargumentsthencalledaprivateapply()methodontheresults:043subApplyf044my($self,$form,$env)=@_;045046my@evaluated_args=mapf$_->Eval($env)g$form->value;047return$self->_apply(@evaluated_args);048gAnotherjobformapeval()then:044subApplyf045my($self,$form,$env,$cont)=@_;046047$form->map_eval(048$env,049contf 198CHAPTER13.CONTINUATIONS050my($ra_evaluated_args)=@_;051$self->_apply($ra_evaluated_args,$cont);052g053);054gNotehoweverthatweneedtopassthecurrentcontinuationtothatprivateapply()method.That'sbecausetheclosurewillbecallingEval()onitsbody.Let'stakealookatPScm::Closure::apply().021sub_applyf022my($self,$ra_args,$cont)=@_;023024my$extended_env=025$self->env->ExtendUnevaluated([$self->args],$ra_args);026return$self->body->Eval($extended_env,$cont);027gItdi ersinthatittakesareferencetoanarrayofargsandacontinuation,ratherthanjustanarrayofargs,anditpassesthecontinuationintothecalltoEval()onitsbody.Finally,weneedtorewritePScm::Env::Extend().015subExtendf016my($self,$ra_symbols,$ra_values,$cont)=@_;017018PScm::Expr::List->new(@$ra_values)->map_eval(019$self,020contf021my($ra_evaluated_values)=@_;022$cont->Cont(023$self->ExtendUnevaluated(024$ra_symbols,$ra_evaluated_values025)026);027g028);029gItusesmapeval()toevaluateitslistofvalues,passingtheresulttoacontinuationthatextendstheenvironmentwiththosevalues.Itcallstheargumentcontinuation$contontheresult.Atthispointinthediscussion,wehaveaworkingcpsversionofinterpreter0.0.2,andalltheoriginalteststhatwerewrittenforthatversionstillpass.Howeverweseemtohavedonealotofhardworkfornobene t,sincetheinterpreterisexternallyequivalenttotheoriginal0.0.2version.Wecanremedythatbygivingtheinterpreteranadditionalconstructthatprovidesdirectaccesstotheunderlyingcontinuations.Therearemanywaysthatthiscouldbedone,butoneofthebest-knownandmostpowerfulwaysiswithaformthatgoesbytheunwieldytitlecall-with-current-continuation,usuallyabbreviatedtocall/cc.Thisformtakesafunctionasargumentandcallsit,passingthecurrentcontinuationasanexplicitargumenttothefunction,forexample: 13.6.IMPLEMENTATION199>(call/cc(lambda(cont)(cont10)))10Whenthefunctioninvokesthecontinuationasafunction,controlreturnstothecall/ccandtheargumenttothecontinuationbecomestheresultofthecalltocall/cc.Ifthepreviousexampledoesn'tseemtooexciting,howaboutthis:>(call/cc>(lambda(cont)>(if(cont10)>20>30)))10Herethecallto(cont10)producedanimmediatereturnofthevalue10throughthecall/cceventhoughitwasexecutedintheconditionalpositionofanifstatement.Thesetwoexamplesonlyshowcontrolpassingdownthestack"whenacontinuationisinvoked.Howeveritisperfectlyreasonableforcontroltoreturnupthestacktoaprocedurethathasalreadyreturned.Itissimplynoteasytodemonstratewiththisversionoftheinterpreter.Oncewehaveaninterpreterwithassignmentandsequences,itbecomesmucheasier.call/ccisinfactalow-level,ifnotthelowestlevelcontinuationtool.Itispossibletobuildhigherlevelcontrolconstructsusingit.Abandoningpschemeforamoment,considerthisFibonacci13sequencegeneratorinsomehypotheticalPerl-likelanguagethatsupportsco-routines:subfibfmy($i,$j)=(0,1);for(;;)fyield$i;($i,$j)=($j,$i+$j);ggwhile((my$i=fib())<22)f#prints0123581321print"$i";gThatyieldcallnotonlybehaveslikeareturnstatement,butalsoremembersthecurrentstateofthefunctionsothatthenexttimethefunctioniscalledcontrolresumeswhereitlastlefto .Withbuiltincontinuationsthissortofcontrol owisveryeasytoachieve.AnywayIhopethishaswhetyourappetitealittleforwhatcall/cccando,solet'shavealookatitsimplementation.Itisofcourseaspecialform,andasusualithasanApply()method:13TheFibonacciseriesstartswith0and1.thenextnumberintheseriesisalwaysthesumoftheprevioustwo,e.g.0,1,1,2,3,5,8,13,21... 200CHAPTER13.CONTINUATIONS074packagePScm::SpecialForm::CallCC;075076usebaseqw(PScm::SpecialForm);077usePScm::Closure;078usePScm::Continuation;079080subApplyf081my($self,$form,$env,$cont)=@_;082083$form->first->Eval(084$env,085contf086my($closure)=@_;087$closure->Apply(newPScm::Expr::List($cont),088$env,$cont);089g090);091g0920931;Itevaluatesits rstargument,whichshouldresultinafunctionofoneargument,passingtheEval()acontinuationwhichwillApply()thefunctiontoaformexplicitlycontainingthecurrentcontinuation.Italsopassesthecurrentenvandthecurrentcontinuationasecondtime,thistimeasthenormalimplicitargument.That'sallthereistoit.OfcoursethecontinuationitselfwillneedanApply()methodsothatitcanbeinvokedasanoperator.We'renowreadytoseethewholeofthePScm::Continuationpackage,inListing13.10.1onpage225.We'vealreadyseenmostofthis,onlytheApply()methodisnew.023subApplyf024my($self,$form,$env,$cont)=@_;025$form->map_eval(026$env,027contf028my($ra_evaluated_args)=@_;029$self->Cont($ra_evaluated_args->[0]);030g031);032gApply()onLines23-32isanothermethodthatmakesuseofmapeval()toevaluateitsarguments.Itpassesitacontinuationthatcallsitselfonthe rstofitsevaluatedarguments,totallyignoringthepassed-in,currentcontinuation,ande ectingtransferofcontroltowhatevercontextthiscontinuationrepresents.Andwe'redone. 13.6.IMPLEMENTATION201Asimpletestofcall/cccanbeseeninListing13.10.2onpage226.Fullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.1.2.tgz 202CHAPTER13.CONTINUATIONS13.6.3cpsletrecTheinterpreterversion0.0.3backinChapter6onpage73introducedletrec(letrecursive)whichallowedenvironmentstobecreatedinsuchawaythatclosureswouldextendtheenvironmentthattheywerethemselvesde nedin,allowingthemtomakerecursivecalls.Thissubsectiontakestheadditionstoversion0.0.3andreimplementsthemincps.We'regoingtostarttopickupthepacesomewhatfromhereonin,butI'llstillpresentallofthechanges,startingwiththeletrecspecialformitself.Here'stheoriginalv3:040subApplyf041my($self,$form,$env)=@_;042043my($ra_symbols,$ra_values,$body)=$self->UnPack($form,$env);044045return$body->Eval(046$env->ExtendRecursively($ra_symbols,$ra_values));047gThecpsversioncallsamodi edPScm::Env::ExtendRecursively(),passingacontinuationthattakestherecursivelyextendedenvironmentandevaluatesthebodyinit,passingtheoriginalcontinuationtothatEval().049subApplyf050my($self,$form,$env,$cont)=@_;051052my($ra_symbols,$ra_values,$body)=$self->UnPack($form);053054$env->ExtendRecursively(055$ra_symbols,056$ra_values,057contf058my($extended_env)=@_;059$body->Eval($extended_env,$cont);060g061);062gPScm::Env::ExtendRecursively()callsPScm::Env::ExtendUnevaluated()asasimpleexpressionthencallsevalvalues()ontheextendedenvironment,passingtheoriginalcontinuation:041subExtendRecursivelyf042my($self,$ra_symbols,$ra_values,$cont)=@_;043044my$newenv=$self->ExtendUnevaluated($ra_symbols,$ra_values);045$newenv->_eval_values($cont);046gHere'sthenewcpsevalvalues(): 13.6.IMPLEMENTATION203048sub_eval_valuesf049my($self,$cont)=@_;050$self->_map_bindings([keys%f$self->fbindingsgg],$cont);051gItusesanewhelpermapbindings(),wheretheoriginalevalvalues()justusedmap.Thisworksinasimilarwaytomapeval(),evaluatingeachvalueintheenvironmentbutthenassigningtheresultbacktotheoriginalbinding:053sub_map_bindingsf054my($self,$ra_keys,$cont)=@_;055my(@keys)=@$ra_keys;056if(@keys)f057my$firstkey=shift@keys;058$self->fbindingsgf$firstkeyg->Eval(059$self,060contf061my($value)=@_;062$self->fbindingsgf$firstkeyg=$value;063$self->_map_bindings([@keys],$cont);064g065);066gelsef067$cont->Cont($self);068g069gOthermethodsareunchangedsowehavecompletedthecpsrewriteofletrec,andalltestsfor0.0.3stillpassin0.1.3.Fullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.1.3.tgz 204CHAPTER13.CONTINUATIONS13.6.4cpslet*Theinterpreterversion0.0.4backinChapter7onpage81addedlet*,ashorthandwayofcreatingnestedenvironmentsprovidingtheappearenceofsequentialassignmentwithinthebindingsofthelet*expression.Thiswasasimpleaddition,therewritewillbeequallysimple.Here'sthenewPScm::SpecialForm::LetStar::Apply():070subApplyf071my($self,$form,$env,$cont)=@_;072073my($ra_symbols,$ra_values,$body)=$self->UnPack($form);074075$env->ExtendIteratively(076$ra_symbols,077$ra_values,078contf079my($extended_env)=@_;080$body->Eval($extended_env,$cont);081g082);083gJustlikeletrec(whichcalledamodi edPScm::Env::ExtendRecursively(),)thiscallsamodi edExtendIteratively(),passingacontinuationthatevaluatesthebodyofthelet*inthenewenviron-mentwiththeoriginalcontinuation.Here'sthemodi cationstoPScm::Env::ExtendIteratively():048subExtendIterativelyf049my($self,$ra_symbols,$ra_values,$cont)=@_;050my@symbols=@$ra_symbols;051my@values=@$ra_values;052if(@symbols)f053my$symbol=shift@symbols;054my$value=shift@values;055$self->Extend(056[$symbol],057[$value],058contf059my($extended)=@_;060$extended->ExtendIteratively([@symbols],[@values],061$cont);062g063);064gelsef065$cont->Cont($self);066g067g 13.6.IMPLEMENTATION205Theoldversionjustiteratedoverthename/valuepairs,creatinganadditionalnestedenvironmenteachtimearoundtheloopandreturningthe nalresult.cpsiseasierwithrecursivede nitionssothisExtendIteratively()hasbeenrecastasarecursivemethod.Itstilldoesthesamejob,butadditionallyarrangesthattheoriginalcontinuationgetscalledonthe nal,extendedenvironment.Fullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.1.4.tgz 206CHAPTER13.CONTINUATIONS13.6.5cpsListProcessingOurnextiterationoftheinterpreter,version0.0.5backinChapter8onpage87,addedthelistmanipu-lationfunctionsquote,list,car,cdrandconstothelanguage,andadditionallychangedtheinternalimplementationofPScm::Expr::Listfromsimpleperllistrefstolinkedlists,makingPScm::Expr::Listabstract,addingaPScm::Expr::List::Pairclasstorepresenttheconscells,andaddingaPScm::Expr::List::Nullclasstorepresenttheemptylist.Surprisingly,Thecpsrewriteofallofthisisquiteminimal.First,here'sthenewquoteinPScm::SpecialForm::Quote::Apply():157subApplyf158my($self,$form,$env,$cont)=@_;159$cont->Cont($form->first);160gTheoriginalreturnedits rstargumentunevaluated,thecpsformcallsitscontinuationonit.Rememberthatthequotesystemwasre-writtenforalaterversionoftheinterpretertosupportunquotebackinSection9.2.2onpage112,sowe'llbereturningtoquotelateron,inSection13.6.6onpage208,wherewerewritethatrewrite!Theotheradditionalfunctions:car,cdr,consandlistareallprimitivesthatshareanApply()methodthathasalreadybeenrewrittenintocpsinSection13.6.2onpage188.AmongthePScm::Exprclasses,theonlythingthatchangesisthemapeval()method.Thatmethodwasintroducedinversion0.0.5toworkwithpschemelists,thenre-introducedatanearlierstageofthecpsrewrite,inversion0.1.2,becauseweneededarecursivealternativetoPerl'smap.Finally,here,wecombinethetwoimplementations.Here'sPScm::Expr::List::Pair::mapeval():130submap_evalf131my($self,$env,$cont)=@_;132133$self->[FIRST]->Eval(134$env,135contf136my($evaluated_first)=@_;137$self->[REST]->map_eval(138$env,139contf140my($evaluated_rest)=@_;141$cont->Cont($self->Cons($evaluated_first,142$evaluated_rest));143g144);145g146);147gAndhere'sthenewdefaultPScm::Expr::mapeval()thatterminatestherecursionofmapeval()if$selfisPScm::Expr::Null,doestherightthingifthecdrofthelistisnotalist,andhandlescontinuations,allinonetinymethod: 13.6.IMPLEMENTATION207034submap_evalf035my($self,$env,$cont)=@_;036$self->Eval($env,$cont);037gFullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.1.5.tgz 208CHAPTER13.CONTINUATIONS13.6.6cpsmacroandunquoteVersion0.0.6ofourinterpreter,fromChapter9onpage107,introducedthemacrospecialform.ThisspecialformtookargumentsinanidenticalmannertolambdaandcreatedavarianttypeofclosurePScm::Closure::Macro.Normallambdaclosureevaluationproceedsbyevaluatingtheargumentstotheclosurethenevaluatingthebodyoftheclosurewiththoseargumentsbound.Incontrastmacroclosureevaluationproceedsbyevaluatingthebodyoftheclosurewithitsunevaluatedargumentsbound,thenre-evaluatingtheresult.Therewriteintocpsistrivial, rsthere'sthecpsformofPScm::SpecialForm::Macro::Apply():175subApplyf176my($self,$form,$env,$cont)=@_;177my($args,$body)=$form->value;178$cont->Cont(PScm::Closure::Macro->new($args,$body,$env));179gJustaswithPSCm::SpecialForm::Lambda::Apply(),thecreationoftheclosurecanbetreatedasasimpleexpression(nocallstoEval())andthecontinuationcalledontheresult.NowPScm::Closure::Macro::Apply():062subApplyf063my($self,$form,$env,$cont)=@_;064065$self->_apply(066$form,067contf068my($new_form)=@_;069$new_form->Eval($env,$cont);070g071);072gHerewepassacontinuationtothecoreapply()methodthattakestheresultingnewformandevaluatesthatinthecurrentenvironmentwiththecurrentcontinuationasanadditionalargument.Thecore-apply()extendstheenvironmentwithunevaluatedarguments,thencallsthebodyofthemacrowiththenewenvironmentandthepassedincontinuation.017sub_applyf018my($self,$args,$cont)=@_;019020my$extended_env=021$self->fenvg->ExtendUnevaluated($self->fargsg,$args);022$self->fbodyg->Eval($extended_env,$cont);023gButyougettheidea.That'sallthereistotherewriteofthemacroextensionintocps.HoweverChapter9onpage107alsorewrotePScm::SpecialForm::Quotetosupporttheunquotekeywordwhichallowstheinterpolationofevaluatedsub-expressionswithinaquotedexpression.Thatprovesmoreinterestingtorecastintocps.Let'sstartatthetopbylookingatthenewcpsversionofPScm::SpecialForm::Quote::Apply(): 13.6.IMPLEMENTATION209186subApplyf187my($self,$form,$env,$cont)=@_;188$form->first->Quote($env,$cont);189gSofarsogood,wejustpassthecurrentcontinuationalongwiththecurrentenvironmenttotheQuote()methodofwhateverexpressionwe'requoting.Let'sdealwiththeeasystu rst.PScm::Expr::Quote()usedtojustreturn$self,thecpsversioncallsthecontinuationon$selfinstead:041subQuotef042my($self,$env,$cont)=@_;043$cont->Cont($self);044gThatleavesPScm::Expr::List::Pair'sQuote():163subQuotef164my($self,$env,$cont)=@_;165if($self->[FIRST]->is_unquote)f166$self->[REST]->first->Eval($env,$cont);167gelsef168$self->quote_rest($env,$cont);169g170gGreat!sincethecallstobothEval()andquoterest()areintailposition,itneedonlypassthecontinuationalongtoboth.AlltheEval()methodshavealreadybeendealtwithofcourse,sothatleavesquoterest().Let's rstrefreshourmemoriesbylookingatthenon-cpsoriginal:142subquote_restf143my($self,$env)=@_;144return$self->Cons(145$self->[FIRST]->Quote($env),146$self->[REST]->quote_rest($env)147);148gThisisde natelynottailrecursive.Butifwethinkitthroughtherearenoproblems.The rstthingitdoesiscallQuote()onitsfirst()element,thenitcallsitselfontherest()ofthelist,then nallyitcallsCons()onthosetworesults.BoththecalltoQuote()andquoterest()couldpotentiallyresultincallstoEval()soweneedtopasscontinuationstoboth.Wecanrewriteitalittle rsttomaketheorderofoperationsmoreexplicit:subquote_restfmy($self,$env)=@_;my$quoted_first=$self->first->Quote($env); 210CHAPTER13.CONTINUATIONSmy$quoted_rest=$self->rest->quote_rest($env);return$self->Cons($quoted_first,$quoted_rest);gNowallwedoisrewritethatsothatthecalltoQuote()getsacontinuationthatperformstheremainingtwooperations,includingpassingasecondcontinuationtoquoterest()thatperformsthelastCons().here'sthecpsrewrite:172subquote_restf173my($self,$env,$cont)=@_;174$self->[FIRST]->Quote(175$env,176contf177my($quoted_first)=@_;178$self->[REST]->quote_rest(179$env,180contf181my($quoted_rest)=@_;182$cont->Cont(183$self->Cons($quoted_first,$quoted_rest));184g185);186g187);188gItcallsQuote()onits rstelement,passingacontinuation(Lines176-186)thatacceptsthe$quoted-firstandthencallsquoterest()ontherestoftheelements,passingthatacontinuation(Lines180-184)thatacceptsthe$quotedrestandcallstheoriginalcontinuationontheresultofCons()-ingthe$quotedfirstand$quotedresttogether.Finally,asbefore,wherethedefaultPScm::Expr::quoterest()justreturned$self,nowitcallsitsaregumentcontinuationon$self:046subquote_restf047my($self,$env,$cont)=@_;048$cont->Cont($self);049gThat'sitforourcpsrewriteoftheunquotefacility.Allothermethodsareunchanged.Fullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.1.6.tgz 13.6.IMPLEMENTATION21113.6.7cpsSequencesandAssignmentNextuparethesequences(begin)andassignment(set!)introducedbyinterpreter0.0.7inChapter10onpage123.Let'sstartwithsequencesandthebeginspecialform.Torecapbegintakesasequenceofzeroormoreexpressionsandevaluatestheminstrictlefttorightorder,returningthelastresult.Ifgivennoarguments,inthisimplementationitreturnstheemptylist.Here'sitsApply():215subApplyf216my($self,$form,$env,$cont)=@_;217if($form->is_pair)f218$self->apply_next($form,$env,$cont);219gelsef220$cont->Cont($form);221g222gTheoriginalwasiterative.ThisversionhasbeenrecastinarecursivemouldtomakethecpstransformeasierandtotakeadvantageofPScm::Expr::List.Ifthelistisemptyitcallsthecontinuationontheemptylist,otherwiseitpassestheform,environmentandcontinuationtoahelpermethodapplynext():224subapply_nextf225my($self,$form,$env,$cont)=@_;226227$form->first->Eval(228$env,229contf230my($val)=@_;231if($form->rest->is_pair)f232$self->apply_next($form->rest,$env,$cont);233gelsef234$cont->Cont($val);235g236g237);238gItevaluatesthe rstelementofthelist,passingacontinuationthatacceptstheresult.Ifthereismoreofthelisttoprocess,itcallsitselfrecursivelyontherestofthelist,otherwiseitcallstheoriginalcontinuationonthe$val(thevalueofthelastexpressiononthelist).Notethesimilaritybetweenthismethodandmapeval().Thediferenceisonlythatapplynext()doesnotneedtoconstructanewlistofalltheevaluatedresults.Nextandlastisset!.set!usesPScm::Env::Assign()thatwasdevelopedforletrectolocatethenearestbindingforasymbolandchangeitsvalue.set!isaspecialformsinceitevaluatesitssecondargument(thevalue)butnotits rst(thesymbol).197subApplyf198my($self,$form,$env,$cont)=@_; 212CHAPTER13.CONTINUATIONS199my($symbol,$expr)=$form->value;200$expr->Eval(201$env,202contf203my($val)=@_;204$cont->Cont($env->Assign($symbol,$val));205g206);207gThecpsrewriteisprettystraightforward.Itevaluatesthevaluepassingacontinuationthatwillperformtheassignment(asimpleexpression)callingtheoriginalcontinuationontheresult.Fullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.1.7.tgz13.6.8cpsdefineVersion0.0.8inChapter11onpage131 nallyintroduceddefine,delayeduntilthenforreasonsIwon'treiteratehere.Akintoset!,definetakesasymbolandavalueandbindsthesymboltothevalueinthecurrentenvironment.Itisaspecialformsinceitdoesnotevaluatethesymbol.ThecpsrewriteofPScm::SpecialForm::Define::Apply()proceedsmuchastheoneforset!did:246subApplyf247my($self,$form,$env,$cont)=@_;248my($symbol,$expr)=$form->value;249250$expr->Eval(251$env,252contf253my($value)=@_;254$cont->Cont($env->Define($symbol,$value));255g256);257gItevaluatestheexpression,passingacontinuationthatacceptstheresultandcallsPScm::Env::Define()tobindtheresulttothesymbolinthecurrentenvironment.WecantreatthecalltoPScm::Env::Define()asasimpleexpressionandjustcalltheoriginalcontinuationontheresult.Fullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.1.8.tgz 13.6.IMPLEMENTATION21313.6.9cpsoopVersion0.0.9inChapter12onpage135introducedanobject-orientedextensiontoPScheme,bymeansofthemake-classspecialformandtherootparentclass.Althoughtherewasafairamountofcodeaddedtoimplementthatextension,itturnsoutthatverylittleofthatcodeneedschangingtoproduceacpsversion.AsusualwejusthavetohuntdownthecallstoEval()andensurethatthereisacontinuationavailabletopassin.Let'sstartwithPScm::SpecialForm::MakeClass::Apply().265subApplyf266my($self,$form,$env,$cont)=@_;267268my$parent_expr=$form->first;269my$fields=$form->rest->first;270my$methods=$form->rest->rest;271272$parent_expr->Eval(273$env,274contf275my($parent_class)=@_;276$cont->Cont(277PScm::Class->new(278$parent_class,$fields,$methods,$env279)280);281g282);283gThe rstthingtheoldversiondidwastoevaluatetheparentexpression(thevalueoftheparentclass)thenusethatalongwiththe eldsandmethods(unevaluated)tocreatethenewclass.Ourrewriteevaluatestheparentexpressionpassingacontinuationthatwillcreatetheclass.Wecantreattheclasscreationasasimpleexpression(itmakesnofurthercallstoEval())andjustcalltheoriginalcontinuationontheresult.Nothingtofollowuponthere,sonextweturnourattentiontotheapplicationofaclasstoarguments,whichcreatesanewobject.ThisisinPScm::Class::Apply():033subApplyf034my($self,$form,$env,$cont)=@_;035036my$new_object=$self->make_instance();037$new_object->call_method(038$new_object,039"init",$form,$env,040contf041$cont->Cont($new_object);042g043);044g 214CHAPTER13.CONTINUATIONSAsusual,Apply()nowtakesacontinuation.Theoldversioncalledmakeinstance()tocreateanewinstanceoftheclass,thencalledtheinitmethodofthenewobjectwiththeargumentstotheclass,thenreturnedthenewobject.Thecpsversioncanstilljustcallmakeinstance()asasimpleexpression,butneedstopassacontinuationtocallmethod()becausecallmethod()willbecallingEval()onboththeargumentsandthebodyofthemethod.Thecontinuationjustcallstheoriginalcontinuationonthenewobject(asifitwerereturningit).Rememberingthatobjectsinthisimplementationarejustenvironments,Here'stherewriteofPScm::Env::callmethod():192subcall_methodf193my($self,$this,$method_name,$args,$env,$cont)=@_;194195if(my$method=$self->_lookup_method($method_name))f196$method->ApplyMethod($this,$args,$env,$cont);197gelsef198$cont->Cont(undef);199g200gHey,thisisn'ttoobadatall.Ifcallmethod() ndsthemethod,itcallsApplyMethod()onit,passingtheoriginalcontinuationasanextraargument.Wherastheoldversionimplicitlyreturnedundefifthemethodwasnotfound,thecpsversionmustexplicitlycallthecontinuationonundef14.NextweneedtolookatPScm::Closure::Method::ApplyMethod():096subApplyMethodf097my($self,$this,$form,$env,$cont)=@_;098099$form->map_eval(100$env,101contf102my($evaluated_args)=@_;103$self->_apply(104PScm::Expr::List->Cons($this,$evaluated_args),105$cont);106g107);108gAgainweusemapeval()onthe$form(theargumentstothemethod)toevaluatethem,thistimepassingacontinuationthatappliesthemethodtoitsevaluatedarguments,usingPScm::Expr::List::Cons()toprependthecurrentobject$thistothePScm::Expr::ListofargumentstothecorePScm::Closure::apply()method,andpassingintheoriginalcontinuation.We'vealreadydiscussedthatcoreapply()methodinSection13.6.2onpage188whenwere-wrotelambda.Thatjustleavesthecallingofmethodsontheobjectsthemselves.BothPScm::EnvandPScm::Env::SuperhaveanApply()method.TheonefromPScm::Env::Superarrangestopassthecurrentvalueofthistothecalledmethod,otherwisetheyareverysimilar.Here'sPScm::Env::Apply():14Notethatcallingacontinuationonundefisnotthesameasreturningundef,thetrampolinewillneverseethisundefandterminatethecomputationprematurely. 13.6.IMPLEMENTATION215202subApplyf203my($self,$form,$env,$cont)=@_;204205my($method,$args)=($form->first,$form->rest);206$self->CallMethodOrDie($self,$method,$args,$env,$cont);207gAndhere'sPScm::Env::Super::Apply():231subApplyf232my($self,$form,$env,$cont)=@_;233234my($method,$args)=($form->first,$form->rest);235my$this=$env->LookUp(PScm::Expr::Symbol->new("this"));236$self->CallMethodOrDie($this,$method,$args,$env,$cont);237gTheybothnowmakeuseofanewsupportmethodPScm::Env::CallMethodOrDie()whichjustdoeswhatitsays.209subCallMethodOrDief210my($self,$this,$method,$args,$env,$cont)=@_;211$self->call_method(212$this,213$method->value,214$args,$env,215contf216my($res)=@_;217if(defined$res)f218$cont->Cont($res);219gelsef220die"method",$method->value,"notfound ";221g222g223);224gItcallscallmethod()on$selfandpassesacontinuationthatchecksiftheresultisde ned.Iftheresultisde nedthatmeansthatcallmethod()sucessfullyfoundthemethodandinvokedit,inwhichcasetheoriginalcontinuationiscalledontheresult.IftheresultisundefthenthemethodwasnotfoundandCallMethodOrDie()livesuptoitsname.TomakesomeofthesubsequenttestseasierI'veaddedaprintprimitive.IttakesasingleevaluatedargumentandcallsitsPrint()method.Theonlyproblemtosolveishowtotellitaboutthecurrentoutput lehandle.IdeallywewouldcreateanewPScm::Expr::FileHandletypeandinstallonecon-tainingthedefaultoutput lehandleintheenvironmentforprintto nd,howeveritiseasierforourlimitedpurposestojustpasstheoutput lehandletothePScm::SpecialForm::Printconstructor.YoucanseethisinthechangestoReadEvalPrint()between0.0.9and0.1.9here: 216CHAPTER13.CONTINUATIONS033subReadEvalPrintf034my($infh,$outfh)=@_;035036$outfh||=newFileHandle(">-");037my$reader=newPScm::Read($infh);038my$initial_env;039$initial_env=newPScm::Env(040let=>newPScm::SpecialForm::Let(),041'*'=>newPScm::Primitive::Multiply(),042'-'=>newPScm::Primitive::Subtract(),043'+'=>newPScm::Primitive::Add(),044if=>newPScm::SpecialForm::If(),045lambda=>newPScm::SpecialForm::Lambda(),046list=>newPScm::Primitive::List(),047car=>newPScm::Primitive::Car(),048cdr=>newPScm::Primitive::Cdr(),049cons=>newPScm::Primitive::Cons(),050letrec=>newPScm::SpecialForm::LetRec(),051'let*'=>newPScm::SpecialForm::LetStar(),052eval=>newPScm::SpecialForm::Eval(),053macro=>newPScm::SpecialForm::Macro(),054quote=>newPScm::SpecialForm::Quote(),055'set!'=>newPScm::SpecialForm::Set(),056begin=>newPScm::SpecialForm::Begin(),057define=>newPScm::SpecialForm::Define(),058'make-class'=>newPScm::SpecialForm::MakeClass(),059'call/cc'=>newPScm::SpecialForm::CallCC(),060print=>newPScm::SpecialForm::Print($outfh),061);062063$initial_env->Define(064PScm::Expr::Symbol->new("root"),065PScm::Class::Root->new($initial_env)066);067trampoline(contfrepl($initial_env,$reader,$outfh)g);068gThePScm::SpecialForm::Printpackageisunusual,therefore,inthatithasanew()methodandcreatesitsowninstance,becauseitneedstosavetheargument lehandle.Otherthanthatitisastandardcpsspecialform:286packagePScm::SpecialForm::Print;287288usebaseqw(PScm::SpecialForm);289290usePScm::Continuation;291 13.6.IMPLEMENTATION217292subnewf293my($class,$outfh)=@_;294blessfoutfh=>$outfhg,$class;295g296297subApplyf298my($self,$form,$env,$cont)=@_;299$form->first->Eval($env,contf300my($thing)=@_;301$thing->Print(302$self->foutfhg,303contf304$cont->Cont($thing)305g306);307g);308g3093101;PScm::SpecialForm::Print::Apply()invokesitsargument$form'sEval()methodwiththecurrentenvironmentandacontinuationthatwillprinttheresult,thencalltheoriginalcontinuationontheevaluatedresult.Soprintreturnstheexpressionthatitprinted.Fullsourcecodeforthisversionoftheinterpreterisavailableathttp://billhails.net/Book/releases/PScm-0.1.9.tgz 218CHAPTER13.CONTINUATIONS13.7cpsWithoutClosuresPerlhasmadeitrelativelyeasyforustoimplementcpsbypassingclosuresascontinuations,albeitwithanobjectwrapper.Weshouldbegladofthatandcontinuetousethisfeature.Howeverthequestionmustbeasked:howwouldwegoaboutimplementingcpsinalanguagethatdoesnotsupportclosures?Theanswer,oroneanswerinanycaseistorollourownclosuresasseparateobjects.ThiswouldmeanaseparateclassforeveryoccurrenceofthecontfgconstructinthePSchemesource.Theobjectwouldbeinitializedwithallofthevaluesthattheclosurereferredto,andamethodintheclasswouldperformthesamedutyastheindividualclosure,referringtothosesavedvaluesasattributesof$selfratherthanaslexicalvariables.ConsiderthePScm::Expr::List::Eval()methodwediscussedearlyon,initscpsform:063subEvalf064my($self,$env,$cont)=@_;065returncontf066$self->first()->Eval(067$env,068contf069my($op)=@_;070$op->Apply($self->rest,$env,$cont);071g072);073g;074gYoucanseethreecontinuationsinthisonemethod:thecontinuation$contpassedinasargument,theoutercontfgreturnedtothetrampoline,andtheinnercontfgthatappliestheevaluated$optotheas-yetunevaluatedarguments.Thereisalotofdependancyonthelexicalscopeofvariousvariablesinthiscode.Ifweweregoingtodothiswithoutclosureswewouldhavetomakeallthatexplicit.Here'sanattempt:subEvalfmy($self,$env,$cont)=@_;returnnewBouncer(newListEvalFirstCont($self,$env,$cont));gTheBouncerclasswouldcopewithanycontinuationsreturnedtothetrampoline,itwouldhaveanew()methodtocapturetheargumentcontinuationandaBounce()methodthatinvokedthecapturedcontinuation:packageBouncer;subnewfmy($class,$cont)=@_;blessfcont=>$contg,$class;g 13.7.CPSWITHOUTCLOSURES219subBouncefmy($self)=@_;$self->fcontg->Cont();gThenListFirstEvalContwouldneedanew()method,andaCont()methodofnoarguments,sincethatiswhattheBouncerwouldcallonitit:packageListEvalFirstCont;subnewfmy($class,$list,$env,$cont)=@_;blessflist=>$list,env=>$env,cont=>$cont,g,$class;gsubContfmy($self)=@_;$self->flistg->first()->Eval($self->fenvg,newListEvalRestCont($self->flistg,$self->fenvg,$self->fcontg););gYoucanseethattheCont()methodhereisdoingthesamethingthattheclosurewasdoingintheoriginalcode,butitinturnmustcreateanewListEvalRestContobjectratherthanaclosure.ThatListEvalRestContwouldinturnneedanew(),andaCont()method,sincethatiswhattheoperationsEval()methodwouldcall:packageListEvalRestCont;subnewfmy($class,$list,$env,$cont)=@_;blessflist=>$list,env=>$env,cont=>$cont,g,$class;gsubContf 220CHAPTER13.CONTINUATIONSmy($self,$op)=@_;$op->Apply($self->flistg->rest,$self->fenvg,$self->fcontg);gAndthat'snottheendofit,sincereallythatlastCont()methodshouldbereturningacontinuationtothetrampolineratherthanjustcallingApply()directly...Admittedlythisisjust rstpassuntestedcodetogiveyouanidea,butI'mnotwritingthisjusttoscareyouo .Thepointtonoteisthattherearethreebasicthingsthathavetobekepttrackofwhenimplementingcpswithoutcontinuations.Oneisthecurrentpositioninthecontrol ow(inthiscasethelistbeingevaluated.)Thesecondisthecurrentenvironment:thevaluesofvariablesthatthecontinuationsneedtoexecute;thethirdisthecontainingcontinuation.Infactwhattheclosureimplementationmakesimplicitande ectivelyhidesfromusisthatthereisachainofcontinuations,fromclosuretoclosure,backtotheoriginatingcontinuationintherepl.Thisisanimportantobservation.ItwillgreatlysimplifythewritingofaPSchemecompilerlater.Thereareevensomeadvantagestoimplementingcontinuationsinthisway.Primarilybecausethechainofcontinuationsisexplicit,itcanbetraversedandsearchedmakingallsortsofadditionalcontrol owconstructseasiertoimplement.Forexampleatry/throw/catch/resumemechanismneedmerelytraversebackupthecontinuationchainlookingforacatchingcontinuation,invokeitwiththeoriginatingcontinuation,andifthecatchblockcould xtheproblemitwouldresumefromtheinstructionafterthethrow.Whilethisispossiblewithourexistingimplementation,itismoretricky.13.8cpsFunWe'redone!Let'splayaroundwithwhatwehave.13.8.1AnerrorHandlerTherearemanyusesofcall/cc,thesimplestisprobablythede nitionofescapeprocedures,proceduresthatwhencalled,escapethecurrentcontextandreturncontroltoacontainingoutercontext.Thesimplesttypeofescapeprocedureiserror.Ifatanypointinyourcodeyouinvokeerror,theargumenterrormessageisprinted,thecurrentcontextisabandonedandcontrolisreturnedtothetoplevel.Forexample(pretendingwehaveadivide/"operator):>(definediv>(lambda(numeratordenominator)>(ifdenominator>(/numeratordenominator)>(error"divisionbyzero"))))div>(+(div20)1)Error:divisionbyzero()> 13.8.CPSFUN221Theadditionneverhappened.Controlreturneddirectlytothetoplevel.Usingcall/cc,wecande neanerrorescapeprocedureinthePSchemelanguageitself,withoutneedingtomakefurtherchangestotheinterpreter.Allerrorhastodoistoprintitserrormessageandcallacontinuationthatreturnscontroltothetoplevel.So,assumingthattoplevelcontinuationisalreadyinstalledas^escape(I'mjustusingacaret,^",topre xanycontinuationnamessotheystandout,)theerrorprocedureitselfisstraightforward:(defineerror(lambda(msg)(begin(printmsg)(^escape()))))Notethatthe^escapecontinuationexpectsanargument,soerrorpassestheemptylist(),andthatiswhatthereplprintsastheresultofwhateverexpressionerroriscalledfrom.Nextweneedtocreatethattoplevelcontinuation.Here'sa rstattempt:(define^escape(call/cc(lambda(c)c)))Thislookspromising.Thecall/cccallstheanonymouslambdaexpressionpassinginthecurrentcontinuation.Thelambdaexpressionjustreturnsitsargument,whichiswhatcall/ccreturns,andthatiswhatgetsboundtotheglobal^escape.Thisisgood,asfarasitgoes,thecurrentcontinuationcertainlyisboundto^escape.Theproblemisthattheoperationofde ning^escapeispartofthecontinuationsavedin^escape.Putanotherway,the rsttimeerrorcalls^escape,controlresumesatthepointthatcall/ccisreturningitsvalueandsothedefineisre-executed,bindingtheemptylistto^escapeandforgettingthepreviouslyboundcontinuation.Sothe^escapecontinuationisonlyuseableonce.Therearetwowaysto xthis.The rstwaywouldbetochangeerrorsothatitpasses^escapeasargumenttoitself:(^escape^escape),ThedownsideofthatisthatyouwillgetaPScm::Continuationprintedoutastheresultofanycalltoerror.Theotherwayto xthisistochangethewaywesetup^escapeinthe rstplace.Firstofallwecreateaglobal^escapevariablewithanarbitraryinitialbinding:(define^escape0)Thenweusecall/ccasbeforebutcallanexpressionthatdirectlyassignsto^escape:(call/cc(lambda(cont)(begin(set!^escapecont))))Thiswaythecall/cchasnoenclosingcontext,nodeferredoperationstoperform,andwhen^escapeisinvokedcontrolreturnsdirectlytothetoplevel.Atestoftheerrorhandler,whichjustduplicatestheabovecode,isinListing13.10.3onpage227.Nextlet'strysomethingabitmorechallenging. 222CHAPTER13.CONTINUATIONS13.8.2yieldRememberthatstrangeFibonaccigeneratorthatwasdescribedtowardstheendofSection13.6.2onpage188,theonethatusedahypotheticalyieldcommandtoreturnavaluewhilerememberingitscurrentpositionsothatasubsequentcalltothefunctionwouldresumewhereitlefto ?Wellhere'showwe'dliketowriteitinPScheme:(definefib(yielder(letrec((fib-loop(lambda(ij)(begin(yieldi)(fib-loopj(+ij))))))(fib-loop01))))andhere'showwe'dliketocallit:(let((n10))(whilen(begin(set!n(-n1))(print(fib)))))Notethatthereisnothingatallspecialaboutthecodethatusesfib.Alloftheinterestingdetailsareinthede nitionoffibitself.Thefunctionfibisde nedasayielder(myterm).Itcreatesarecursivehelperroutinefib-loopandcallsitwitharguments0and1.Thathelperroutineyieldsthecurrentvalueofits rstargumenti,thencallsitselfwithargumentireplacedbyjandjreplacedwithi+j.Thesecondpartoftheexampleloops10timesprintingthenextvaluefromfibeachtimearoundtheloop.OfcoursethispresupposesafewfeaturesthatPSchemedoesn'tappeartohave.ThewhilemacrowasalreadyintroducedinSection9.2.2onpage112,buthereitisagainjustforcompleteness:(definewhile(macro(testbody)'(letrec((loop(lambda()(if,test(begin,body(loop))()))))(loop))))Youcanseethatit'sarecursivede nition,butbynowthatshouldn'tworryyou:aseachcalltoloopisintailpositionwewon'tgrowanycontextoreatanystackwiththisde nition. 13.8.CPSFUN223Theothertwofeatureswedon'thaveyetareyieldandyielder.yieldisactuallyquiteeasytowrite.Whenwesay(yieldvalue),whatwereallywanttodoisreturnnotonlythevalue,butalsothecurrentcontinuationsothatthenexttimewearecalledthatcontinuationcanbecalledinstead,returningustowherewelefto .Wecanreturnmorethanonevaluebywrappingthevaluesinalist,sonotionally(yieldvalue)means:(^return(listcurrent-continuationvalue))where^returnisanothercontinuationsetupbythecallerofthefunction.Nowweneedawayofgettingthecurrentcontinuationandtheonlywaytodothatiswithcall/ccso,stillnotionally,butgettingcloser,(yieldvalue)means:(call/cc(lambda(current-continuation)(^return(listcurrent-continuationvalue))))Infactthat'sit,allweneedtodoiswrapthatupinamacro,andwehaveyield:(defineyield(macro(value)'(call/cc(lambda(^here)(^return(list^here,value))))))Nextweneedtolookatyielder.Youprobablywon'tbesurprisedto ndoutit'sanothermacro.Itreturnsafunctionthat,whencalledforthe rsttime,invokesthebodyoftheyielderexpression,savingthecurrentcontinuationina^returnvariable.Whenyieldisinvokedcontrolreturnsthroughthe^returncontinuation.The^herecontinuationpartofthereturnedresultissavedandthevaluepartisreturnedbythefunctionasawhole.Onsubsequentcalls,ratherthaninvokingthebodyoftheyielderagain,thesaved^herecontinu-ationisinvoked,returningcontroltowheretheyieldlefto .Here'syielder:(defineyielder(macro(body)'(let((firsttime1)(^resume0)(^return0))(lambda()(iffirsttime(let((res(call/cc(lambda(^cont)(begin(set!^return^cont),body)))))(begin(set!firsttime0)(set!^resume(carres))(car(cdrres)))) 224CHAPTER13.CONTINUATIONS(let((res(call/cc(lambda(^cont)(begin(set!^return^cont)(^resume))))))(begin(set!^resume(carres))(car(cdrres)))))))))Again,continuationsare aggedwithacaret.Thatreallyisallthereistoit.Ifthisseemslikeasolutionlookingforaproblem,thenletmepointoutaveryrealproblemforwhichthiswouldbetheperfectsolution.ConsiderthevenerableFile::FindpackagefromthecorePerldistribution.Thispackageallowsyoutospecifycriteriaforrecursivelylocating lesina lesystem,alongwithacallbacksubroutinewhichiscalledoneach lesolocated.Sometimesthisisexactlywhatyouneed,butsometimesyouwouldpreferFile::Findtobehaveasaniterator,returningthenext lefoundoneachcall.Withpropersupportforco-routinesinperl,thecallbackfunctionthatyouprovidetoFile::Findneedonlyyieldthe letoachieveexactlythat,withoutchangingasinglelineoftheFile::Findpackageitself!Testsforthisyieldfeature,whichjustduplicatetheabovecode,areinListing13.10.4onpage228.13.9SummaryThishasbeenalongchapterandadicultone,particularilyifyouwereunfamiliarwiththesubjectofcontinuations.Ithashoweverprovidednumerousreal-worldexamplesofcpsduringtherewriteoftheinterpreterandhopefullythebasicprinciplesofcpshavebeenwellcovered.Toreiteratethebasicidea,continuationscanbethoughtofastherestofthecomputation",orperhapsmoregraphicallyasareferencetoareturnstatement"thatcanbecalledasafunction.Anyway,havingachievedacpsinterpreterinSection13.6.2onpage188wethenintroducedthecall/ccform,whichpassesthecurrentcontinuationtoitsargumentfunction.IfPerlfunctionscouldtakeareferencetotheirreturnstatementwithasyntaxlikereturn,thenwecouldwritecall/ccinperllikethis:subcall_ccfmy($sub)=@_;$sub->(return);gUnfortunatelyPerl5doesnotsupportthatsyntaxyet,butlestyouthinkthisisallirrelevanttoPerl,youshouldbeawarethattheParrotvirtualmachinewhichwillrunPerl6hascontinuationsbuiltinfromthegroundup!Finally,havingcompletelyre-workedtheinterpreterincps,inSection13.8onpage220weshowedhowwecouldusecall/ccinconjunctionwithmacrotocreatetwohigh-levelcontrolconstructs:errorandyieldfromwithinthePSchemelanguageitself.Thenextfewchapterswilltakecontinuationsalittlefurther,toshowthesortsofthingsthatcanbedonebyvaryingtheinternaldetailsoftheimplementationofcontinuations. 13.10.LISTINGS22513.10Listings13.10.1PScm/Continuation.pm001packagePScm::Continuation;002003usestrict;004usewarnings;005usebaseqw(PScm);006007requireExporter;008009our@ISA=qw(PScmExporter);010011our@EXPORT=qw(cont);012013subnewf014my($class,$cont)=@;015blessfcont=>$contg,$class;016g017018subcont(&)f019my($cont)=@;020returnPACKAGE->new($cont);021g022023subApplyf024my($self,$form,$env,$cont)=@;025$form->mapeval(026$env,027contf028my($raevaluatedargs)=@;029$self->Cont($raevaluatedargs->[0]);030g031);032g033034subContf035my($self,$arg)=@;036returncontf$self->fcontg->($arg)g;037g038039subBouncef040my($self)=@;041$self->fcontg->();042g043044subEvalf045my($self,$env,$cont)=@;046$cont->Cont($self);047g0480491; 226CHAPTER13.CONTINUATIONS13.10.2t/PScmCallCC.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>2;006007BEGINfuseok('PScm')g008009evalok(<2;006007BEGINfuseok('PScm')g008009evalok(<2;006007BEGINfuseok('PScm')g008009evalok(<Bounce()whiledefined$cont;073gIfyouthinkaboutit,acontinuationalreadyrepresentsasinglethreadofcomputation.Thetrampolineisjustmanagingthatsinglethread,ensuringthatitdoesnotconsumetoomuchstack.Supposethattrampoline(),insteadofjustrepeatedlyinvokingthecurrentcontinuation,keptaqueueofcontinua-tions,andafterbouncingtheoneatthefrontofthequeueputtheresultontothebackofthequeue(iftheresultwasnotundef,)loopinguntilthequeuewasempty.Thisversiondoesexactlythat:101subtrampolinef102while(@thread_queue)f103my$cont=shift@thread_queue;104$cont=$cont->Bounce();105push@thread_queue,$contifdefined$cont;106g107gNotethatitnolongertakesanargumentcontinuation,insteaditgetsthenextcontinuationfromthefrontofthequeue.@threadqueueisanewlexicalmy"variableinthePScmpackage.Weplacenewthreadsonthatqueuewithanewthread()method,alsointhePScmpackage:096subnew_threadf097my($self,$cont)=@_;098push@thread_queue,$cont;099gVerysimple.ittakesacontinuationandpushesitontothequeue.NextweneedawayofcreatingthreadsfromthePSchemelanguage.Thisisdoneusinganewspecialformspawn.spawntakesnoargumentsandreturns0toonethreadand1totheother.Thismeansyou231 232CHAPTER14.THREADScanwritecodethatdoesdi erentthingsindi erentthreadsbytestingtheresult,muchliketheunixforksystemcalldoes:>(if(spawn)>(begin>(print"hello")>(print"hello")>1)>(begin>(print"goodbye")>(print"goodbye")>(exit)))"hello""goodbye""hello""goodbye"1Noticethatalthoughboththreadsruninparallel,onethreaddoesan(exit)soonlytheresult1fromtheotherthreadgetsprinted.spawnisanewspecialforminPScm::SpecialForm::Spawn,andit'ssurprisinglyeasytoimplement:286packagePScm::SpecialForm::Spawn;287288usebaseqw(PScm::SpecialForm);289usePScm::Continuation;290291subApplyf292my($self,$form,$env,$cont)=@_;293294PScm->new_thread(contf295$cont->Cont(newPScm::Expr::Number(0));296g);297298$cont->Cont(newPScm::Expr::Number(1));299gOnLine294itcallsnewthread()withanewcontinuationthatwillcallthecurrentcontinuationwithargument0,andonLine298itdirectlycallsthecurrentcontinuationwithanargumentof1.ThisissoeasyIfeellikeIhavecheated!,butreallythat'sallthereistoit.ThenewcontinuationwillgetexecutedinturnwhentheCont()onLine298returnscontroltothetrampoline,andthetrampolinewillcontinueexecutinganythreadsonitsqueueuntilallthreadshave nishedandthequeueisempty.exitisevenmoretrivial.Ithastobeaspecialformbecauseindividualprimitivesdonotgetcalledintailposition,butallthatithastodoistoreturnundeftothetrampoline:327packagePScm::SpecialForm::Exit;328 233329usebaseqw(PScm::SpecialForm);330331subApplyf332my($self,$form,$env,$cont)=@_;333returnundef;334g3353361;Incidentally,exitprovidesausefulwayofterminatingtheinteractiveinterpreter.Typing(exit)atthepromptwhileonlyonethreadisrunningwillresultinanempty$threadqueuesothetrampolinewill nish.Allthatremainsistowirethisintotherepl:035subReadEvalPrintf036my($infh,$outfh)=@_;037038$outfh||=newFileHandle(">-");039my$reader=newPScm::Read($infh);040my$initial_env;041$initial_env=newPScm::Env(042let=>newPScm::SpecialForm::Let(),043'*'=>newPScm::Primitive::Multiply(),044'-'=>newPScm::Primitive::Subtract(),045'+'=>newPScm::Primitive::Add(),046if=>newPScm::SpecialForm::If(),047lambda=>newPScm::SpecialForm::Lambda(),048list=>newPScm::Primitive::List(),049car=>newPScm::Primitive::Car(),050cdr=>newPScm::Primitive::Cdr(),051cons=>newPScm::Primitive::Cons(),052letrec=>newPScm::SpecialForm::LetRec(),053'let*'=>newPScm::SpecialForm::LetStar(),054eval=>newPScm::SpecialForm::Eval(),055macro=>newPScm::SpecialForm::Macro(),056quote=>newPScm::SpecialForm::Quote(),057'set!'=>newPScm::SpecialForm::Set(),058begin=>newPScm::SpecialForm::Begin(),059define=>newPScm::SpecialForm::Define(),060'make-class'=>newPScm::SpecialForm::MakeClass(),061'call/cc'=>newPScm::SpecialForm::CallCC(),062print=>newPScm::SpecialForm::Print($outfh),063spawn=>newPScm::SpecialForm::Spawn(),064exit=>newPScm::SpecialForm::Exit(),065);066067$initial_env->Define( 234CHAPTER14.THREADS068PScm::Expr::Symbol->new("root"),069PScm::Class::Root->new($initial_env)070);071__PACKAGE__->new_thread(contfrepl($initial_env,$reader,$outfh)g);072trampoline();073gApartfromtheadditionofspawnandexittotheinitialenvironment,thereisonlyonechange.Thereplusesnewthread()toaddtheinitialthread(continuation)tothe@threadqueuethencallstrampoline()withnoarguments,ratherthanpassingthecontinuationdirectlytotrampoline().14.1VariationsAmorecompletethreadimplementationwouldalsoprovidemechanismsforcollectingtheresultofonethreadinanotherwithawaitcommand|notsoeasy,you'dneedtoputthewaitingthreadonaseparatequeueandhavetheexitcommandtakeanargumentandputitsomewherethatthewaitcommandcould nd.Youwouldalsoneedtobeabletopreventconcurrentaccesstosectionsofcode,bestdonewithsomesortofatomicsemaphoreoperation.Butatomicityiseasytoguaranteeattheleveloftheinterpreterinternals,aslongasnocontinuationsarecalledduringtheclaimingofasemaphore.Thesevariationsareexercisesyoucantryathome.14.2TestsAsimpletestforspawnisinListing14.3.1onthefacingpage. 14.3.LISTINGS23514.3Listings14.3.1t/CPSSpawn.t001usestrict;002usewarnings;003useTest::More;004uselib't/lib';005usePScm::Testtests=>3;006007BEGINfuseok('PScm')g008009evalok(<-");039my$reader=newPScm::Read($infh);040my$initial_env;041$initial_env=newPScm::Env(042let=>newPScm::SpecialForm::Let(),043'*'=>newPScm::Primitive::Multiply(),044'-'=>newPScm::Primitive::Subtract(),045'+'=>newPScm::Primitive::Add(),046if=>newPScm::SpecialForm::If(),047lambda=>newPScm::SpecialForm::Lambda(),048list=>newPScm::Primitive::List(),237 238CHAPTER15.BETTERERRORHANDLING049car=>newPScm::Primitive::Car(),050cdr=>newPScm::Primitive::Cdr(),051cons=>newPScm::Primitive::Cons(),052letrec=>newPScm::SpecialForm::LetRec(),053'let*'=>newPScm::SpecialForm::LetStar(),054eval=>newPScm::SpecialForm::Eval(),055macro=>newPScm::SpecialForm::Macro(),056quote=>newPScm::SpecialForm::Quote(),057'set!'=>newPScm::SpecialForm::Set(),058begin=>newPScm::SpecialForm::Begin(),059define=>newPScm::SpecialForm::Define(),060'make-class'=>newPScm::SpecialForm::MakeClass(),061'call/cc'=>newPScm::SpecialForm::CallCC(),062print=>newPScm::SpecialForm::Print($outfh),063spawn=>newPScm::SpecialForm::Spawn(),064exit=>newPScm::SpecialForm::Exit(),065error=>newPScm::SpecialForm::Error(066$outfh,067contfrepl($initial_env,$reader,$outfh)g068),069);070071$initial_env->Define(072PScm::Expr::Symbol->new("root"),073PScm::Class::Root->new($initial_env)074);075__PACKAGE__->new_thread(contfrepl($initial_env,$reader,$outfh)g);076trampoline();077gYoucanseethetokenerror"beingboundtoanewPScm::SpecialForm::Errorobject,andtheconstructorforthatobjectispassedboththecurrent$outfhandacontinuationwhichjustcallsrepl()withappropriatearguments.TheconstructorforPScm::SpecialForm::Errorjuststashesitsarguments:348subnewf349my($class,$outfh,$cont)=@_;350blessf351outfh=>$outfh,352cont=>$cont,353g,$class;354gWhenweinvokeerrorwithforexample(error"myerrormessage")itsApply()methodisinvoked.Hereitis:356subApplyf 15.2.USINGTHEERRORBUILTINFORINTERNALERRORS239357my($self,$form,$env,$cont)=@_;358359$form->first->Eval($env,contf360my($msg)=@_;361$self->do_error($msg->display_string());362g);363gIthastousecpsbecausetheerrormessageitselfmightbecomputed,wecan'tjustassumethatitisalreadyastring.SoitEval()'sthemessage,passinginacontinuationthatwill rstofallconverttheresultingmessagetoastringsuitablefordisplay,andthencallasecondarymethoddoerror()onthatstring.thedisplaystring()methodisde nedinPScm::Exprtojustcallasstring():036subdisplay_stringf$_[0]->as_string()gbutPScm::Expr::Stringoverridesthistocall$self->value()instead:276subdisplay_stringf$_[0]->valuegTheupshotofthisisthattheerrormessage,ifit'saPScm::Expr::String,won'tbewrappedinquoteswhenprintedwhichiswhatthePScm::Expr::String::asstring()methodwouldhavedone.ReturningtoPScm::SpecialForm::Error,doerror()isalsoquitesimple:365subdo_errorf366my($self,$errstr)=@_;367$errstr=~s/ $//;368$self->foutfhg->print("Error:",$errstr," ");369return$self->fcontg;370gItexpectsonlyasimpleperlstring.itstripsanytrailingnewlinefromtheerrormessage,printsittothestoredoutput lehandle,thenreturnsthestoredcontinuationtothetrampoline.Thatcontinuationwillrestarttherepl,skippingtheprintstageofthecurrentloop.Apartfrommakingthecodealittleeasierontheeye,thereisanotherreasonforhavingaseparatedoerror()method,andthatbringsustothesecondpartofthischapter.15.2UsingtheerrorBuiltinforInternalErrorsItwouldbeveryusefulifwecouldavailourselvesofthiserrorbuiltintoreportandrecoverfrominternalerrorssuchastypecheckandvariablelookupfailures.Thisisactuallyeasytodo.Allwehavetodoislookuptheerrorhandlerinthecurrentenvironmentandinvokeitsdoerror()method.AnewmethodError()inthePScmbaseclassdoesexactlythis,andsoisavailableeverywhere:119subErrorf120my($self,$msg,$env)=@_;121my$error=$env->LookUp(newPScm::Expr::Symbol('error'));122$error->do_error($msg);123g 240CHAPTER15.BETTERERRORHANDLINGTheonlythingithastobecarefulofisthatitcallsdoerror()intailposition,sothatthecontinuationgetsreturnedtothetrampoline.Let'slookatafewplaceswherewecanmakeuseofthisnewmethod.Ifyouremember,waybackinSection3.6onpage27wesawhowthevariousprimitiveoperationsmadeuseofachecktype()method,whichwoulddieiftheargumentobjectwasnotofthedesiredtype.Nowwecancheatalittle,andratherthanrewritingthoseprimitivesincps,wejustcatchtheerrorwitha(Perl)evalinthesharedPScm::Primitive::Apply()method,andcallError()withargument$@ifanerrorwasdetected.Here'sthepreviousversionofthatPScm::Primitive::Apply():008subApplyf009my($self,$form,$env,$cont)=@_;010011$form->map_eval(012$env,013contf014my($evaluated_args)=@_;015$cont->Cont($self->_apply($evaluated_args->value));016g017);018gandhere'sthechanges:008subApplyf009my($self,$form,$env,$cont)=@_;010011$form->map_eval(012$env,013contf014my($evaluated_args)=@_;015my$result=evalf016$self->_apply($evaluated_args->value);017g;018if($@)f019$self->Error($@,$env);020gelsef021$cont->Cont($result);022g023g024);025gItissafeforApply(),onLine16toevaluatetheindividualprimitiveseparately,sinceitisnotincpsform.Thenallithastodoiseithercallthecurrentcontinuationontheresult,orinvokeError()with$@,bothcallsbeingintailposition. 15.2.USINGTHEERRORBUILTINFORINTERNALERRORS241Apartfromprimitiveexpressions,anotherplacewherewethrowanexceptiononarecoverableerrorisintheLookUp()methodofPScm::Env,whenwedon't ndabindingforavariable.UnfortunatelyLookUp()wastreatedasasimpleexpressioninourcpsrewrite,soweneedtobacktrackto ndthecpscodethatinvokesLookUp()inordertoinstalltheerrorhandling.Fortunatelythereisonlyoneplacewherethathappens,whenasymbolisevaluated.Here'sthepreviousPScm::Expr::Symbol::Eval().103subEvalf104my($self,$env,$cont)=@_;105$cont->Cont($env->LookUp($self));106gandthechanges:230subEvalf231my($self,$env,$cont)=@_;232my$result=evalf$env->LookUp($self)g;233if($@)f234$self->Error($@,$env);235gelsef236$cont->Cont($result);237g238gAgain,aswiththeprimitiveApply()above,itissafeforittoexecutetheLookUp() rst,sinceLookUp()isnotincps.Then,dependingon$@,iteitherinvokesError()orcallsthecontinuationontheresultofthelookup.WenextmakeanidenticalchangetoPScm::SpecialForm::Set::Apply().Ifyouremembertheset!specialformusesPScm::Env::Assign()toreplaceanexistingbindingwithanewvalue,anditisanerrorifAssign()can't ndabindingtochange.ItisAssign()thatdiesifthebindingisnotfound,andsinceAssign()isasimpleexpressionthathasnotbeenrewrittenintocps,PScm::SpecialForm::Set::Apply()musttraptheexceptionandthrowthePSchemeerror.Firstlyhere'sPScm::SpecialForm::Set::Apply()beforethechanges:197subApplyf198my($self,$form,$env,$cont)=@_;199my($symbol,$expr)=$form->value;200$expr->Eval(201$env,202contf203my($val)=@_;204$cont->Cont($env->Assign($symbol,$val));205g206);207gandhere'sthechanges: 242CHAPTER15.BETTERERRORHANDLING197subApplyf198my($self,$form,$env,$cont)=@_;199my($symbol,$expr)=$form->value;200$expr->Eval(201$env,202contf203my($val)=@_;204my$result=evalf$env->Assign($symbol,$val)g;205if($@)f206$self->Error($@,$env);207gelsef208$cont->Cont($result);209g210g211);212gAnotherplacewherewediedwasinPScm::Env::populatebindings()wherewehandlethepossibilityofdotnotationandsinglevaluesintheformalargumentstoalambdaexpression.ThisroutineisonlycalledbyExtendUnevaluated(),butunfortunatelyExtendUnevaluated()isnotyetincpsform.Inthiscase,becauseExtendUnevaluated()iscalledfromanumberofplacesandallthoseplaceswouldhavetobeawarethatExtendUnevaluated()couldthrowaPerlexception,itseemsbettertorewriteExtendUnevaluated()intocps,andchangeitscallerstousethecpsform.Here'sthecpsversionofExtendUnevaluated().038subExtendUnevaluatedf039my($self,$symbols,$values,$cont)=@_;040041my%bindings;042evalf043$self->_populate_bindings(%bindings,$symbols,$values);044g;045if($@)f046$self->Error($@,$self);047gelsef048my$newenv=$self->new(%bindings);049$newenv->fparentg=$self;050$cont->Cont($newenv);051g052gMostofthemethodsthatcallExtendUnevaluated()arealreadyincpssowedon'treallyneedtoseethechangestothem.Onemethod,makeinstance()inPScm::Classisnotincps,soweneedtorewritethattoo: 15.2.USINGTHEERRORBUILTINFORINTERNALERRORS243048submake_instancef049my($self,$cont)=@_;050051$self->fparentg->make_instance(contf052my($parent_instance)=@_;053054$self->fenvg->ExtendUnevaluated(055newPScm::Expr::List(056PScm::Expr::Symbol->new("class"),#$self057PScm::Expr::Symbol->new("super"),#$parent_instance058@f$self->ffieldsgg,#0...059),060newPScm::Expr::List(061$self,#"class"062PScm::Env::Super->new(super=>$parent_instance)063,#"super"064((PScm::Expr::Number->new(0))x@f$self->ffieldsgg)065,#field...066),067$cont068);069g);070gAndtheequivalentmethodinPScm::Class::Root:098submake_instancef099my($self,$cont)=@_;100101$self->fenvg->ExtendUnevaluated(102newPScm::Expr::Symbol("class"),103$self,104$cont105);106gThecallerofmakeinstance(),PScm::Class::Apply(),wasalreadyincpssotransformingthattocallthecpsformofmakeinstance()istrivial:033subApplyf034my($self,$form,$env,$cont)=@_;035036$self->make_instance(contf037my($new_object)=@_;038$new_object->call_method(039$new_object,040"init",$form,$env, 244CHAPTER15.BETTERERRORHANDLING041contf042$cont->Cont($new_object);043g044);045g);046gThelastplacewherewedieunnecessarilyisinPScm::Env::CallMethodOrDie()whereitisanerrorifamethodcannotbefound.FortunatelyCallMethodOrDie()isalreadyincpssoitiseveneasiertochange.Here'stheoriginal:209subCallMethodOrDief210my($self,$this,$method,$args,$env,$cont)=@_;211$self->call_method(212$this,213$method->value,214$args,$env,215contf216my($res)=@_;217if(defined$res)f218$cont->Cont($res);219gelsef220die"method",$method->value,"notfound ";221g222g223);224gandherearethechanges:213subCallMethodOrDief214my($self,$this,$method,$args,$env,$cont)=@_;215$self->call_method(216$this,217$method->value,218$args,$env,219contf220my($res)=@_;221if(defined$res)f222$cont->Cont($res);223gelsef224$self->Error(225"method".$method->value."notfound ",226$env227); 15.3.TESTS245228g229g230);231gVerysimple:thediewasalreadyintailposition,sowhereitusedtodie,itinvokesError()instead.15.3TestsAfewsimpletestsforerrorareinListing15.4.1onthenextpage.Primarily,besidesdemonstratingthattheerrorbuiltinworks,theyshowthatthereplisstillupandrunningafterwards. 246CHAPTER15.BETTERERRORHANDLING15.4Listings15.4.1t/CPSBuiltInError.t001usestrict;002usewarnings;003useTest::More;004uselib't/lib';005usePScm::Testtests=>8;006007BEGINfuseok('PScm')g008009evalok(<(amb123)1>?2>?3>?Error:nomoresolutions>?Error:nocurrentproblemWhat'sgoingonhere?Wellambisgivenalistofvalues,andreturnsallofthem.Butitreturnsthemoneatatime.Whena?"istypedatthePSchemepromptcontrolbacktrackstoambanditreturnsitsnextresult.Sotheexecutionofexpressionsinvolvingambissomehowthreadedintotheread-eval-printloopitself.Ishouldprobablypointoutthatthisnewbehaviourisnotspeci ctoamb,butratherageneralpropertyoftheinterpreter:249 250CHAPTER16.CHRONOLOGICALBACKTRACKING>?Error:nocurrentproblem>(+22)4>?Error:nomoresolutions>?Error:nocurrentproblemTheError:nocurrentproblemmessagemeansjustthat:thereisnocurrentproblemsonoback-trackingispossible,wherastheError:nomoresolutionsmessagemeansthatthecurrentprob-lem"hasjustexhahstedallofitsposibilities.Withnooccurenceofambintheproblem"thereisonlyonepossibleoutcome(4inthe(+22)exampleabove)sothereplcontinuestobehaveasnormalfor ormal"input.ambwillonlyreturnasubsequentvalueifitistoldthatthepreviousvalueisnotacceptable.Onewayofdoingthat,aswehaveseen,isbytyping?"attheschemeprompt.Wecandothesamethingwithinourcodehowever,asI'lldemonstratenext:>(list(amb12)(amb'a'b))(1a)>?(1b)>?(2a)>?(2b)>?Error:nomoresolutionsNowthat'sinteresting.Therearetwocallstoamb,andlistcollectstheresults.Bestwegothroughthisonestepatatime.1.Theexpression rstreturnsalistofthe rstargumentstoeachcalltoamb,namely1anda.2.Whenwetelltheinterpreterthatwe'dliketoseemoreresultsbytyping?attheprompt,thesecondambcallinterceptstherequestandreturnsitssecondargument,sothewholeexpressionreturns(1b).3.Whenweaskforathirdresult,thesecondambagaininterceptstherequest,butthistimeithasrunoutofarguments,soitfailstosatisfytherequestandcontrolpropogatesbacktothe rstcalltoamb.The rstambnowreturnsitssecondresult,2,andcontrolpassesforwardsagaintothesecondamb.Thissecondambisnowbeingcalledafresh,asitwere,andisbackinitsinitialstatewhereitreturnsits rstargument,sothewholethirdresultis(2a).4.Therequestforafourthresultproceedsastherequestforthesecondresultdid,withthesecondambproducingb,resultingin(2b).5.Withthe fthand nalrequest,thesecondambagainfails,sopropogatesthefailurebacktothe rstamb,butthistimethe rstambhasalsoexhausteditsresults,sopropogatesthefailurebacktothecommandloopandwegettheerror". 16.1.INTRODUCINGAMB251ThediagraminFigure16.1attemptstoshowthiscontrol owinaction1.Figure16.1:Control owduring(list(amb12)(amb'a'b))read(list(amb12)(amb'a'b))Erroreval(amb12)12??(amb'a'b)(amb'a'b)1a1b2a2b????listlistlistlist(1a)(1b)(2a)(2b)????print"(1a)"?readprint"(1b)"?readprint"(2a)"?readprint"(2b)"?readSoinwhatwaydoesthisdemonstratethatwecancontrolthebacktrackingbehaviourofamb?Simple.Whenambitselffailsitpropogatescontrolbacktothechronologicallypreviouscalltoamb,justastypinga?"atthepromptdoes.Whenthesecondambcallranoutofoptionsintheexample,controlpropogatedbacktothe rstambcall.Nowacalltoambwithnoargumentsmustimmediatelyfail,becauseithasnoargumentstochoosefrom:1Ofcoursenoneofthiswouldbepossiblewithoutcontinuations.OnlywithcpsdowehaveasituationwhereRead()callsEval()andsoforth,butthat'sbestleftforSection16.3onpage266,whichdiscussesimplementation. 252CHAPTER16.CHRONOLOGICALBACKTRACKING>(amb)Error:nomoresolutionsSocallingambwithnoargumentsforcesanypreviousambtodeliverupitsnextvalue2.Wecanwrapthatbehaviourinafunctionthattestssomecondition,andforcesanotherchoiceiftheconditionisfalse.Thatfunctioniscalledrequire:(definerequire(lambda(x)(ifxx(amb))))Thereturnvalueofxifthetestsucceedsismerelyutilitarian,itisthecalltoambwithnoargumentsifthetestfailsthatisimportant.Sohowcanweuserequre?Wellforexamplelet'sassumewehaveapredicateeven?thatreturnstrueifitsargumentiseven.Wecanusethatto ltertheresultsofanearlieramb:>(let((x(amb123456)))>(begin>(require(even?x))>x))2>?4>?6>?Error:nomoresolutionsTheexpression(require(even?x)) lteredouttheoddvaluesofx,soonlytheevenvalueswerepropogatedtotheresult(s)oftheexpression.Youshouldbestartingtoseehowambandcpsaredeeplyinterlinked,andhowbacktrackingcanthereforereturntoanychronologicallypreviouspointinthecomputation,notjustdownthestack"toacallerofthecodethatinitiatesthebacktracking.16.2ExamplesofambinActionNowweknowwhatambdoes,whatcanweuseitfor?Thatexamplewith(requireeven?x)aboveshouldgiveyousomeidea,butinaword:search3.16.2.1TheLiars"PuzzleConsiderthefollowinglogicproblem,oneofaclassicandsimpletype4.2Howeveryoucan'ttype(amb)atthepromptinsteadof?"topromptbacktrackingonthepreviousproblem"becausetheinterpreterreguardsanythingotherthan?"asthestartofanewproblem."3I'mnottalkingaboutsearchingtheweb,Imeansearchingforsolutionstoproblems.4Thispuzzleappearsasanexercisein[1,p420]andtheyinturnaccreditHubertPhilips,1934,TheSphinxProblemBook. 16.2.EXAMPLESOFAMBINACTION253LiarsFiveschoolgirlssatforanexamination.Theirparents|sotheythought|showedanunduedegreeofinterestintheresult.Theythereforeagreedthat,inwritinghomeabouttheexamination,eachgirlshouldmakeonetruestatementandoneuntrueone.Thefollowingaretherelevantpassagesfromtheirletters:Betty:Kittywassecondintheexamination,Iwasonlythird."Ethel:You'llbegladtohearthatIwasontop.Joanwassecond."Joan:Iwasthird,andpooroldEthelwasbottom."Kitty:Icameoutsecond.Marywasonlyfourth."Mary:Iwasfourth.TopplacewastakenbyBetty."Whatinfactwastheorderinwhichthe vegirlswereplaced?ambmakesiteasytosolvethistypeofproblembymerelyenumeratingallthepossibilitiestheneliminatingthosepossibilitiesthatarewronginsomeway:>(defineliars>(lambda()>(let((betty(amb12345))>(ethel(amb12345))>(joan(amb12345))>(kitty(amb12345))>(mary(amb12345)))>(begin>(require(distinct?(listbettyetheljoankittymary)))>(require(xor(eq?kitty2)(eq?betty3)))>(require(xor(eq?ethel1)(eq?joan2)))>(require(xor(eq?joan3)(eq?ethel5)))>(require(xor(eq?kitty2)(eq?mary4)))>(require(xor(eq?mary4)(eq?betty1)))>'((betty,betty)>(ethel,ethel)>(joan,joan)>(kitty,kitty)>(mary,mary))))))liars>(liars)((betty3)(ethel5)(joan2)(kitty1)(mary4))Thebindingsintheletsupplyallpossiblegradestoeachofthegirls,thenthe rstrequireinthebodyoftheletmakessurethatallthegirlshavedi erentgrades:thedistinct?functiononlyreturnstrueiftherearenoduplicatesinitsargumentlist.I'llshowyoutheimplementation,andthatofotherfunctionshere,later.Theremainingrequirementssimplylistthetwopartsofeachgirl'sstatement,requiringthatoneistrueandoneisfalse:thexor(exclusiveor)functionreturnstrueonlyifoneofitsargumentsistrueandtheotherisfalse.Theeq?functiontestsiftwoexpressionsareequal. 254CHAPTER16.CHRONOLOGICALBACKTRACKINGSowestartoutbyrequiringthatall vegirlshavedistinctpositionsintheexamresults.Thewegoontorequirethatexactlyoneofeachofthegirlstwostatementsistrue.Finallywebuildandreturnalistofpairsofthegirl'snames,andtheassociatedpositionsthatsatisfyalltherequirements,usingquoteandunquote.Ofcoursethisishorriblyinecient.Thereare55=3125permutationsofbetty,ethel,joan,kittyandmary,andthat rstdistinctrequirementforcesare-evaluationofallbut5!=120ofthem5,soabout96%oftheinitialpossibilitiesareprunedatthe rststep,andbacktrackingisprovoked.Infact,whenwritingtestsforthisambexample,thissinglefunctiontooksolongtorun(about14secondsonmylaptop)thatIwasforcedto ndwaystooptimizeit.Theoptimizationsdemonstratesomeadditionalbehaviourofamb,sohere'stheoptimizedversion:(defineliars(lambda()(let*((betty(amb12345))(ethel(one-of(exclude(listbetty)(list12345))))(joan(one-of(exclude(listbettyethel)(list12345))))(kitty(one-of(exclude(listbettyetheljoan)(list12345))))(mary(car(exclude(listbettyetheljoankitty)(list12345)))))(begin(require(xor(eq?kitty2)(eq?betty3)))(require(xor(eq?ethel1)(eq?joan2)))(require(xor(eq?joan3)(eq?ethel5)))(require(xor(eq?kitty2)(eq?mary4)))(require(xor(eq?mary4)(eq?betty1)))'((betty,betty)(ethel,ethel)(joan,joan)(kitty,kitty)(mary,mary))))))Itstartsoutasbefore,settingbettytoanambchoicefromtheavailablepositions,butthencallsacoupleofnewfunctionstocalculatethevalueforethelandtherestofthegirls.excludereturnsalistofalltheelementsinitssecondlistthataren'tinits rstlist.Soforexampleifbettyis1,thenethelonlygetsthechoiceofvalues2through5.I'llshowyouexcludelater.one-ofismoreinteresting,sinceitmakesuseofrequireandamb.Itdoesthesamethingasamb,buttakesasinglelistofvaluesasargumentratherthanindividualarguments:(defineone-of(lambda(lst)(begin(requirelst)(amb(carlst)(one-of(cdrlst))))))5Yes,apracticalapplicationofthefactorialfunction! 16.2.EXAMPLESOFAMBINACTION255Firstlyitrequiresthatthelistisnotempty,thenitusesambtochooseeitherthecarofthelist,orone-ofthecdrofthelist.Thisinfactdemonstratesthatambmustbeaspecialform:thisfunctionwouldnotworkifambhaditsargumentsevaluatedforit;ifbothargumentstothatsecondambwereevaluatedbeforeambsawthemthenone-ofwouldgetrecursivelyexecuteduntilthelistwasempty,thenthe rstambtobeactuallyinvokedwouldbetheonethatterminatesrecursionwhen(requirelst)fails,sothisfunctionwouldalwaysfailifambwereaprimitive.Backtoouroptimizedliarsexample.Theuseoflet*insteadofletmakesthevaluesofthepreviousbindingsavailabletosubsequentones.Bythetimewegettoassigningtomary,thereisonlyonechoiceleft,sowejusttakeitwithcarratherthanusingone-of.Sinceourvaluesarenowguaranteedtobedistinct,wecanremovethatexplicitrequirementfromthecode,andtheoptimizedversionrunsinalittleunderasecondonthesamemachine.Aspromised,herearetherestoftheschemefunctionsneededtoimplementthesolutiontotheLiars"puzzleandotherexamplesseenearlier.Youcanskimtheseifyou'renotinterestedinthedetails,theydon'treallydoanythingnew.Tomakethesefunctionseasiertowrite(andread)I'veintroducedthebooleanshortcircuitingspecialformsandandortothisversionoftheinterpreter:(andab)willreturnawithoutevaluatingbifaisfalse,and(orab)willreturnawithoutevaluatingbifaistrue.Someofthesefunctionsalsomakeuseofnot.andandorhavebeenaddedasspecialformstotheinterpreterandsointeractwiththeambrewrite,soyou'llhavetowaittoseethose,butnotisjust:(definenot(lambda(x)(ifx01)))Andsotooursupportroutines.Firstlyeven?:(definedivisible-by(lambda(n)(lambda(v)(begin(defineloop(lambda(o)(if(eq?ov)1(if(>ov)0(loop(+on))))))(loop0)))))(defineeven?(lambda(a)((divisible-by2)a)))ThisisreallyjustdemonstratingthefunctionalprogrammingstylethatSchemepromotes6.Thefunctiondivisible-bytakesanargumentnumbernandreturnsanotherfunctionthatwillreturntrueifits6andthatwecanstillgetawaywithouta/"primitive. 256CHAPTER16.CHRONOLOGICALBACKTRACKINGargumentisdivisiblebyn.Itcreatesaninnerloopmethodwhichloopsover0;n;2n;3n:::untileitherequaltoorgreaterthanthenumberbeingtested.even?usesthistocreateafunctionthattestsfordivisibilityby2,andcallsitonitsargumenta.Itistotaloverkilltodoitthisway,butfun.Nextdistinct?:(definedistinct?(lambda(lst)(iflst(and(not(member?(carlst)(cdrlst)))(distinct?(cdrlst)))1)))distinct?saysifthelistisnotempty,thenitisdistinctifits rstelement(itscar)isnotamemberoftherestofthelistandtherestofthelistisdistinct.Ifthelistisempty,thenitisdistinct.distinctmakesuseofanotherfunction,member?,shownnext.(definemember?(lambda(itemlst)(iflst(or(eq?item(carlst))(member?item(cdrlst)))0)))member?determinesifitsargumentitemisamemberofitsargumentlst.Itsaysifthelistisnotempty,thentheitemisamemberofthelistifitisequaltothecarofthelistoramemberofthecdrofthelist.Theitemisnotamemberofanemptylist.member?usesanotherfunctioneq?totestequality,butthat'sbeenaddedtotheinterpreterasaprimitive,sowe'llleavethatforlater.Nextupforconsiderationisxor.xortakestwoargumentsandreturnstrueonlyifpreciselyoneofthoseargumentsisfalse.(definexor(lambda(xy)(or(andx(noty))(andy(notx)))))Lastlyforthesupportroutines,ouroptimizedexamplemadeuseofexcludewhichreturnsitssecondargumentlistafterremovinganyitemsonits rstargumentlist.It'seasytodonowthatwehavemember?:(defineexclude(lambda(itemslst)(iflst(if(member?(carlst)items)(excludeitems(cdrlst))(cons(carlst)(excludeitems(cdrlst))))())))Foranon-emptylist:ifthe rstelementistobeexcludedthenjustreturntheresultofcallingexcludeontherestofthelist.Ifitisnottobeexcluded,thenprependittotheresultofcallingexcludeontherestofthelist.Foranemptylisttheonlyresultcanbetheemptylist. 16.2.EXAMPLESOFAMBINACTION25716.2.2BarrelsofFunOurnextexampleisanotherlogicpuzzle,from[2].Itissomewhatdi erent,butrequiresmuchthesameapproach.BarrelsofFunAwinemerchanthassixbarrelsofwineandbeercontaining:30gallons32gallons36gallons38gallons40gallons62gallonsFivebarrelsare lledwithwineandonewithbeer.The rstcustomerpurchasestwobarrelsofwine.Thesecondcustomerpurchasestwiceasmuchwineasthe rstcustomer.Whichbarrelcontainsbeer?Here'sasolution:(definebarrels-of-fun(lambda()(let*((barrels(list303236384062))(beer(one-ofbarrels))(wine(exclude(listbeer)barrels))(barrel-1(one-ofwine))(barrel-2(one-of(exclude(listbarrel-1)wine)))(purchase(some-of(exclude(listbarrel-1barrel-2)wine))))(begin(require(eq?(*2(+barrel-1barrel-2))(sumpurchase)))beer))))Again,itismoreorlessjustastatementoftheproblem.Westarto bypickingthebeerbarrelatrandom.Thenwesaythatthewinebarrelsaretheremainingbarrels.Nextwerandomlypickthe rstbarrelofwineboughtfromthewinebarrels,andthesecondfromtheremainingwinebarrels.Wedon'tknowhowmanybarrelsthesecondcustomerbought,sowemerelyassignsome-oftheremainingbarrelstothatpurchase.Finallyinthebodyoftheletwerequirethatthesecondcustomerbuystwiceasmuchwineasthe rst,thenreturnthebeerbarrel(theansweris40bytheway).Wehaven'tseensome-ofbefore.Itisverysimilartoone-ofdescribedabove,andmakesdirectuseofamb.(definesome-of(lambda(lst)(begin 258CHAPTER16.CHRONOLOGICALBACKTRACKING(requirelst)(amb(list(carlst))(some-of(cdrlst))(cons(carlst)(some-of(cdrlst)))))))Itrequiresthelisttobenon-empty,thenchoosesbetweenjustthe rstelementofthelist(asalist),someoftherestofthelist,orthe rstelementprependedtosomeoftherestofthelist.Thiswilleventuallyproduceallnon-emptysubsetsofthelist.Theonlyotherfunctionwehaven'tseenbeforeissum.Itaddsallthevaluesinitsargumentlistandisquitetrivial:(definesum(lambda(lst)(iflst(+(carlst)(sum(cdrlst)))0)))Thesumofalististhecarofthelistplusthesumofthecdrofthelist.Thesumofanemptylistiszero.16.2.3PythagoreanTriplesAsanotherexampleofamb,considergeneratingso-calledpythagoreantriples,triplesofintegersx,yandzsuchthatx2+y2=z2.Thisshouldbeprettyeasy.>(definesquare>(lambda(x)>(*xx)))square>(definepythagorean-triples>(lambda()>(let((x(amb12345678))>(y(amb12345678))>(z(amb123456789101112)))>(begin>(require(eq?(+(squarex)>(squarey))>(squarez)))>'((x,x)(y,y)(z,z))))))pythagorean-triples>(pythagorean-triples)((x3)(y4)(z5))>?((x4)(y3)(z5))>? 16.2.EXAMPLESOFAMBINACTION259((x6)(y8)(z10))>?((x8)(y6)(z10))>?Error:nomoresolutionsAndsoitwas.Afterde ningsquare,wepicksomerangesofnumbersx,yandz,thenrequirethatthesumofthesquaresofxandyequalsthesquareofz.Althoughitissimpleandeasytounderstand,that'saterriblynaiiveimplementation.Wejustguessedtherange1...8forxandybasedona xedrange1...12forz.Plustheresultincludesduplicates:((x3)(y4)(z5))isthesameas((x4)(y3)(z5)).Plus,thenumberofresultsisconstrainedbythehighestvalueofz,altogethernotverysatisfactory.Withtheadditionofacouplemorefunctions,wecanremedyallofthesede ciencies.Firstly,here'safunctionintegers-betweenthatwillambivalentlyreturneverynumberbetweenitslowerboundanditsupperbound,inascendingorder:(defineintegers-between(lambda(lowerupper)(begin(require(<=lowerupper))(amblower(integers-between(+lower1)upper)))))Itbeginsbyrequiringthatitslowerboundislessthanorequaltoitsupperbound,thenambivalentlyreturns rstthelowerbound,thentheresultofcallingitselfwithitslowerboundincrementedbyone.Thinkingaboutit,ifweweretoremovetheboundscheckfromintegers-betweenitwouldcontinuetoproducenewintegers,oneatatime,ad-in nitum,andwithouttheboundscheckitwouldhavenoneedfortheupperboundargument.Thatrealisationgivesusoursecondfunction,integers-from:(defineintegers-from(lambda(x)(ambx(integers-from(+x1)))))Thisfunctionwilljustcarryonreturningoneintegerafteranotheraslongasitisbacktrackedto.Giventhesetwosimplefunctionswecanwriteamuchmoresatisfactoryversionofpythagorean-triples:(definepythagorean-triples(lambda()(let*((z(integers-from1))(x(integers-between1z))(y(integers-betweenxz)))(begin(require(eq?(+(squarex)(squarey))(squarez)))'((x,x)(y,y)(z,z)))))) 260CHAPTER16.CHRONOLOGICALBACKTRACKINGItuseslet*tomakethevalueofzavailabletothede nitionofx,andlikewisethevalueofxavailabletothede nitionofy,muchasintheliarspuzzleabove.Itletszequaleachofthepositiveintegersinturn,thenitletsxrangeoverthevalues1toz.Then,toavoidduplication,itonlyallowsytorangeoverthevaluesxtoz.Therestoftheimplementationisunchanged.It'salittleslow,butitwillcontinuetogenerateuniquepythagoreantriplesaslongasyoukeepaskingitformore:>(pythagorean-triples)((x3)(y4)(z5))>?((x6)(y8)(z10))>?((x5)(y12)(z13))>?((x9)(y12)(z15))>?((x8)(y15)(z17))>?((x12)(y16)(z20))>?((x7)(y24)(z25))>?...Towrapupthissection,althoughitshouldbeobvious,it'sprobablyworthpointingoutthatthereisapitfalltousingambtogeneratein nitesequenceslikethis.Thefunctionintegers-fromcanneverfail,sounlessitisthe rstcalltoambinyourprogram,anypreviouscallstoambwillnevergetbacktrackedto.Thisworksoutprettywellforpythagorean-triples:sinceweneedthecurrentvalueofztoconstrainthevaluesofx,thecalltointegers-fromhadtohappen rst,butevenifwehadn'tneededthevalueofz rst,wewouldstillhavetohavecalculatedit rst,otherwiseanypreviouscallstoambwouldnevergetachancetoyieldmorethantheir rstresult.Forexamplethefollowingjustwon'twork:...(let((x(integers-from1))(y(integers-from1))(z(integers-from1)))...Thelastcalltointegers-fromtoprovidethevalueofz,whenbacktrackedto(byhitting?"orbysomedownstreamcalltoamb),wouldjustkeeponproducingvalues,sothedeclarationsofxandywouldnevergetbacktrackedtoandneverproducealternativevalues.16.2.4ParsingNaturalLanguageOurlastexampleofambisalittledi erent.Itturnsoutthatambisextremelyusefulforparsing.Becauseambcanbacktrackandiscapableoftryingmanyalternativestrategies,itismuchmorepowerfulthan 16.2.EXAMPLESOFAMBINACTION261anysimplebottom-upparserliketheoneusedtoparsePSchemeitself.Infactitisquitecapableofparsingsomerestrictedsubsetsofnaturallanguage.Tounderstandwhatfollows,itisessentialtorealisethatevenset!,whenbacktrackedthrough,willhaveitse ectundone.Thisiswhatismeantbychronologicalbacktracking":chronologicalbacktrack-ingreallydoesrestorethestateofthemachinetoaprevioustime,asifnothingsincetheambbeingbacktrackedtoeverhappened.Ithinkthatisquiteamazing.Tostartthediscussiononparsing,considerthefollowingtwoEnglishsentences:Time ieslikeanarrow."Fruit ieslikeabannanna."Althoughsuper ciallyverysimilar,thetwosentenceshaveradicallydi erentstructuresandsemantics:Time,theinde nitecontinuedprogressofexistence"isnotedtoalways yforwardinthemannerofanarrow,wherasfruit iesofthegenusMelanogasterareknowntobequitepartialtobannanas.Thisdemonstratesquitevividlythatitisinfactimpossibletocorrectlyparsenaturallanguagewithoutinvolvingsemantics,andofcourseitisimpossibletoextractthesemanticswithoutparsing;achickenandeggproblemthatIhopetoshowambcanneatlycircumvent.Drawingonoldschoolgrammarlessons,Figure16.2showsareasonableparsetreeforthe rstsentence.Itconsistsofthenountime"andaverbphrase.Theverbphraseconsistsoftheverb ies"andaprepositionalphrase.Theprepositionalphraseconsistsoftheprepositionlike"andanounphrase.Thenounphraseconsistsofthedeterminantan"andthenounarrow".Figure16.2:OnepossibleparseofTime ieslikeanarrow"sentencenounverbphraseverbprepphraseprepnounphrasedetnountimeflieslikeanarrowSimilarily,Figure16.3onthenextpageshowsaparsetreeforthesecondsentence.Thistimethesentencebreaksdownintotheclassicnounphraseplusverbphrasestructure(asdidthe rst,butthenounphrasejustcontainedanoun).Thenounphrasecontainstheadjectivefruit"andthenoun ies".Theverbphrasecontainstheverblike"andanothernounphrase.Thissecondnounphraseconsistsofthedeterminanta"andthenounbannanna".Inordertoparsethesesentences,wecanstarto bycategorizingtheindividualwords: 262CHAPTER16.CHRONOLOGICALBACKTRACKINGFigure16.3:AparsetreeforFruit ieslikeabannanna"sentencenounphraseverbphraseadjnounverbnounphrasedetnounfruitflieslikeabannanna(defineverbs'(verbflieslike))(definenouns'(nountimefruitfliesbannannaarrow))(definedeterminants'(detaan))(defineadjectives'(adjtimefruit))(defineprepositions'(preplike))The rstsymboloneachlistidenti esthetypeoftherestofthewordsonthelist.Notethatanumberofthewordsoccuronmorethanoneofthelists:like"actsasaprepositioninthe rstsentence,whileitisaverbinthesecond.Similarily ies"istheverbinthe rstsentence,butanouninthesecond.Additionally,I'veaddedacoupleofcategorisationsthataren'tneededtoparsethosesentencescorrectly,butwouldnonethelessbepresentinasucientlygenerallexicon:fruit"iscertainlyanoun,andtime"isaperfectlyacceptableadjective(timetravel"forexample).Theseadditionalclassi cationsareexactlywhatcauseustodothatdoubletakewhenwe rstencounterthesetwosentences,andwillmaketheparsingmorerealistic.Nextwecreateaglobalvariable*unparsed*toholdthewordsremainingtobeparsed.thisisinitiallyde nedtobeempty:(define*unparsed*())Thenwede neatoplevelparseroutine:(defineparse(lambda(input)(begin(set!*unparsed*input)(let((sentence(parse-sentence)))(begin(require(not*unparsed*))sentence)))))parsestartsbysettingtheglobal*unparsed*toitsargument.Thenitcallsparse-sentence,collectingtheresult.Finallyitrequiresthatthereisnothingleftin*unparsed*andreturnstheresultofparse-sentence. 16.2.EXAMPLESOFAMBINACTION263Readerswhoappreciatethedangersofglobalstateandmutationmightbewonderingwhatonearthisgoingonhere.Afunctionthatacceptsanargumentthenjustassignsittoaglobalvariable?Worse,itthenproceedstomutatethatglobalastheparseproceeds?Surelythatistheantithesisofgoodprogramming?Thereisaverysoundreasonthatitisdonethisway,andthatistodemonstratewhatambiscapableof.Pleasebearwithme.parsewillbecalledlike(parse'(fruitflieslikeabannanna))andshouldreturnaparsetreewiththenodesofthetreelabelled,like:(sentence(noun-phrase(adjfruit)(nounflies))(verb-phrase(verblike)(noun-phrase(deta)(nounbannanna))))Wehaveseenthatparsecallsparse-sentence,andweshallseeshortlythatparse-sentencecallsouttootherparse-noun-phraseetc.routinestofutherbreakdownthesentence.Thevariousparse-*routinesallindirectlyconsumetokensfromtheglobal*unparsed*variable,buttheonlyfunctionthatdirectlyremovestokensfrom*unparsed*isthefunctionparse-word:(defineparse-word(lambda(words)(begin(require*unparsed*)(require(member?(car*unparsed*)(cdrwords)))(let((found-word(car*unparsed*)))(begin(set!*unparsed*(cdr*unparsed*))(list(carwords)found-word))))))Theargumentwordswillbeoneofthelistsofwordsde nedabove,wherethecaristhetypeofthewordsandthecdristheactualwordstoberecognized.Hencetheuseofcarandcdrtogettheappropriatecomponents.Soparse-wordiscalledlike(parse-wordnouns)andwillsucceedandreturnalistofatypeandawordifthe rstwordof*unparsed*isoneofitsargumentwords.Forexampleif*unparsed*is'(flieslikeanarrow)andwecall(parse-wordnouns)itshouldreturnthelist(nounflies)andasasidee ectset*unparsed*to'(likeanarrow).parse-wordrequiresthattherearetokenslefttoparse,thenrequiresthatthe rstwordof*unparsed*isamemberofitslistofcandidatewords.Ifsothenitremovesthe rstwordfrom*unparsed*andreturnsit,appendedtothecategoryofwordsthatmatched.Iftherearenowordslefttoparse,orifthenextwordin*unparsed*isnotoneoftheargumentwords,thenparse-wordfailsandcontrolbacktrackstothepreviousdecisionpointwherethenextalternativeistried.Itisimportanttorememberherethatthee ectofset!on*unparsed*canbeundonebythebacktrackingofamb.Backtoparse.parsecallsparse-sentence:(defineparse-sentence(lambda()(amb(list'sentence 264CHAPTER16.CHRONOLOGICALBACKTRACKING(parse-wordnouns)(parse-verb-phrase))(list'sentence(parse-noun-phrase)(parse-verb-phrase)))))parse-sentenceambivalentlychoosestoparseeitherthestructureofthe rstsentenceorthestructureofthesecond.Itprependstheresultwiththeappropriategrammaticallabeljustasparse-worddid.Sincethesecondpartofbothsentencesisthesame(averbphrase)wecouldequivalentlyhavesaid:(defineparse-sentence(lambda()(list'sentence(amb(parse-wordnouns)(parse-noun-phrase))(parse-verb-phrase))))Infactthissecondformulationislikelytobemoreecientsinceitdoesn'thavetobacktrackthroughparse-verb-phraseunneccessarily.Nextlet'slookatparse-verb-phrase.Ourtwoexampleverbphrasesaredi erent.The rstconsistsofaverbandaprepositionalphrase,thesecondconsistsofaverbandanounphrase.Wecancombinethetwo,eliminatingtheduplicationonverbsforaslightlymoreecientparse.Here'sparse-verb-phrase:(defineparse-verb-phrase(lambda()(list'verb-phrase(parse-wordverbs)(amb(parse-prep-phrase)(parse-noun-phrase)))))Goingbredth- rstfromparse-sentence,nextupisparse-noun-phrase:(defineparse-noun-phrase(lambda()(list'noun-phrase(amb(parse-wordadjectives)(parse-worddeterminants))(parse-wordnouns))))Wehavetwoexamplenounphrases:anadjectivefollowedbyanounandadeterminantfollowedbyanoun.Againwe'veremovedtheduplication,thistimeonthenoun.Lastly,wehavetoparseprepositionalphrases,ofwhichwehaveonlyoneexample:aprepositionfollowedbyanounphrase:(defineparse-prep-phrase(lambda()(list'prep-phrase(parse-wordprepositions)(parse-noun-phrase)))) 16.2.EXAMPLESOFAMBINACTION265Withthesede nitionsinplace,wecanattempttoparseourtwosentences(outputreformattedmanuallytoaidreadability):>(parse'(timeflieslikeanarrow))(sentence(nountime)(verb-phrase(verbflies)(prep-phrase(preplike)(noun-phrase(detan)(nounarrow)))))>?(sentence(noun-phrase(adjtime)(nounflies))(verb-phrase(verblike)(noun-phrase(detan)(nounarrow))))>?Error:nomoresolutionsand>(parse'(fruitflieslikeabannanna))(sentence(nounfruit)(verb-phrase(verbflies)(prep-phrase(preplike)(noun-phrase(deta)(nounbannanna)))))>?(sentence(noun-phrase(adjfruit)(nounflies))(verb-phrase(verblike)(noun-phrase(deta)(nounbannanna))))>?Error:nomoresolutionsSowhiletimedoesindeed ylikeanarrow,andfruit iesarefondofbannannas,othervalidparsesofthesentencesimplythatsomestrangecreaturescalledtime ies"areattractedtoarrows,andthatfruitdoes ymuchlikeabannannadoes.Whatmakesthisreallyexcitingisthatwearestillinbacktrackingmodewhentheparseiscomplete.Ifwedon'tlikeaparticularresultwecanrequestfurtherresultsbyhitting?"attheprompt,butthisoptionisalsoavailabletoclientcodeoftheparser:Subsequentdownstreamanalysisoftheresultmayrejectitonsemanticgrounds(fruitcan't y,nosuchthingastime ies"),andrequestanalternativeparse.Soyou'veseenwhatambcando.Therestofthischapterdiscussesitsimplementation. 266CHAPTER16.CHRONOLOGICALBACKTRACKING16.3ImplementingambThecoreideabehindambistouseanadditionalcontinuationtoletusdobacktracking.Insteadofjustpassingaroundonecontinuationthatspeci esthepointofreturnforthecalledfunction,wepassaroundtwocontinuations.The rstcontinuationisthesameasbefore,andthattakescareofnormalcontrol ow.Thesecondcontinuationisafailure"continuationofnoargumentsthatgetscalledbyambwhenitrunsoutofoptions,andbythereplwhenyoutypeina?".Thatfailurecontinuationresumesexecutionatthepreviousdecisionpoint.Here'sausefulanalogytohelpkeeptrackofwhatisgoingon.Ifyouconsidernormalcontrol ow,bothcallstomethodsandcalls(returns)tonormalcontinuationstobealwaysdownstream"towardsthesuccessfulproductionofaresult,thentheinvocationofthefailurecontinuationcausescontroltopassbackupstream"toapreviouspointinthecomputationandresumefromthere.TheinitialfailurecontinuationispassedtoRead()bytherepltoproducethe ocurrentproblem"errorandrestarttherepl(infactError()alreadyrestartsthereplforus).ThenwhenthereplinvokesEval()onanexpressionithasread,itpassesanalternative omoresolutions"failurecontinuationwhichagaincallsError().Theseinitialfailurecontinuationsareasfarupstream"asyoucangetbecausetheyexistbeforethecomputationisevenattempted.Nowifambisinvoked,itreplacesthecurrentfailurecontinuationwithanewonethat,ifcalled,willcauseambeithertopassitsnextvaluebackdownstream"again(tothesuccesscontinuation)or,iftherearenomorechoices,toretreatevenfurtherupstream"tothepreviousfailurecontinuation.That'sallwehavetoachievereally,therestofthischapterisjustthedetails.Therearehoweveranothercoupleofplaceswherethefailurecontinuationneedstobetreatedspecially.RemembermylittlerantaboutpurelyfunctionallanguagesatthestartofSection10.1onpage123?Wellinapurelyfunctionallanguagetherewouldbenosuchextraplaces,becauseitisonlysidee ectsthatneedtobeundoneasthefailurecontinuationbacktracksupstreamthroughthem.Bothdefineandset!needtoinstalltheirownfailurecontinuationsthatwillundowhateverchangetheymade,thencallthepreviousfailurecontinuationtocontinuebackupstream.Asyoumighthaveguessed,ambrequiresanotherrewriteofourinterpreter.Howeverthistimetherewriteis,onthewhole,apurelymechanicalone.Apartfromtheplacesmentionedabove,thefailurecontinuationisalwayssimplypassedthroughuntouched.Itisanextraargumenttoallthemethodsthattakeacontinuationargument,andthesuccesscontinuationsthemselvesnowalltakeanextrafailurecontinuationasargumenttoo,sincethefailurecontinuationmustnotbelosttrackof.16.3.1ChangestoContinuationsNoticethatwenowhavethreekindsofcontinuation:asuccesscontinuationfornormalcontrol ow,afailurecontinuationforbacktracking,andlet'snotforgetthecontinuationofnoargumentsreturnedtothetrampolinetoclearthestack.ItwasbecomingobviousthatifIjuststuckwithcontfgtocreateallcontinuations,Iwouldhavetostarttolitterthecodewithcommentstothee ectofthisisthesuccesscontinuation",thisisthefailurecontinuation"etc.ItmakesmuchmoresensetomaketheoriginalPScm::Continuationclassabstract,andtohaveconcretesubclassesforeachofthesetypes.Then,insteadofthegenericcontfgconstructtocreateacontinuation,wenowhavethreeseparateprototypedsubroutinestocreatecontinuationsoftheappropriatetype.Thecontfgconstructstillcreatesthe ormal"continuations,butnewconstructsfailfgandbouncefgcreatetheothertypesofcontinuation.Sowithoutfurtherado,here'sthenewabstractPScm::Continuationclass: 16.3.IMPLEMENTINGAMB267001packagePScm::Continuation;002003usestrict;004usewarnings;005usebaseqw(PScm::Expr);006007requireExporter;008009pushour@ISA,qw(Exporter);010011our@EXPORT=qw(bouncecontfail);012013subnewf014my($class,$cont)=@_;015blessfcont=>$contg,$class;016g017018subcont(&)f019my($cont)=@_;020returnPScm::Continuation::Cont->new($cont);021g022023subfail(&)f024my($fail)=@_;025returnPScm::Continuation::Fail->new($fail);026g027028subbounce(&)f029my($bounce)=@_;030returnPScm::Continuation::Bounce->new($bounce);031gThecontsubnowcreatesaPScm::Continuation::ContobjectinsteadofaPScm::Continuationobject.Thenewfailandbouncesubsarecompletelyanalogous.Additionally,insteadofthethreenewcontinuationclassessharingacommonCont()methodtoinvokethecontinuation,Cont()hasbeenmovedtothePScm::Continuation::Contclass,andthePScm::Continuation::FailclasshasaFail()method.ThesearebothalmostidenticaltotheearliergenericCont()method,butexpectthecorrectnumberofargumentsetc.TheoldBounce()method,whichinvokedthecontinuationdirectly,hasjustbeenmovedintothePScm::Continuation::Bounceclass.16.3.2MechanicalTransformationsoftheInterpreterIdon'twanttoshowyouthoseotherPScm::Continuationderivedclassesyet,becausethatwouldjumpthegunonthepassingofthenewfailurecontinuationaroundinApply()etc.Instead,nowweknowwhatfailfgandbouncefgdo,letstakealookatsomeexamplesofhowthismechanicalrewriteoftheinterpreterwillproceed. 268CHAPTER16.CHRONOLOGICALBACKTRACKINGThedefaultEval()methodinPScm::Exprdemonstratesthesimplestkindoftransformation.Thepreviousversionsimplycalleditscontinuationon$self,sobydefaultexpressionsevaluatetothemselves.Thenewambversiontakesanextra$failcontinuationasargument,andpassesitalongtotheoriginalcontinuationasanextraargument:016subEvalf017my($self,$env,$cont,$fail)=@_;018$cont->Cont($self,$fail);019gNextup,let'stakealookattransforminganexamplemethodthatcreatesanewcontinuation.ThePScm::SpecialForm::Let::Apply()methoddoesthat.Itextendsthecurrentenvironmentwiththenewbindingsfortheletexpression,passingacontinuationthatwillevaluatethebodyoftheletinthatnewenvironment.Thenewversionforambisnotthatdi erent.Asyoucanseeallthemethodcallsthatusedtotakeasinglecontinuationasargumentnowtakeanextra$failcontinuation,andtheoriginalcontinuationsthemselvesnowtakeanextra$failcontinuation,passingittoanymethodthatnowexpectsit.Otherwise,it'sunchanged:013subApplyf014my($self,$form,$env,$cont,$fail)=@_;015016my($symbols,$values,$body)=$self->UnPack($form);017018$env->Extend(019$symbols,$values,020contf021my($newenv,$fail)=@_;022$body->Eval($newenv,$cont,$fail);023g,024$fail025);026gPleasenotehoweverthattherearetwo$failvariableshere.The rstoneispassedtoApply()asargumentonLine14andgetspassedonasanadditionalargumenttoExtend()onLine24.Thesecond$failisargumenttothenewcontinuationonLine21andispassedonasanadditionalargumenttoEval()onLine22.Itisveryimportantthatthesetwo$failvariablesarekeptdistinct.Beforewe nallygetaroundtosomecodethatactuallydoesmorethanjustpassthefailurecon-tinuationaround,let'stakealookatafairlyinvolveduseofcontinuations,andthe(stillmechanical)transformationthatambrequiresofit.InSection8.5.1onpage92weintroducedPScm::Expr::List::Pair::mapeval(),whichevaluateseachcomponentofitslistandreturnsanarrayrefofthoseevaluatedcomponents.ThatmethodwasintroducedevenearlierinourcpsrewriteinSection13.6.2onpage188andwas nallyreunitedwithitsoriginallistimplementationinSection13.6.5onpage206whereitdealswithbothcontinuationsandtruePSchemelists.Here'sPScm::Expr::List::Pair::mapeval()sofar:157submap_evalf158my($self,$env,$cont)=@_; 16.3.IMPLEMENTINGAMB269159160$self->[FIRST]->Eval(161$env,162contf163my($evaluated_first)=@_;164$self->[REST]->map_eval(165$env,166contf167my($evaluated_rest)=@_;168$cont->Cont($self->Cons($evaluated_first,169$evaluated_rest));170g171);172g173);174gAndhereitisaftertheambchanges:171submap_evalf172my($self,$env,$cont,$fail)=@_;173174$self->[FIRST]->Eval(175$env,176contf177my($evaluated_first,$fail)=@_;178$self->[REST]->map_eval(179$env,180contf181my($evaluated_rest,$fail)=@_;182$cont->Cont(183$self->Cons($evaluated_first,184$evaluated_rest),185$fail186);187g,188$fail189);190g,191$fail192);193gIt'sabitlonger,butIhopeyoucanseethattheonlychangeisthatextra$failargumentalongsideeachpassedcontinuation,andasanextraargumenttoanycontinuationwhichisactuallycalled.Noteagainthatit'sveryimportantthateachcontinuationactuallydeclaresitsextraargument.Althoughthesame$failvariablenameisusedthroughout,theactualscopeofeachvariableisdi erent,andcouldeasilyhaveadi erentvalue.Havingsaidthat,thisisthemainreasonthatthisrewriteissomechanical. 270CHAPTER16.CHRONOLOGICALBACKTRACKING16.3.3RemainingContinuationChangesNowthatwe'veseenexamplesofhowthefailurecontinuationgetspassedaround,it'ssafetoreturntoourPScm::Continuationclassesandshowthedetailsofthevariousmethodstherein.Firstly,here'sthePScm::Continuation::Contclass.034packagePScm::Continuation::Cont;035036usestrict;037usewarnings;038usebaseqw(PScm::Continuation);039040BEGINf041*cont=&PScm::Continuation::cont;042*bounce=&PScm::Continuation::bounce;043g044045subApplyf046my($self,$form,$env,$cont,$fail)=@_;047$form->map_eval(048$env,049contf050my($evaluated_args,$fail)=@_;051$self->Cont($evaluated_args->first,$fail);052g,053$fail054);055g056057subContf058my($self,$arg,$fail)=@_;059060bouncef$self->fcontg->($arg,$fail)g061gWehavetomanuallyimportthecontandbouncesubroutinesfromPScm::Continuationbecausethey'reinthesame le(afailureofusebase.)ThenonLines45-55weseetheApply()methodforcontinuations(remembercall/ccpresentscontinuationsasfunctionssotheyneedanApply()method.)Apply()isunchangedexceptforthepassingoftheextra$failcontinuation.Thismeansthatthefailurecontinuationiskepttrackofeventhroughtheuseofcall/cc.LastlytheCont()methodissimilarilyunchangedexceptthatitusesbouncefginsteadofcontfgtocreateaPScm::Continuation::Bounceforthetrampoline,andofcourseithastheextra$failcontinuationtopasson.PScm::Continuation::Failissomewhatshorter:064packagePScm::Continuation::Fail;065 16.3.IMPLEMENTINGAMB271066usestrict;067usewarnings;068usebaseqw(PScm::Continuation);069070BEGINf*bounce=&PScm::Continuation::bounce;g071072subFailf073my($self)=@_;074bouncef$self->fcontg->()g075gAgainwemustmanuallyimportthebouncefgconstructthatweneed,butthentheFail(),method,whichtakesnoarguments,merelyreturnsabouncefgcontinuationtothetrampolinethatwillinvokethefailurecontinuationwithnoarguments.PScm::Continuation::Bounceisevenshorter.It'ssingleBounce()method,againwithnoargu-ments,directlyinvokesitsstoredcontinuationasitalwaysdid.078packagePScm::Continuation::Bounce;079080usestrict;081usewarnings;082usebaseqw(PScm::Continuation);083084subBouncef085my($self)=@_;086$self->fcontg->();087g0880891;Sobacktotherewrite.Howaboutactuallydoingsomethingwiththefailurecontinuation?AsI'vesaid,thereareonlyafewplacesintheinterpreterwhereanewfailurecontinuationisconstructed,namelyintheApply()methodforambiteslf;intheApply()methodfordefine;intheApply()methodforset!;intherepl.Thesearetheonlyplacesintheinterpreterwheretheambrewriteisnotpurelymechanical.We'llgothroughthesecasesinthesameorder,startingwithPScm::SpecialForm::Amb::Apply(). 272CHAPTER16.CHRONOLOGICALBACKTRACKING16.3.4ambItselfambisthewholepointofthischapter,andsodeservessomeattention.Thisspecialformmustevaluateandreturnits rstargumentdownstream"whenitiscalled,butifcontrolbacktrackstoitthenitmustreturnitsnextargument,andsoon,untiltheargumentlistisexhaustedatwhichpointitshouldinvokethefailurecontinuationthatitwasoriginallycalledwithandbacktrackfurtherupstream:477packagePScm::SpecialForm::Amb;478479usebaseqw(PScm::SpecialForm);480481usePScm::Continuation;482483subApplyf484my($self,$choices,$env,$cont,$fail)=@_;485if($choices->is_pair)f486$choices->first->Eval(487$env,488$cont,489failf490$self->Apply(491$choices->rest,492$env,493$cont,494$fail495)496g497)498gelsef499$fail->Fail();500g501g5025031;It'sreallynotthatbad.IttakesthesameargumentsasanynormalApply()method,includingtheextrafailurecontinuation.OnLine485itteststoseeiftheargument$choices(theactualargumentstotheambfunction)istheemptylist.If$choicesisnotempty,thenonLine486itevaluatesthe rstchoice,passingtheoriginalsuccesscontinuation$contwhichwillreturntheresultdownstreamtothecaller.Butinsteadofjustpassinginitsargument$failcontinuation,onLines489-496itpassesanewfailfgcontinuationthatwill,ifbacktrackedto,callAmb::Apply()againontherestofthearguments.NotethatonLine494thenewfailurecontinuationpassesamb'soriginalfailurecontinuationtoAmb::Apply().Soifambitselfdecidestobacktrackbycallingthat,controlwillpassimmediatelybacktowhateverfailurecontinuationwasinplacebeforeambinstalledthisnewone.Ifontheotherhandthenewfailurecontinuationiseverinvokeddownstreamofthis,itwillcausecontroltoproceedbackupstreamtothisoccurenceofambwhichthenreturnsitsnextvaluebackdownstreamviaApply()tothecurrentsuccesscontinuation. 16.3.IMPLEMENTINGAMB273Ifthelistofargumentsisempty,thenonLine499Apply()invokesitsoriginalargument$failcontinuationcausingexecutiontoimmediatelybacktrackfurtherupstream.16.3.5ChangestodefineNext,let'stakealookatdefine.defineinstallsitssymbol/valuepairinthecurrentenvironmentframe,reguardlessofthepresenceorabsenceofanypreviousbinding.Theambversionofde nemustundowhateveritdidifitisbacktrackedthrough,soitneedstorememberthepreviousvalue,ifany.Here'stheambversionofPScm::SpecialForm::Define::Apply():331subApplyf332my($self,$form,$env,$cont,$fail)=@_;333my($symbol,$expr)=$form->value;334my$old_value=$env->LookUpHere($symbol);335336$expr->Eval(337$env,338contf339my($value,$fail)=@_;340$cont->Cont(341$env->Define($symbol,$value),342failf343if(defined$old_value)f344$env->Assign($symbol,$old_value);345gelsef346$env->UnSet($symbol);347g348$fail->Fail();349g350);351g,352$fail353);354gdefineinthepreviousversionevaluateditsvaluepartinthecurrentenvironment,passingacontinuationthatwouldcallthetopenvironmentframe'sDefine()methodonthesymbolandtheresult.Thisnewversionmustadditionallykeeptrackofthepreviousvalueofthesymbol,ifany,andarrangethatitsfailurecontinuationrestoresthatvaluebeforebacktrackingfurther.Apartfromtheextra$failargument,the rstthingthatisnewisthatonLine334itcallsanewmethodPScm::Env::LookUpHere()whichonlylooksinthetopframeandreturnseitherthevalueoftheargumentsymbolorundef.Thenthingsproceedasnormalapartfromtheextra$failcontinuationuntilLines342-349whereareplacementfailfgcontinuationispassedtodefine'soriginalsuccesscontinuation.Thatnewfailfgcontinuationcheckstoseeifthe$oldvalueisde ned.Ifitis,thenitcallsAssign()ontheenvironmenttorestoretheoldvalue.Ifitisnotde ned(therewasnopreviousvalue)thenitmustcallanewmethodofPScm::Env,UnSet(),toremovethebindingfromthetopframe.Ineithercase,it nallyreturnsthroughtheoriginal$failcontinuationtobacktrackfurtherupstream. 274CHAPTER16.CHRONOLOGICALBACKTRACKINGThelocationofthefailfgisquitesubtle,infactanearlierversionofthiscodehadabugthatwentunnoticedforaconsiderabletime.ConsiderthefollowingPSchemefragment(assumexisalreadyde ned):(definex(cons(amb12)x))Obviously,whenbacktracking,wewantthepreviousvalueofxtoberestoredbeforeweconsthenextvaluefromambontoit,otherwisewewouldbebreakingthesemanticsofchronologicalbacktracking.i.e.ifxstartsoutas(5),thenafterthe rsttimethroughtitwillobviouslybe(15),andthesecondtimethroughitshouldbe(25).Now,referringtoFigure16.4,thinkabouttheorderthatthingshappenhere.Passingcontinuationsismuchliketearingafunctionintotwoormorepieces:the rstpieceisthehead"ofthefunction,beforeitmakesanycallsofitsown.Theremainingpiecesarethecontinuationsthatitpassestothefunctionsthatitcalls.This gureomitsmanydetails,butyoucanseethatdefinecallsconswhichcallsamb,thenambcallsthecontinuationofconswhichinturncallsthewaitingcontinuationofdefine.Bythewaythisisanotherexampleofcpsbeingasimpli cationinthatitlinearizescontrol ow.Figure16.4:defineinstallsafailurecontinuationlast012defineconsambconsdefineInthis gure,downstream"islefttorightandupstream"isrighttoleft.Additionallythecirclesrepresentnewfailurecontinuationsbeingcreatedandpasseddownstream.Ifcontrolbacktracksupstreamintothispieceofcode,itwill rstencounterthemostrecentlyinstalled,e.g.therightmostfailurecontinuation.Youcanseethatambinstallsanewfailurecontinuationat1,andthatinorderfordefinetohaveitsfailurecontinuationsupplanttheonesetupbyambitmustbecreateddownstreamofamb's.Thereforeitisdefine'ssuccesscontinuationthatmustinstallthefailurecontinuation,at2inthe gure.Ifinsteadtheinitialcodeonentrytodefinehadinstalledthefailurecontinuationat0(bypassingitasthelastargumenttotheoutermostEval),thenbacktrackingwould ndamb'sfailurecontinuation rst,anddefinewouldnotgetachancetoundoitse ectbeforeambsentitsnextvaluedownstreamagain.Thatwasthebugofcourse,settingupthefailurecontinuationat0insteadof2|itworksalmostallofthetime,unlessevaluationofthesecondargumenttodefineorset!resultsinacalltoamb.16.3.6Changestoset!Nextwe'regoingtolookatset!.set!searchestheenvironmentforabindingandreplacesit,throwinganerrorifnobindingcanbefound.Firstofall,here'sthenewde nitionforPScm::SpecialForm::Set::Apply():208subApplyf209my($self,$form,$env,$cont,$fail)=@_;210my($symbol,$expr)=$form->value; 16.3.IMPLEMENTINGAMB275211my$old_value=$env->LookUpNoError($symbol);212$expr->Eval(213$env,214contf215my($val,$fail)=@_;216my$result=evalf$env->Assign($symbol,$val)g;217if($@)f218$self->Error($@,$env);219gelsef220$cont->Cont(221$result,222failf223$env->Assign($symbol,$old_value);224$fail->Fail();225g226);227g228g,229$fail230);231gThisissimilartoPScm::SpecialForm::Define::Apply()above,butitusesanothernewmethodofPScm::Env,LookUpNoError()toseeifthevariablebeingsethadapreviousvalueinanyframe.Thenitproceedsasitdid,PassingacontinuationtotheEval()oftheexpressionthatwillcallAssign()ontheenvironment,trappinganyerror.Butthenitinstallsanewfailurecontinuationthatwill,ifinvoked,restorethepreviousvalueandbacktrackfurther.Thisnewfailurecontinuationjustassignsthepreviousvalueandbacktracks.Itneednotworrythattherewasnopreviousvalue,sinceinthatcasethecodeinthesuccesscontinuationwouldhaveinvokedError(),thusrestartingtherepl,andthefailurecontinuationwouldneverbebacktrackedthrough.Thesamesubtletiesdescribedindefineapplyhere:set!mustpassitsnewfailurecontinuationtotheoriginalsuccesscontinuationpassedtoset!,ratherthantotheevalofthevaluetobeassigned,incasethatevaluationcontainsanamb.Theset!failurecontinuationthatundoesthemutationmustbebacktrackedthroughbeforeanyambfailurecontinuation.16.3.7Changestorepl()ItisnotwithoutreasonthatI'vekeptthechangestotherepluntillast.Thisisthemostcomplexpartoftheambrewrite.IfyourememberfromSection13.6.2onpage188ReadEvalPrint()nowcallsahelperroutinerepl()todotheheavylifting,anditisrepl()thatweseehereundergoingsigni cantchange.Howeverthereisnothingherethatisreallynew,nowthatyou'veseenthemechanicsoftherestoftherewrite.Itismostlycomplicatedbecausetheoriginalrepl()methodwasalreadycomplicated.Here'srepl()fromthepreviousversionoftheinterpreter:079subreplf080my($env,$reader,$outfh)=@_;081$reader->Read( 276CHAPTER16.CHRONOLOGICALBACKTRACKING082contf083my($expr)=@_;084$expr->Eval(085$env,086contf087my($result)=@_;088$result->Print(089$outfh,090contf091repl($env,$reader,$outfh);092g093)094g095)096g097)098gAsI'vesaidbefore,it'sreallyjustRead()calledwithacontinuationthatcallsEval()withacontinuationthatcallsPrint()withacontinuationthatcallsrepl()again.Asbeforetherearegoingtobeextrafailurecontinuationspassedaround,butthatpartoftherewriteispurelymechanical.Theadditionalcomplicationsarebecauserepl()mustadditionallyinstallthe nalupstreamfailurecontinuations,andadditionallymustcheckiftheexpressionjustreadisa?"requesttobacktrack.Bearingallthatinmindit'sreallynottoobad:087subreplf088my($env,$reader,$outfh,$fail1)=@_;089$fail1||=failf__PACKAGE__->Error("nocurrentproblem",$env)g;090$reader->Read(091contf092my($expr,$fail2)=@_;093$expr->Eval(094$env,095contf096my($result,$fail3)=@_;097$result->Print(098$outfh,099contf100my($dummy,$fail4)=@_;101repl($env,$reader,$outfh,$fail4);102g,103$fail3104)105g,106failf107__PACKAGE__->Error("nomoresolutions",$env);108g 16.3.IMPLEMENTINGAMB277109)110g,111$fail1112)113gToaidreadabilitysomewhat,I'venamedthevariousoccurencesofthefailureconinuationseparately:$fail1,$fail2etc.Theycouldalljustbecalled$failwithoutbreakinganything,butitwouldbemoreconfusing.The$fail1argumenttorepl()isoptional.NeitherError()northenewthread()callthatinitiallyinstallsthereplonthetrampolinebothertopassone.Ifnofailurecontinuationispassed,thenonLine89repl()defaultsittoacalltoError()witha ocurrentproblem"message.Then,asbeforerepl()callsRead()withacontinuationthatwillcallEval()etc.Read()itselfchangesslightlyhowever:ifitreadsa?"itwillinvokethecurrentfailurecontinuation.Iftheexpressionreadisnotaretryrequest,theneverythingproceedsasnormal,bartheextrafailurecontinuations:Eval()iscalledwithacontinuationthatcallsPrint()withacontinuationthatcallsrepl()again.NotethatonLine101thecontinuationpassedtoPrint()callsrepl()withitsargumentfailurecontinuation$fail4,whichishowbacktrackingworkswhena?"isreadsubsequently.Onelastthingtonotice.OnLines106-108ThefailurecontinuationpassedtoEval()producesthe omoresolutions"error,whichwillbeprintedifrequiredbeforerepl()reinstatesthedefault ocurrentproblem"failure.Asmentionedabove,Read()haschangedalittle.Here'sthenewde nition:094subReadf095my($self,$cont,$fail)=@_;096my$res=$self->_read();097returnundefunlessdefined$res;098if($res->is_retry)f099$fail->Fail();100gelsef101$cont->Cont($res,$fail);102g103gBeforereturningitsvalue,Read()must rstcheckthattheexpressionitisabouttopasstoitssuccesscontinuationisnot?".OnLine98Read()checkstoseeiftheexpressionreturnedbyread()isaretryrequest.isretry()isde nedtoreturnfalseinPScm::Expr,butPScm::Expr::Symbolrede nesthistoreturntrueifthesymbol'svalue()is?".Ifitisaretryrequest,Read()invokesitsargumentfailurecontinuation.Attheverystartthiswillbethe"nocurrentproblem"error,sotyping?"atafreshPSchemepromptwillproducethiserror.That'sreallyallthereistoamb.TherestofthissectionjoinsthedotsbyshowingthesupportroutinesthatI'veglossedover. 278CHAPTER16.CHRONOLOGICALBACKTRACKING16.3.8AdditionalChangesThere'snotreallymanyofthosetodescribe.IfyouremembertherewereacoupleofextramethodsaddedtoPScm::Envtoaiddefineandset!inundoingtheirchanges.The rstofthesewasLookUpHere():159subLookUpHeref160my($self,$symbol)=@_;161if(exists($self->fbindingsgf$symbol->valueg))f162return$self->fbindingsgf$symbol->valueg;163gelsef164returnundef;165g166gLookUpHere()justchecksthecurrentframetoseeifthebindingexists.Itiscalledbyournewdefinetosaveanypreviousvaluebeforedefinereplacesit.NextisLookUpNoError():149subLookUpNoErrorf150my($self,$symbol)=@_;151152if(defined(my$ref=$self->_lookup_ref($symbol)))f153return$$ref;154gelsef155returnundef;156g157gItusestheexistinglookupref()methodtolocatethesymbol,eitherdereferencingandreturningthevalueifitwasfound,orreturningundef.LookUpNoError()iscalledbyset!beforeassigninganewvaluetothefoundvariable.TheotheradditiontoPScm::EnvwasanUnSet()methodwhichwouldremoveabindingfromtheenvironment.191subUnSetf192my($self,$symbol)=@_;193delete$self->fbindingsgf$symbol->valueg;194gThismethodjustdeletesabindingfromthecurrentframe.Itisonlycalledbydefinewhenbacktrackingtoremovethesettingthatdefineaddedtothecurrentenvironmentframe,soitneednot,andshouldnotrecurse.Finally,andmosttrivially,thereisanisretry()methodofPScm::Expr,sothatthecontinuationpassedtoRead()canaskpolitelyiftheexpressionjustreadisarequesttobacktrack(?").ThebasePScm::Exprclassde nesthistobefalseasadefault:014subis_retryf0gButPScm::Expr::Symbolrede nesthistoreturntrueifthesymbol'svalue()is"?". 16.4.SUPPORTFORTESTINGAMB279281subis_retryf282my($self)=@_;283return$self->valueeq"?";284g16.4SupportforTestingambTheexamplesatthestartofthischapterinSection16.1onpage249madeuseofquiteafewsupportfunctionsofvarioussorts.Mostofthosefunctionscouldbede neddirectlyinthePSchemelanguage,butafewremainingfunctionswerelefttobeimplementedintheinterpreteritself.Speci cally,thosefunctionswere:andandor.Botharespecialformssothatargumentsdonotgetevaluatedunnecessarilyandshort-circuitevaluationispossible.Numericinequalitytests>,<,>=and<=.Ageneralequalitytesteq?.Thisfunctionwillworkfornumbers,symbols,stringsandlists(twolistsareconsideredeq?iftheircarsareeq?andtheircdrsareeq?)7.16.4.1andandorItisbesttomakethesebothspecialforms,sothattheydonotevaluatetheirargumentsunnecessarily.Infacttheysharequiteabitincommonwiththeexistingbeginspecialform.beginevaluatesallitsargumentsinturn,wherasandandorevaluateeachoftheirargumentsuntilsomeconditionismet.IfyourememberfromSection13.6.7onpage211,PScm::SpecialForm::Begin::Apply()calledouttoahelperfunctionapplynext()ifitsargumentlistwasnon-empty.WhatI'vedoneistocreateacommonabstractbaseclassPScm::SpecialForm::Sequenceforallofbegin,andandor,becausetheycanallshareacommonApply()method:234packagePScm::SpecialForm::Sequence;235236usebaseqw(PScm::SpecialForm);237238subApplyf239my($self,$form,$env,$cont,$fail)=@_;240if($form->is_pair)f241$self->apply_next($form,$env,$cont,$fail);242gelsef243$cont->Cont($form,$fail);244g245gPScm::SpecialForm::BegininheritsfromthattogetitsApply()method,itsapplynext()isun-changedotherthanhavingtheextrafailurecontinuation:7Thisdi ersfromschemewhichhasseparateequalitytestsforsymbolsandlists,foreciencyreasons. 280CHAPTER16.CHRONOLOGICALBACKTRACKING249packagePScm::SpecialForm::Begin;250251usebaseqw(PScm::SpecialForm::Sequence);252usePScm::Continuation;253254subapply_nextf255my($self,$form,$env,$cont,$fail)=@_;256257$form->first->Eval(258$env,259contf260my($val,$fail)=@_;261if($form->rest->is_pair)f262$self->apply_next($form->rest,$env,$cont,$fail);263gelsef264$cont->Cont($val,$fail);265g266g,267$fail268);269gPScm::SpecialForm::Andreimplementsapplynext()toreturnfalseassoonasanevaluatedvalueisfalse.Ifalloftheargumentstoandaretrue,andreturnsthevalueofthelastargument.272packagePScm::SpecialForm::And;273274usebaseqw(PScm::SpecialForm::Sequence);275usePScm::Continuation;276277subapply_nextf278my($self,$form,$env,$cont,$fail)=@_;279280$form->first->Eval(281$env,282contf283my($val,$fail)=@_;284if($form->rest->is_pair)f285if($val->isTrue)f286$self->apply_next($form->rest,$env,$cont,$fail);287gelsef288$cont->Cont($val,$fail);289g290gelsef291$cont->Cont($val,$fail);292g293g, 16.4.SUPPORTFORTESTINGAMB281294$fail295);296gPScm::SpecialForm::Orbehavessimilarily.itevaluateseachofitsargumentsuntiloneofthemistrue,inwhichcaseitreturnsthatresult.Ifallofitsargumentsarefalse,itreturnsfalse.299packagePScm::SpecialForm::Or;300301usebaseqw(PScm::SpecialForm::Sequence);302usePScm::Continuation;303304subapply_nextf305my($self,$form,$env,$cont,$fail)=@_;306307$form->first->Eval(308$env,309contf310my($val,$fail)=@_;311if($form->rest->is_pair)f312if($val->isTrue)f313$cont->Cont($val,$fail);314gelsef315$self->apply_next($form->rest,$env,$cont,$fail);316g317gelsef318$cont->Cont($val,$fail);319g320g,321$fail322);323g16.4.2NumericInequalityTestsThenextthingwe'llneedisanumericinequalitytest>".Thefullstandardsetofnumericinequalitytests<",>",<=",and>="nowexistasprimitivesintheinterpreter.TheyareallunderPScm::Primitive,infacttheyalldescendfromasubclassofthatcalledPScm::Primitive::Comparewhichprovidesacommonapply()method:159packagePScm::Primitive::Compare;160161usebaseqw(PScm::Primitive);162163sub_applyf164my($self,@numbers)=@_;165$self->_check_type($numbers[0],'PScm::Expr::Number')if@numbers; 282CHAPTER16.CHRONOLOGICALBACKTRACKING166while(@numbers>1)f167my$number=shift@numbers;168$self->_check_type($numbers[0],'PScm::Expr::Number');169returnPScm::Expr::Number->new(0)170unless$self->_compare($number,$numbers[0]);171g172returnPScm::Expr::Number->new(1);173gSotheyalltakeanarbitrarynumberofargumentslikethearithmeticprimitives.Forexample(<=2334)istruebecauseeachargumentis<="thenextargument.apply()iteratesoveritsargu-ments,checkingeachoneisanumberandcallingaseparatecompare()methodoneachpair.Thecompare()methodcalledonLine170isimplementedseparatelybyeachofPScm::Primitive::Lt,PScm::Primitive::Gt,PScm::Primitive::LeandPScm::Primitive::Ge.Theyallgoexactlythesameway,soforexamplehere'sPScm::Primitive::Lt:176packagePScm::Primitive::Lt;177178usebaseqw(PScm::Primitive::Compare);179180sub_comparef181my($self,$first,$second)=@_;182return$first->value<$second->value;183gOnlytheactualcomparisonoperatordi ersbetweentheimplementations.16.4.3eq?Finallyeq?.Theeq?implementationisabitmoreinteresting.ItcanbeusedtocompareanyPSchemedatatypesthatinheritfromPScm::Expr.Equalityisarelativetermhowever.Forinstance,unlikePerl,astringandanumberwillneverbeconsideredequal,howevertwolistswiththesamecontentareconsideredequal.Here'sthenewPScm::Primitive::Eqclass:145packagePScm::Primitive::Eq;146147usebaseqw(PScm::Primitive);148149sub_applyf150my($self,@things)=@_;151while(@things>1)f152my$thing=shift@things;153returnPScm::Expr::Number->new(0)unless$thing->Eq($things[0]);154g155returnPScm::Expr::Number->new(1);156g 16.4.SUPPORTFORTESTINGAMB283Asyoucansee,liketheinequalitytestsabove,itwilltakeanarbitrarynumberofarguments.Apply()keepscomparingadjacentargumentsbycallingtheirEq()methoduntilatestfails,oralltestspass.TheEq()methodisde neddi erentlyforvarioustypesofPScm::Expr.AdefaultmethodinthebasePScm::Exprjustcomparesobjectidentity:040subEqf041my($self,$other)=@_;042return$self==$other;043gThismeansthat,forexample,twofunctionswiththesamearguments,envandbodywouldstillnotbeconsideredequal.Thiscouldbe xed,butI'mnotsureit'sworthit.AnywayPScm::Expr::AtomoverridesthisEq()methodtodoastringcomparisononthe(scalar)valuesofthetwoobjects, rstcheckingthatthetwoobjectsareofthesametype.Thisisgoodenoughforstrings,numbersandsymbols:092subEqf093my($self,$other)=@_;094return0unless$other->isa(ref($self));095return$self->valueeq$other->value;096gPScm::Expr::List::Pair::Eq()ismoreinteresting.Firstlyitdoesaquickcheckforobjectidentity,thatwillsaveunnecessaryrecursionifthetwoobjectsareactuallythesameobject.Thenitchecksthattheobjectisalist,and nallyitrecursivelycallsitselfonbothfirst()andrest()tocompletethetest:228subEqf229my($self,$other)=@_;230return1if$self==$other;231return0unless$other->is_pair;232return$self->[FIRST]->Eq($other->[FIRST])&&233$self->[REST]->Eq($other->[REST]);234gLastoftheEq()methodsisinPScm::Expr::List::Null.ThismethodreturnstrueonlyiftheotherobjectisalsoaPScm::Expr::List::Null,sincenullisonlyequaltonull:255subEqf256my($self,$other)=@_;257return$other->is_null;258g16.4.4WiringitupFinally,here'stheadditionalmethodswiredintoReadEvalPrint().YoucanalsoseethatonLine67thenewthread()routineinstallsabouncefgcontinuationthatstartstherepl.Thatcontinuationdoesn'tpassafailurecontinuationtorepl(),sorepl()willdefaultthattotheError:nocurrentproblemerror. 284CHAPTER16.CHRONOLOGICALBACKTRACKING035subReadEvalPrintf036my($infh,$outfh)=@_;037038$outfh||=newFileHandle(">-");039my$reader=newPScm::Read($infh);040my$initial_env;041$initial_env=newPScm::Env(042let=>newPScm::SpecialForm::Let(),043'*'=>newPScm::Primitive::Multiply(),044'-'=>newPScm::Primitive::Subtract(),045'+'=>newPScm::Primitive::Add(),046if=>newPScm::SpecialForm::If(),047lambda=>newPScm::SpecialForm::Lambda(),048list=>newPScm::Primitive::List(),049car=>newPScm::Primitive::Car(),050cdr=>newPScm::Primitive::Cdr(),051cons=>newPScm::Primitive::Cons(),052letrec=>newPScm::SpecialForm::LetRec(),053'let*'=>newPScm::SpecialForm::LetStar(),054eval=>newPScm::SpecialForm::Eval(),055macro=>newPScm::SpecialForm::Macro(),056quote=>newPScm::SpecialForm::Quote(),057'set!'=>newPScm::SpecialForm::Set(),058begin=>newPScm::SpecialForm::Begin(),059define=>newPScm::SpecialForm::Define(),060'make-class'=>newPScm::SpecialForm::MakeClass(),061'call/cc'=>newPScm::SpecialForm::CallCC(),062print=>newPScm::SpecialForm::Print($outfh),063spawn=>newPScm::SpecialForm::Spawn(),064exit=>newPScm::SpecialForm::Exit(),065error=>newPScm::SpecialForm::Error(066$outfh,067bouncefrepl($initial_env,$reader,$outfh)g068),069amb=>newPScm::SpecialForm::Amb(),070'eq?'=>newPScm::Primitive::Eq(),071'>'=>newPScm::Primitive::Gt(),072'<'=>newPScm::Primitive::Lt(),073'>='=>newPScm::Primitive::Ge(),074'<='=>newPScm::Primitive::Le(),075and=>newPScm::SpecialForm::And(),076or=>newPScm::SpecialForm::Or(),077);078079$initial_env->Define(080PScm::Expr::Symbol->new("root"), 16.5.SUMMARYANDDIRECTIONS285081PScm::Class::Root->new($initial_env)082);083__PACKAGE__->new_thread(bouncefrepl($initial_env,$reader,$outfh)g);084trampoline();085g16.5SummaryandDirectionsambdemonstratesaverysimplebutunfortunatelyinecientmechanismforembeddingso-called on-deterministic"programmingintoanotherwiseprocedurallanguage.Theterm on-deterministic"meansthatthecontrol owthroughtheinterpreterisnot,onthesurface,determinedsolelybyasinglesetofconditionsataparticularpoint:therearechoicesavailable.Themainreasonthatambissoinecientisthatituseswhatiscalledchronologicalbacktracking".Itreallyisasthoughtheinterpreterwindsbacktheclock"whenaconditionfails,goingbacktoapriormomentintimeandretryingadi erentchoiceatthatpoint.Ofcoursethisisanillusion,butausefulandsimpleanalogytouse.Howeverchronologicalbacktrackingisabrute-forceapproachtosearch,sinceallpossiblesolutionsareattemptedandinatypicalsearchthevastmajorityofthesepossiblesolutionsarediscarded(rememberthedistinctfunctioninthe rstsolutiontotheLiars"puzzle.)Therearealternativestochronologicalbacktracking.Theyarebeyondthescopeofthischapter,buttogiveyousomeidea,themostpopularandsuccessfulofthesealternativesisknownasdependancy-directedbacktracking.Thistechniquegainsmorecontroloverthenextchoicemadebynotingthereasonforthepreviousfailureandavoidingchoicesthatwouldagainproducethesamefailure.Forexample,intheLiars"puzzlefromSection16.1onpage249,ifoneoftheconditionsfails,forinstance(xor(eq?kitty2)(eq?betty3)),itisonlythechoicesofkittyandbettythatcausethefailure,butnaiivechronologicalbacktrackingwouldtrymanyotheralternativesbeforeactuallyarrivingatanychoicesthata ectthatfailure.Dependancy-directedbacktrackingontheotherhand,wouldbacktrackdirectlytothosechoicepoints.Whilesimpleenoughtodescribe,dependancy-directedbacktrackingisbynomeanseasytoimplementinpractice.Imentionedintheintroductiontothischapterthatambwasonesteptowardsalogicprogramminglanguage.AsI'vejustdescribedsomesortofdependancy-directedbacktrackingisanecessarysecondstepforanyproduction-qualitylanguage.Thethirdcomponenttologicprogrammingisaveryspecialandinterestingtechniquecalleduni cation.Ignoringanyeciencyconcernsthatambmighthave,wewillnonethelessbelookingatuni cation,andhowitfacilitateslogicprogramming,inthenextchapter.16.5.1AnAlternativeImplementationBefore nallymovingonfromambitisworthconsideringaslightlydi erent,andpotentiallymorepowerfulapproachtoitsimplementation.ThereisadesignpatterncalledParameterObjectwhichgoessomethinglikethis:Ifyouarealwayspassingthesamesetofparametersaroundfrommethodtomethod,thenwrapthoseparametersinasingleobjectandpassitasasingleparameter."Nowthe$contand$failparametersofambareperfectcandidatesfortheapplicationofthispattern.ThemainreasonsIhaven'tdoneitare1.Iwantedtokeepthecodeexplicit,and2.thecontfgandfailfgconstructswouldhavetobemadealotclevererinordertomanipulateanexistingcompositecontinuationparameter.HoweverifwehadgonefortheParameterObjectpattern,therewouldhavebeenaveryinterestingpayo :ifyoucouldhavetwocontinuations,whynotthree?four?etc.Whymightyouwantsucha 286CHAPTER16.CHRONOLOGICALBACKTRACKINGthing?Well,imaginealanguagewhereallthecontrol ow(forandwhileloops,break,continue,returnetc.)wereimplementedbycontinuations.Thenaforloopwouldinstallbreakandcontinuecontinuations(anduninstallthemagain),asubroutinewouldinstallareturncontinuation,etc.Evenmoreexciting,consideranenvironmentofcontinuationsasaparameterobject.Thenforexamplenestedforloopswouldpushandpoptheirbreakandcontinuecontinuations.Itwouldthenberelativelyeasytobreakorcontinueorreturntoanarbitrarycontainingpoint.Thesealternatives,whileexciting,areleftasanopenexerciseshouldyouwishtopursuethem.16.6TestsThe rstsetoftestsinListing16.7.1onthenextpagetriesoutorequalityandinequalityoperators.It'snicetoknowtheyallworkasexpected.ThenextsetoftestsinListing16.7.2onpage289exercizesthenewreplitselfensuringthattheappro-priateerrormessagesareproducedaftervariousrequestsforbacktracking,andthatthereplrecoversgracefullyinallsituations.Thelastsetoftests,inListing16.7.3onpage290givesambathoroughworkout.Ittestsmostoftheexamplesthatwehaveseeninthischapter,plusafewmoreforgoodmeasure.Additionally,itteststhatset!anddefinedoinfactundotheirassignmentsinthefaceofbacktracking 16.7.LISTINGS28716.7Listings16.7.1t/PScmCompare.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>38;006007BEGINfuseok('PScm')g008009evalok('(eq?11)','1','eqnumbers');010evalok('(eq?12)','0','neqnumbers');011evalok('(eq?1"1")','0','neqnumbersandstrings');012evalok("(eq?1'a)",'0','neqnumbersandsymbols');013evalok("(eq?1(list1))",'0','neqnumbersandlists');014015evalok('(eq?"a""a")','1','eqstrings');016evalok('(eq?"a""b")','0','neqstrings');017evalok('(eq?"1"1)','0','neqstringsandnumbers');018evalok('(eq?"a"'a)','0','neqstringsandsymbols');019evalok('(eq?"a"(list"a"))','0','neqstringsandlists');020021evalok("(eq?'a'a)",'1','eqsymbols');022evalok("(eq?'a'b)",'0','neqsymbols');023evalok("(eq?'a1)",'0','neqsymbolsandnumbers');024evalok('(eq?'a"a")','0','neqsymbolsandstrings');025evalok("(eq?'a(list'a))",'0','neqsymbolsandlists');026027evalok("(eq?(list12)(list12))",'1','eqlists');028evalok("(eq?(list12)(list123))",'0','neqlists');029evalok("(eq?(list1)1)",'0','neqlistsandnumbers');030evalok('(eq?(list"a")"a")','0','neqlistsandstrings');031evalok("(eq?(list'a)'a)",'0','neqlistsandsymbols');032033evalok("(eq?()())",'1','eqemptylists');034evalok("(eq?()(list1))",'0','neqemptylists');035evalok("(eq?1111)",'1','eqmultiplearguments');036evalok("(eq?1112)",'0','neqmultiplearguments');037038evalok("(<1234)",'1','4321)",'1','>multiplearguments');045evalok("(>4322)",'0','!>multiplearguments');046047evalok("(>=4322)",'1','>=multiplearguments');048evalok("(>=4323)",'0','!>=multiplearguments');049 288CHAPTER16.CHRONOLOGICALBACKTRACKING050evalok("(and123)","3",'andsuccess');051evalok("(and12()3)","()",'andfailure');052053evalok("(or123)","1",'orsuccess');054evalok("(or0030)","3",'orsuccess[2]');055evalok("(or0000)","0",'orfailure');056057#vim:ft=perl 16.7.LISTINGS28916.7.2t/AMBrepl.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>2;006007BEGINfuseok('PScm')g008009evalok(<8;006007BEGINfuseok('PScm')g008009my$prereqs=<ov)0740075(test(+on))))))076(test0)))))077078(defineeven?079(lambda(a)080((divisible-by2)a)))081EOT082083my$prereqsoutput=<millercooper))180(require(not(eq?(differencesmithfletcher)1)))181(require(not(eq?(differencecooperfletcher)1)))182(list(list'bakerbaker)183(list'coopercooper)184(list'fletcherfletcher)185(list'millermiller)186(list'smithsmith))))))187188(multiple-dwelling)189EOT190$prereqsoutput191multiple-dwelling192((baker3)(cooper2)(fletcher4)(miller5)(smith1))193EOR194195evalok(<(prove'((marylikesX)))>?((marylikeswine))>?((marylikescheese))>?((marylikesjohn))Alsonotethatthestatementtobeprovedhasthoseapparentlyredundantextrabracestoo.Thisisbecausethesystemcanbeaskedtoproveanynumberofthingsatonce,forexample:>(prove'((marylikesX)(Xlikesbeer)))((marylikesjohn)(johnlikesbeer))>?Error:nomoresolutionsThiscanbereadasprovemarylikesXandXlikesbeer."Thequeryonlysucceedsifallofthecomponentssucceed,soitisjustlikethebodyofaruleinthisrespect. 17.1.LOGICPROGRAMMINGEXAMPLES30117.1.2J.S.BachandFamilyThistypeofprogrammingcanalsobeusedforrecursivesearch.Figure17.1showsasmallsnippetoftheBachfamilytree,witholdJ.S.atthecentre.Figure17.1:Bach'sFamilyTreeJohannAmbrosiusMariaJohannAnnaBarbaraSebastianMagdalenaWilhelmCarlPhillipJCJohannFriedmannEmanuelFriedrichChristianWFErnstWecantranslatethatintothefollowingfacts:(((johannambrosius)father-of(js)))(((jcfriedrich)father-of(wfernst)))(((js)(annamagdalena)parents-of(jcfriedrich)))(((js)(annamagdalena)parents-of(johannchristian)))(((js)(mariabarbara)parents-of(wilhelmfriedmann)))(((js)(mariabarbara)parents-of(cpe)))Now,wehaveamixtureoffactsaboutfathersalone,andparentswherethemotherisknown.Butwecanrectifythatwithafewextrarulesasfollows:((Xfather-ofY)(X_parents-ofY))((Xmother-ofY)(_Xparents-ofY))((Xparent-ofY)(Xfather-ofY))((Xparent-ofY)(Xmother-ofY))Thatunderscoreisaspecialpatternvariablethatalwaysmatchesanything.The rstrulethensaysXisthefatherofYifXandanybodyareparentsofY"(theparents-offactsalwayslistthefather rst.)LikewisethesecondrulesaysXisthemotherofYifanybodyandXareparentsofY."Thelastpairof 302CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGrules(actuallyreallyoneruleintwoparts)saysthatXisaparentofYifXisthefatherofYorXisthemotherofY."Sowehaveageneralwayofexpressingbothandandorinourrules:andisexpressedbyadjacentstatementsinthebodyofasinglerule,whileorisexpressedasalternativeversionsofthesamerule.Nowthatwehaveageneralparent-ofrelation,wecanaddarecursiveancestor-ofrelationshipasfollows:((Xancestor-ofY)(Xparent-ofY))((Xancestor-ofY)(Xparent-ofZ)(Zancestor-ofY))ThissaysXisanancestorofYifXisaparentofYorXisaparentofsomeZandZisanancestorofY."Giventhatde nition,andtherestofthefactsandrules,wecanstarttoaskinterestingquestionsofthesystem:>(prove'((Xancestor-of(wfernst))))(((jcfriedrich)ancestor-of(wfernst)))>?(((johannambrosius)ancestor-of(wfernst)))>?(((js)ancestor-of(wfernst)))>?(((annamagdalena)ancestor-of(wfernst)))>?Error:nomoresolutionsWecanaddyetmorerulestothisdatabase.ForexampleXandYaresiblingsifXandYhavethesameparent:((Xsibling-ofY)(Zparent-ofX)(Zparent-ofY)(require(not(eq?'X'Y))))Ifwedon'trequirethatXandYarenotequalthenthisrulewouldthinkthatoldJ.S.washisownbrother.Giventhat,wecanask:>(prove'(((cpe)sibling-ofX)))(((cpe)sibling-of(johannchristian)))etc.17.1.3AppendingListsIncaseyou'restartingtothinkthatlogicprogrammingisjustsomesortofglori eddatabaselookup,here'samoremeatyexample.Considertheproblemofappendingtwolists.YoucanwriteanappendfunctioninPSchemeveryeasily1as:1Ofcourseyoucande neitevenmoreeasilyinPerlas(@a,@b)butthat'sonlybecausePerlalreadyhasanappendoperation,andsodoesanycompleteschemeimplementation. 17.1.LOGICPROGRAMMINGEXAMPLES303(defineappend(lambda(ab)(ifa(cons(cara)(append(cdra)b))b)))Itwalkstotheendofthelista,thenatthatpointreturnsthelistb,andastherecursionunwindsitbuildsacopyofaprependedtob.Soforexample:>(append'(ab)'(cd))(abcd)Thisde nitionisusefulenoughinitself,butlogicprogrammingallowsamuchmore exiblede nitionofappendasfollows:(definethe-rules(list'((append()YY))'((append(A.X)Y(A.Z))(appendXYZ))))NotethatthisusesthePSchemedottedpairnotationintroducedinSection8.4.1onpage89.Sotheexpression(A.X)referstoalistwho'scarisAandwho'scdrisX.The rstrulesaysthattheresultofappendingsomethingtotheemptylistisjustthatsomething.Thesecondrulesaysthatyoucanjoin(A.X)andYtomake(A.Z)ifyoucanjoinXandYtomakeZ.WhyisthatmorepowerfulthanthePSchemeappend?Becausewecanasklotsofquestionsofit.Notonlycanweaskwhatdowegetifweappend(ab)and(cd)?":>(prove'((append(ab)(cd)X)))((append(ab)(cd)(abcd)))Wecanalsoaskwhatdoweneedtoappendto(a)toget(abcde)?":>(prove'((append(a)X(abcde))))((append(a)(bcde)(abcde)))Andevenwhatcanweappendtogethertomake(abcd)?":>(prove'((appendXY(abcd))))((append()(abcd)(abcd)))>?((append(a)(bcd)(abcd)))>?((append(ab)(cd)(abcd)))>?((append(abc)(d)(abcd)))>?((append(abcd)()(abcd)))>?Error:nomoresolutions 304CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGThisideaisabsolutelycoretotheconceptoflogicprogramming.ArulethatstatesthatAandBmakeC"isequallycapableofdescribingwhatdoIneedtomakeC?"providedChasavaluewhenthequestionisasked.Itisasiftherelationshipdescribedbytherulecanbeinspectedfrommanydi erentangleswhenseekingasolutiontoaproblem.Backtoappend.Therulesforappendcanofcoursebemadeavailabletootherrules,forexample(peekingaheadabit)((sentenceS)(appendNPVPS)(noun-phraseNP)(verb-phraseVP))SaysSisasentenceifyoucanappendNPandVPtomakeS,andNPisanounphrase,andVPisaverbphrase.Rulesfornoun-phraseandverb-phrasewouldbeverysimilar,andrulesforindividualwordswouldjustbeoftheform((noun(garage)))etc.17.1.4FactorialAgainTheaboveexampleshavealreadydemonstratedthatourdatabaseofrulescanberecursive,sohowaboutthekingofrecursivefunctions,ouroldfriendthefactorialfunction?Hereitisrecastintoapairofrules:(definethe-rules(list'((factorial01))'((factorialNX)(Tis(-N1))(factorialTU)(Xis(*NU)))))Thisisn'tasbadasitmight rstlook.The rstruleisthebarefactthatthefactorialof0is1.ThesecondrulesaysthatthefactorialofNisXifTisN-1andthefactorialofTisUandXisN*U.Thespecialin xisoperatorforcesarithmeticevaluationofitsrighthandside,thenrequiresthatitslefthandsideisthesameastheresultofthatevaluation.giventheabovewecancalculatefactorials:>(prove'((factorial10X)))((factorial103628800))Howeverthereisalimitationhere.Becauseoftheunidirectionalnatureofthatisoperator,wecannotaskwhatnumbercanweapplyfactorialtotogetx":>(prove'((factorialX3628800)))Error:nomoresolutionsSoitgoes.17.1.5SymbolicDi erentiationIfyou'restillnotimpressed,howaboutamoredicultproblem?Ihopeyoudon'tmindalittlemaths.Thisnextexamplestatessomeoftherulesofsymbolicdi erentiationthenasksthesystemtoworkoutthedi erentialofanequation.Theruleswe'llbeusingare:Thederivativeofxinxis1.Thederivativeofanyconstantnumberinxis0. 17.1.LOGICPROGRAMMINGEXAMPLES305Thederivativeofxninxisnxn-1.Thederivativeoff+ginxisdf+dgifthederivativeoffinxisdfandthederivativeofginxisdg.Thederivativeoffginxisdfdgifthederivativeoffinxisdfandthederivativeofginxisdg.Thederivativeoffginxisfdg+gdfifthederivativeoffinxisdfandthederivativeofginxisdg.Thederivativeof1=finxisdf=f2ifthederivativeoffinxisdf.Thederivativeoff=ginxis(gdffdg)=f2ifthederivativeoffinxisdfandthederivativeofginxisdg.Ifyoudon'trememberthemathsfromschool,justsitbackandenjoytheride.Theserulescantranslatedirectlyintoourlogicsystemas:(definethe-rules(list'((derivativeXX1))'((derivativeNX0)(require(number?'N)))'((derivative(X^N)X(N*(X^P)))(Pis(-N1)))'((derivative(F+G)X(DF+DG))(derivativeFXDF)(derivativeGXDG))'((derivative(F-G)X(DF-DG))(derivativeFXDF)(derivativeGXDG))'((derivative(F*G)X((F*DG)+(G*DF)))(derivativeFXDF)(derivativeGXDG))'((derivative(1/F)X((-DF)/(F*F)))(derivativeFXDF))'((derivative(F/G)X(((G*DF)-(F*DG))/(G*G)))(derivativeFXDF)(derivativeGXDG))))ThisisobviouslywaymorecomplexthanMaryandJohn,butthereisn'tactuallymuchthatyouhaven'tseenbefore.The rstandmostimportantthingtorealiseisthat,withoneexception,thearithmeticoperators+",-"etc.meannothingspecialtothelogicprogram:theyarejustsymbolsinpatternstobematched.Havingsaidthatwedoneedwaystoperformnumerictestsandtodoarithmetic.ThebodyofsecondrulerequiresthatNisanumber,andthebodyofthirdruleevaluates(-N1)beforeassigning"ittoP.Rulesoftheform(requirehexpri)and(hvariishexpri)arerecognizedandtreatedspeciallybythesystem.Anyway,havingenteredtheseruleswecanaskthesystem: 306CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING(prove'((derivative(((x^2)+x)+1)xX)))Thatistosayprovethedi erentialofx2+x+1inxisX".Thesystemreplies:((derivative(((x^2)+x)+1)x(((2*(x^1))+1)+0)))ThelastlineisthecomputedvalueforX,thepatternvariableinthequery.Fornowwewillhavetosimplifythatmanually:(((2*(x^1))+1)+0)(2*x)+1Youcanseethattheresultisindeedthedi erentialofx2+x+1,namely2x+1.Thatshouldbeenoughtowhetyourappetiteforwhattherestofthischapterhastoo er.Wenextturnourattentiontopatternmatching,whichisthebasisofuni cation,whichalongwithambfromthepreviouschapteristhebasisofourlogicprogrammingimplementation.17.2PatternMatchingThekindofpatternmatchingwewillbediscussingherehasverylittleifanythingtodowithregularexpressions.Thissortofpatternmatchingisaboutmatchingapatternagainstastructure,notastring,andfurthermorethepatternitselfisastructure.Apatternorastructure,forthepurposesofourdiscussionisaPSchemeexpression:astring,number,symbolorlist.Howeverapatternmayalsocontainpatternvariables.ThesearenotthenormalvariablesofPSchemeprograms:anysymbolisavariableinthatsense;theseareaspecialtypeofsymbol,recognisedasavariableonlybythepatternmatchingsystem.AsfarastherestofPSchemeisconcerned,theyarejustordinarysymbols.Forourpurposesapatternvariablewillbeanysymbolthatstartswithacapitalletter2.AlthoughI'mcallingthesesymbolspatternvariables"here,I'lldropthatconventioninfutureandjustcallthemvariables"whenthecontextmakesitclearwhatImean.Firstlet'slookatafewexamplesofpatternmatching.Thepattern(abc)willonlymatchthestructure(abc)becausethepatterncontainsnovariables.Thepattern(abc)willnotmatchthestructure(abfoo)becausecdoesnotequalfoo.2ArealSchemeimplementationiscase-insensitive,sodoesnothavethisluxury.PSchemeiscase-sensitivewhichallowsustofollowtheconventionofthelogicprogramminglanguageProlog,wherecapitallettersintroducepatternvariables. 17.2.PATTERNMATCHING307Thepattern(aXc)willmatchthestructure(abc)becausethevariableXcanstandforthesymbolb.ThepatternXwillmatchthestructure(abc)becauseXcanstandfortheentirestructure.Thepattern(aXc)willmatchthestructure(a(123)c)becauseXcanstandfor(123).Thepattern(a(1X3)c)willmatchthestructure(a(123)c)becauseXcanstandfor2.Thepattern(aXY)willmatchthestructure(abc)becauseXcanstandforbandYcanstandforc.Thepattern(aXX)willnotmatchthestructure(abc)becausethevariableXmuststandforthesamethingthroughoutthematchingprocess:itcannotbebothbandc.I'msureyougettheideabynow.Theresultofapatternmatchisasetofbindings,oneforeachvariablethatmatched.Forexampleaftermatchingthepattern(aXY)against(a(123)b)theresultisthesetofbindingsX=>(123)andY=>b.Thisresultismuchthesameasanenvironment,andwe'lltakeadvantageofthisequivalencelater.17.2.1APerlPatternMatcherAsI'vesaid,patternmatchingismerelyaprecursortouni cation,whichisourgoal.ImplementingapatternmatchingsysteminPschemeisfairlytrivial,eitherinthePSchemelanguageitselforintheun-derlyingPerl,butwe'renotactuallygoingtodothat,becauseitisn'tpowerfulenoughforourpurposes.Howeverweneedtostartsomewhere,sothissectiondiscussesastandalonepattern-matchingimplemen-tationinPerl.It'seasyenoughtounderstand,andthesubsequentsectionbuildsonthattoproduceastandaloneuni cationimplementation.Havingarrivedatastandaloneuni cationimplementation,wecanwirethatintoourinterpreter.But rstweneedtolookatpatternmatching.ThisstandalonePerlpatternmatchercantakepatternsoftheform:['a',fb=>'X'g,'Y']wherecapitalizedstringsrepresentthepatternvariables.Itmatchesthemagainststructuressuchas['a',fb=>[2,3]g,'c']returningahashrefthatwilllooklikefX=>[2,3],Y=>'c'gHere'sabirds-eyeviewofhowour rstpatternmatcherwillwork.Itwalksboththepatternandthestructureinparallel,alsopassinganadditional,initiallyemptyenvironmentaround.Ifitencountersavariableinthepatternthenitcheckstoseeifthevariableisalreadysetintheenvironment.Ifitis,thenitchecksthatthecurrentstructurecomponentisthesameasthevalueofthevariable,failingifitisnot.Ifthevariableisnotsetintheenvironment,itextendstheenvironment,bindingthevariabletothecurrentstructurecomponent,andcontinues.Ofcoursematchingwillalsofailifthepatternandthestructurearenototherwiseidentical.Onsuccess,itreturnstheenvironment,andonfailure,itdies. 308CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGBothforthefunofit,andforcompleteness'sake,thepatternmatcherdescribedherecanhandleperlhashrefsaswellaslistrefs.Thismeansitwillacceptpatternsandstructurescontainingmixturesofbothtypes.Howeveronlythevaluesofahashinapatternwillberecognisedaspatternvariables,thekeyswillnot.Here'sthetop-levelmatch()routine:submatchfmy($pattern,$struct,$env)=@_;$env||=fg;if(var($pattern))fmatch_var($pattern,$struct,$env);gelsif(hashes($pattern,$struct))fmatch_hashes($pattern,$struct,$env);gelsif(arrays($pattern,$struct))fmatch_arrays($pattern,$struct,$env);gelsif(strings($pattern,$struct))fmatch_strings($pattern,$struct,$env);gelseffail();greturn$env;gFirstly,if$envisnotpassed,thenmatch()initializesittoanemptyhashref.Thepatternmatchingalgorithmisneverrequiredtoundoanyvariablebindingsthatitcreates,sowecanjustpassaroundahashbyreferenceandallowthebindingstoaccumulateinit.Thenweseevarioustestsforthetypesof$patternand$struct.Thevar()checkisjust:subvarfmy($thing)=@_;!ref($thing)&&$thing=~/^[A-Z]/;gSoavarisanystringthatstartswithacapitalletter.Thehashes()checkreturnstrueifbothargumentsarehashrefs:subhashesfmy($a,$b)=@_;hash($a)&&hash($b);gWherehash()isjust:subhashfmy($thing)=@_;ref($thing)eq'HASH';gTheothertwochecks,arrays()andstrings()arede nedequivalently: 17.2.PATTERNMATCHING309subarraysfmy($a,$b)=@_;array($a)&&array($b);gsubarrayfmy($thing)=@_;ref($thing)eq'ARRAY';gsubstringsfmy($a,$b)=@_;string($a)&&string($b);gsubstringfmy($thing)=@_;!var($thing)&&!ref($thing);gOk,that'stheadministrativesupportoutoftheway.Sothe rstthingthatmatch()does,ifits$patternisavar(),istocallmatchvar()onthe$patternandthe$struct,passingthecurrentenvironment.Here'smatchvar():submatch_varfmy($var,$struct,$env)=@_;if(exists($env->f$varg))fmatch($env->f$varg,$struct,$env);gelsef$env->f$varg=$struct;ggItcheckstoseeifthe$varisintheenvironment.Ifitisthenitattemptstomatch()thevalueofthevariableagainstthe$struct3.Ifthe$varisnotalreadyintheenvironmentthenitputsittherewithavalueequaltothecurrent$struct(anunassignedvariablewillalwaysmatchthecurrentstructurecomponent,andwillbeinstantiatedtoit.)Returningtomatch(),ifboththe$patternandthe$structarearrays(),match()callsmatch-arrays()onthem.matcharrays()walksbotharraysintandem,callingmatch()oneachpairofelements.Ifthearraysarenotthesamelengththentheycannotpossiblymatch,sothissanitycheckisperformed rst:submatch_arraysfmy($pattern,$struct,$env)=@_;my@patterns=@$pattern;3Weusematch()hereonlybecauseitisconvenient:neitherthevalueofthevariablenorthestructurewillcontainvariables,somatch()isbeingusedasarecursiveequalitytest(likeeq?.) 310CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGmy@structs=@$struct;if(@patterns!=@structs)ffail();gwhile(@patterns)fmatch(shift@patterns,shift@structs,$env);ggThefail()subdieswitha"matchfailed"message:subfailfdie"matchfailed ";gBacktomatch()again.Ifthe$patternandthe$structarebothhashes(),thenmatch()callsmatchhashes()onthem:submatch_hashesfmy($pattern,$struct,$env)=@_;check_keys_eq($pattern,$struct);foreachmy$key(sortkeys%$pattern)fmatch($pattern->f$keyg,$struct->f$keyg,$env);ggMuchasmatcharrays()checksthatthetwoarraysarethesamelength,matchhashes()checksthatthetwohasheshavethesamekeysusingcheckkeyseq():subcheck_keys_eqfmy($as,$bs)=@_;my$astr=join('.',sortkeys%$as);my$bstr=join('.',sortkeys%$bs);failunless$astreq$bstr;gThisisacheaptrickandcouldeasilybefooled,butit'sgoodenoughforourdemonstrationpurposes.Assumingthatthehasheshaveequalkeys(thispatternmatcherdoesnotallow|oratleastexpect|hashkeystobevariables),matchhashes()walksthekeysmatchingtheindividualcomponentsinmuchthesamewayasmatcharrays()did.Itsortsthekeysbeforetraversingthemtoensuretheorderofanyvariableassignmentisatleastdeterministic.Backtomatch()yetagain.Ifboththe$patternandthe$structarestrings(),match()callsmatchstrings()onthem.Thisisthemosttrivialofthematchingsubroutines:itjustcomparesthestringsandfailsiftheyarenotequal:submatch_stringsfmy($pattern,$struct,$env)=@_;failif$patternne$struct;gThiscompletesourprototypepatternmatchingimplementation.Whileverysimple,thispatternmatchercanbemadetodousefulwork.Consideradatabase"offactsinaPerllist: 17.3.UNIFICATION311my@facts=(fcomposer=>'beethoven',initials=>'lv',lived=>[1770,1829]g,fcomposer=>'mozart',initials=>'wa',lived=>[1756,1791]g,fcomposer=>'bach',initials=>'js',lived=>[1685,1750]g);Wecanusethematchertoextractinformationfromthislist:foreachmy$fact(@facts)fevalfmy$result=match(fcomposer=>'COMPOSER',initials=>'js',lived=>'LIVED'g,$fact);print"Thecomposerwithinitials'js'","is$result->fCOMPOSERg","wholivedfrom$result->fLIVEDg[0]","to$result->fLIVEDg[1] ";g;gThat'sitforpatternmatching.WenextturnourattentiontoUni cation,whichasI'vesaidisanextensiontoPatternMatchingandismuchmoreinteresting.17.3Uni cationUni cation,inanutshell,isthematchingoftwopatterns.Itsolvestheproblemgiventwopatterns,thatmightbothcontainvariables, ndvaluesforthosevariablesthatwillmakethetwopatternsequal."Ourpatternmatcherfromtheprevioussectionisagoodjumpingo pointforimplementingtrueuni cation.Infactithasmostofthethingswe'llneedalreadyinplace.Thenextsectiondiscussesthemodi cationswewillneedtomaketoit. 312CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING17.3.1APerlUni erThisuni ercansolveabroaderclassofproblemsthanasimplepatternmatchercan.Forexamplegiventhetwopatterns:['f',['g','A'],'A']and['f','B','abc']Itcancorrectlydeduce:A=>'abc',B=>['g','abc']YoucanseetheprocessgraphicallyinFigure17.2.Thevariable'A'uni eswiththeterm'abc'whilethevariable'B'uni eswiththecompoundterm['g','A'],where'A'isprovidedwithavalue'abc'fromthepreviousuni cation.Figure17.2:Uni cationof['f',['g','A'],'A']with['f','B','abc'][f[gA]A][fBabc][f[gabc]abc]Uni cationiscapableofevenmorecomplexresolutions,forexampleitcanunify(ommittingquotesforbrevitythistime)[F,[A,1,B],[A,B],2]with[C,[D,D,E],C,E]ToshowthatA=1B=2C=[1,2] 17.3.UNIFICATION313D=1E=2F=[1,2]YoucanseethisinactioninFigure17.3ifyoujustfollowthedi erentlystyledarrowsstartingfromthethreeringednodesinthe gureastheypropogateinformationaround.Figure17.3:Amorecomplexuni cationexample[F[A1B][AB]2][C[DDE]CE][[12][112][12]2]Thisuni erhasadditionalfeature:theanonymousvariable"(underscore)behaveslikeanormalvariablebutwillalwaysmatchanything,sinceitisneverinstantiated.Thisallowsyoutospecifyadon'tcare"condition.Forexample,goingbacktoourdatabaseofcomposers,thepattern:fcomposer=>'COMPOSER',initials=>'_',lived=>'_'gwilljustretrieveallofthecomposersnamesfromthedatabase,withouttestingorinstantiatinganyothervariables.Notalsothatsince"alwaysmatches,andisneverinstantiated,itcanbereusedthroughoutapattern.Thisuni erisadirectmodi cationofthepatternmatcherfromtheprevioussection,solet'sjustconcentrateonthedi erences.Firstlymatch()hasbeenrenamedtounify(),andithasanextraclause,incasetheoldstructure,whichisnowalsoapattern,containsvariables.Thevariousmatch*subshavealsobeenrenamedunify*,andthevariables$patternand$struct,nowbothpatterns,havebeenrenamedtojust$aand$b: 314CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGsubunifyfmy($a,$b,$env)=@_;$env||=fg;if(var($a))funify_var($a,$b,$env);gelsif(var($b))funify_var($b,$a,$env);gelsif(hashes($a,$b))funify_hashes($a,$b,$env);gelsif(arrays($a,$b))funify_arrays($a,$b,$env);gelsif(strings($a,$b))funify_strings($a,$b,$env);gelseffail();greturn$env;gTheextraclause,if$bisavar,simplyreversestheorderoftheargumentstounifyvar().Notethatthesingleenvironmentmeansthatvariableswillshareacrossthetwopatterns.Ifyoudon'twantthis,simplymakesurethatthetwopatternsdon'tusethesamevariablenames.unifyhashes(),unifyarrays()andunifystrings()areidenticaltotheirmatch*equivalents,exceptthatunifyarrays()andunifyhashes()callunify()insteadofmatch()ontheircomponents.Thevar()checkisslightlydi erent,toallowfortheanonymousvariable:subvarfmy($thing)=@_;!ref($thing)&&($thingeq'_'||$thing=~/^[A-Z]/);gThatleavesunifyvar(),wheretheactionis.unifyvar()isstillquitesimilartomatchvar(),itjusthasmorethingstowatchoutfor:subunify_varfmy($var,$other,$env)=@_;if(exists($env->f$varg))funify($env->f$varg,$other,$env);gelsif(var($other)&&exists($env->f$otherg))funify($var,$env->f$otherg,$env);gelsif($vareq'_')freturn;gelsef$env->f$varg=$other;gg 17.3.UNIFICATION315So$structwasrenamedto$other,andunifyvar()callsunify()insteadofmatch().If$varisnotsetintheenvironment,insteadofimmediatelyassumingitcanmatch$other,unifyvar()lookstoseeif$otherisavarandalreadyhasavalue.Ifsoitcallsunify()on$varandthevalueof$other.If$otherisnotavar,orhasnobinding,unifyvar()nextcheckstoseeif$varistheanonymousvariable.Ifitis,thenbecausetheanonymousvariablealwaysmatchesandisneverinstantiated,itjustreturns.Lastly,onlywhenallotheroptionshavebeentried,itaddsabindingfromthe$varto$otherandreturns.Let'swalkthroughtheactionsofunify()asitattemptstounifythetwopatterns['f',['g','A'],'A']and['f','B','abc'].unify(['f',['g','A'],'A'],['f','B','abc'],fg)iscalledwiththetwocompletepat-ternsandanemptyenvironment,anddeterminesthatbothpatternsarearrays,socallsunify-arrays().{unifyarrays(['f',['g','A'],'A'],['f','B','abc'],fg)simplycallsunify()oneachcomponent.unify('f','f',())determinesthatbothitsargumentsarestrings,andcallsunify-strings().unifystrings('f','f',fg)=fgsucceedsbuttheenvironmentisunchanged.unify(['g','A'],'B',fg)determinesthatit'ssecondargumentisavariableandsocallsunifyvar()withtheargumentsreversed.unifyvar('B',['g','A'],fg)=fB=>['g','A']gsucceeds,andunify-var()extendstheenvironmentwith'B'boundto['g','A'].unify('A','abc',fB=>['g','A']g)determinesthatit's rstargumentisavari-ableandsocallsunifyvar()again,passingtheenvironmentthatwasextendedbythepreviouscalltounifyvar().unifyvar('A','abc',fB=>['g','A']g)=fB=>['g','A'],A=>'abc'galsosucceeds,extendingtheenvironmentwithanewbindingof'A'to'abc'.Thisenvironmentisthe nalresultoftheentireuni cation.Sothe nalresultfB=>['g','A'],A=>'abc'gfallsalittleshortofourexpectations,becausethevaluefor'B'stillcontainsareferencetothevariable'A'4.Howeverthisisnotaproblemassuch.wecanpatchuptheresultwithaseparateroutinecalledresolve().subresolvefmy($pattern,$env)=@_;while(var($pattern))fif(exists$env->f$patterng)f$pattern=$env->f$patterng;gelsefreturn$pattern;ggif(hash($pattern))f4Infactitisquitepossibletoretrievebindingsofonevariabledirectlytoanother,likefA=>'B'ginothercircumstances,forexampleif'B'didnothaveavaluewhenitwasuni edwith'A'. 316CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGmy$ret=fg;foreachmy$key(keys%$pattern)f$ret->f$keyg=resolve($pattern->f$keyg,$env);greturn$ret;gelsif(array($pattern))fmy$ret=[];foreachmy$item(@$pattern)fpush@$ret,resolve($item,$env);greturn$ret;gelsefreturn$pattern;ggresolve()takesapattern,andtheenvironmentthatwasreturnedbyunify().Ifandwhilethepatternisavariable,itrepeatedlyreplacesitwithitsvaluefromtheenvironment,returningthevariableifitcannotfurtherresolveit.Theniftheresultisahashoranarrayreference,resolve()recursivelycallsitselfoneachcomponentoftheresult,alsopassingtheenvironment.The nalresultofresolve()isastructurewhereanyvariablesinthepatternthathavevaluesintheenvironmenthavebeenreplacedbythosevalues.Notethatresolve()doesnotchangetheenvironmentinanyway.Thiscompletesourstand-aloneimplementationofunify().HopefullyseeingitoutintheopenlikethiswillmakethesubsequentimplementationwithinPSchemeeasiertodigest.Itisalittlediculttodemonstratetheutilityofunify()atthispoint,sinceit'spurposeismostlypartoftherequirementsoflogicprogramming,howevertogiveyousomeidea,considerthattheresultofoneuni cation,anenvironmentorhash,canbepassedasargumenttoaseconduni cation,thusconstrainingthevaluesthatthepatternvariablesintheseconduni cationcanpotentiallytake.Thereisoneextremelyinterestingandusefulapplicationofuni cationoutsideoflogicprogrammingwhichmakesuseofthisidea.ConsiderthatwemightwanttocheckthatthetypesofthevariablesinaPSchemeexpressionarecorrectbeforeweevaltheexpression.Assumethatweknowthetypesoftheargumentsandreturnvaluesfromallprimitivesinthelanguage.Furthermorewecanalsodetectthetypesofanyvariableswhichareassignedconstantsdirectly.Itisthereforepossibletodetectifavariable'sassignedvaluedoesnotmatchit'seventualuse,evenifthateventualuseisremote(throughlayersoffunctioncalls)fromtheoriginalassignment.Suchalanguage,whichdoesnotdeclaretypesbutisnonethelesscapableofdetectingtypemismatches,iscalledanimplicitlytypedlanguage.Wecanuseuni cationtodothistypechecking,bytreatingPSchemevariablesaspatternvariablesandunifyingthemwiththeirtypesandwitheachotheracrossfunctioncalls,accumulatingtypesofargumentsandreturnvaluesforlambdaexpressionsandfunctionsintheprocess.Thathowever,isforanotherchapter.Nextwe'regoingtolookattheimplementationofunifyinPScheme.17.3.2ImplementingunifyinPSchemeTogettheballrollingwiththeimplementationofunify,noticethatthepreviousimplementationofunify()frequentlyteststhetypesofitsarguments.Obviouslyinanobject-orientedimplementation 17.3.UNIFICATION317likePSchemewecandistributeaUnify()methodaroundthevariousdatatypesandavoidthisexplicittypecheckingforthemostpart.Asecondpointworthnotingisthatwheretheaboveunify()didadieonfailure,ournewUnify()canquitereasonablyinvokebacktrackinginstead,totryanotheroption,which tsinquiteneatlywithourexistingambimplementation.Athirdand nalpoint.unify()abovemadeuseofa athashtokeeptrackofvariablebindings,butPSchemealreadyhasaserviceableenvironmentimplementationandweshouldmakeuseofit.ThiswillmeanexposingtheenvironmentasaPSchemedatatypesincethatiswhatisexplicitlypassedtoandreturnedbyUnify(),butthisisnotaconcernsincewehavedonethisoncebeforeinourclassesandobjectsextensionfromChapter12onpage135.We'dbetterstartbylookingattheunifycommandinactionintheinterpreter.TheresultofacalltounifyisaPScm::Envwhichisn'tmuchdirectuse.HoweverwecanaddanotherPSchemecommandthatwillhelpusoutthere.substitutetakesaformandanenvironment,andreplacesanypatternvariablesintheformwiththeirvaluesfromtheargumentenvironment.Italsoperformstheresolve()functiononeachvaluebeforesubstitution.Soforexample:>(substitute>'(fBA)>(unify'(f(gA)A)>'(fBabc)))(f(gabc)abc)Thecalltounifyprovidesthesecondargument,anenvironment,tosubstitute,whichthenperformstheappropriatereplacementsontheexpression(fBA)toproducetheresult(f(gabc)abc).Notethatinallcaseswehavetoquotetheexpressionstopreventthemfrombeingevaluated.Wedoneedtheinterpretertoevaluatetheargumentstosubstituteandunifyinmostcaseshowever,becausetheactualformsbeingsubstitutedanduni edmaybepassedinas(normalPScheme)variablesorotherwisecalculated.Ishouldprobablyalsodemonstratethatunifydoesproperbacktrackingifitfails:>(unify'(abc)'(abd))Error:nomoresolutionsIt'sstillsomewhatdiculttodemonstratetheusefulnessofunifycombinedwithambatthisstagehowever.Thatwillhavetowaituntilthenextsectionwherewe nallygettoseelogicprogramminginaction.The rstthingweneedtodothen,istocreateanewspecialformPScm::SpecialForm::UnifyandgiveitanApply()method.Thisspecialformwillbeboundtothesymbolunifyinthetop-levelenvironment.unifywilltaketwoorthreearguments.The rsttwoargumentsarethepatternstobeuni ed.Thethird,optionalargumentisanenvironmenttoextend.Ifunifyisnotpassedanenvironment,itwillcreateanew,emptyone.Wehavetomakeunifyaspecialformbecauseitneedsaccesstothefailurecontinuation.Here'sPScm::SpecialForm::Unify:504packagePScm::SpecialForm::Unify;505506usebaseqw(PScm::SpecialForm);507 318CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING508usePScm::Continuation;509510subApplyf511my($self,$form,$env,$cont,$fail)=@_;512$form->map_eval(513$env,514contf515my($evaluated_args,$fail)=@_;516my($a,$b,$qenv)=$evaluated_args->value;517$qenv||=newPScm::Env();518$a->Unify($b,$qenv,$cont,$fail);519g,520$fail521);522g5235241;Youcanseethatitusesmapeval()fromSection13.6.5onpage206toevaluateitsargument$form,passingitacontinuationthatbreaksoutthepatterns$aand$b,andtheoptionalenvironment$qenvfromtheevaluatedarguments.Thenitdefaults$qenvtoanew,emptyenvironment,andcallsUnify()on$apassingittheotherpattern,thequeryenvironmentandthesuccessandfailurecontinuations.Referringbacktoourtestimplementationofunify()inSection17.3.1onpage312wecanseethatthe rstthingthatimplementationdoesistocheckifits rstargumentisavar,andifsocallunifyvar()onit.WecanreplacethisexplicitconditionalwithpolymorphismbyputtingaUnify()methodatanappropriateplaceinthePScm::Exprhierarchy.ButthePScm::Expr::Symbolclassisnotthebestplace:notallsymbolsarepatternvariables,onlythosestartingwithcapitallettersorunderscores.Sohere'sthetrick.WecreateanewsubclassofPScm::Expr::SymbolcalledPScm::Expr::Varandputthemethodthere.Read()candetectpatternvariablesoninputandcreateinstancesofthisnewclassinsteadofPScm::Expr::Symbol.SincethenewclassinheritsfromPScm::Expr::Symbol,andwedonotoverrideanyofthatclass'sexistingmethods,thesenewPScm::Expr::VarobjectsbehaveexactlylikeordinarysymbolstotherestofthePSchemeimplementation.Here'sthechangetonexttoken()fromPScm::Readtomakethishappen.066sub_next_tokenf067my($self)=@_;068069while(!$self->fLineg)f070$self->fLineg=$self->fFileHandleg->getline();071returnundefunlessdefined$self->fLineg;072$self->fLineg=~s/^s+//s;073g074075for($self->fLineg)f076s/^(s*//&&returnPScm::Token::Open->new();077s/^)s*//&&returnPScm::Token::Close->new();078s/^'s*//&&returnPScm::Token::Quote->new(); 17.3.UNIFICATION319079s/^,s*//&&returnPScm::Token::Unquote->new();080s/^.s*//&&returnPScm::Token::Dot->new();081s/^([-+]?d+)s*//082&&returnPScm::Expr::Number->new($1);083s/^"((?:(?:\.)|([^"]))*)"s*//&&dof084my$string=$1;085$string=~s/\//g;086returnPScm::Expr::String->new($string);087g;088s/^([A-Z_][^s()]*)s*//089&&returnPScm::Expr::Var->new($1);090s/^([^s()]+)s*//091&&returnPScm::Expr::Symbol->new($1);092g093die"can'tparse:$self->fLineg";094gTheonlychangeisonLines88{89whereifthetokenmatchedstartswithacapitalletterorunderscorethennexttoken()returnsanewPScm::Expr::VarwhereotherwiseitwouldhavereturnedaPScm::Expr::Symbol.Nowwehavesomewheretohangthefunctionalityequivalenttounifyvar()fromourtestimple-mentation,wecanputitinamethodcalledUnify()inPScm::Expr::Var:378subUnifyf379my($self,$other,$qenv,$cont,$fail)=@_;380381if(defined(my$value=$qenv->LookUpNoError($self)))f382$value->Unify($other,$qenv,$cont,$fail);383gelsif($other->is_var&&384defined(my$other_value=$qenv->LookUpNoError($other)))f385$other_value->Unify($self,$qenv,$cont,$fail);386gelsif($self->is_anon)f387$cont->Cont($qenv,$fail);388gelsef389$qenv->ExtendUnevaluated(390newPScm::Expr::List($self),391newPScm::Expr::List($other),392$cont,393$fail394);395g396gIt'sidenticaltotheearlierunifyvar()exceptthatitmakesuseofmethodcallsandiswrittenincps.Theisvar()methodisde nedtobefalseattherootoftheexpressionhierarchyinPScm::Expr,butisoverriddentobetrueinPScm::Expr::Varalone.Equivalentlyisanon()isde nedfalseinPScm:: 320CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGExprbutde nedtobetrueifthevalueofthevaris"inPScm::Expr::Var5:400subis_anonf401my($self)=@_;402$self->valueeq'_';403gTheonlyotheroccurrenceofUnify()isattherootofthehierarchyinPScm::Expr:049subUnifyf050my($self,$other,$qenv,$cont,$fail)=@_;051if($other->is_var)f052$other->Unify($self,$qenv,$cont,$fail);053gelsef054$self->UnifyType($other,$qenv,$cont,$fail);055g056gThisreallyjusttakescareofthecasewherethe rstpatternisnotavarbutthesecondpatternis.IfthesecondpatternisavaritcallsUnify()onit,passing$selfastheargument,reversingtheorderinthesamewayasourprototypeunify()did.Ifthesecondpatternisnotavar,thenitcallsanewmethodUnifyType()on$self.UnifyType()isjustanothernameforUnify()andallowsasecondcrackatpolymorphismsinceitisimplementedseparatelyinacoupleofplacesinthePScm::Exprhierarchy.The rstsuchplaceisinPScm::Expritself.058subUnifyTypef059my($self,$other,$qenv,$cont,$fail)=@_;060if($self->Eq($other))f061$cont->Cont($qenv,$fail);062gelsef063$fail->Fail();064g065gThisworksforallatomicdatatypes.IfthetwopatternsareEq()thensucceed,otherwisefail.Thisisthe rstplacewe'veactuallyseenthefailurecontinuationinvoked.NotethattheequalitytestEq()implicitlydealswithtypeequivalenceforus,sowedon'tneedthearrays()routinesetc.fromtheprototype.NowtheonlyotherplaceweneedtoputUnifyType()isinPScm::Expr::List::Pair276subUnifyTypef277my($self,$other,$qenv,$cont,$fail)=@_;278if($other->is_pair)f279$self->[FIRST]->Unify(280$other->[FIRST],281$qenv,5WecouldhavefurthersubclassedPScm::Expr::Varandhadthereaderrecognizeunderscoresasthistype,butis-anon()istheonlymethodthatwouldthenneedtobespecializedtothistype. 17.3.UNIFICATION321282contf283my($qenv,$fail)=@_;284$self->[REST]->Unify(285$other->[REST],286$qenv,287$cont,288$fail289);290g,291$fail292);293gelsef294$fail->Fail();295g296gThisPScm::Expr::List::Pair::UnifyType()isinfactsimplerinonesensethantheunifyarrays()fromourtestimplementation.Firstitperformsasimplecheckthatthe$otherisalist.Ifnotitcallsthefailurecontinuation.Then,ratherthanwalkingbothlists,itonlyneedstocallUnify()onitsfirst()andrest(),passingthe$other'sfirst()orrest()appropriately.Ofcoursethisisalittlecomplicatedbecauseit'sincps,butnonethelessthatisallithastodo.That'sallforunifyitself.Ifyourememberfromthestartofthissection,wewillalsoneedasubstitutebuiltintoreplacepatternvariableswithvaluesintheenvironment.Itiscalledlike(substitutehpatternihenvi)andreturnsthehpatternisuitablyinstantiated.Wecanmakethisaprimitiveratherthanaspecialformbecauseithasnoneedofanenvironment(otherthantheonethatisexplicitlypassed)andnoneedtoaccessthefailurecontinuation(italwayssucceeds).Here'sPScm::Primitive::Substitute:216packagePScm::Primitive::Substitute;217218usebaseqw(PScm::Primitive);219220sub_applyf221my($self,$body,$qenv)=@_;222$body->Substitute($qenv->ResolveAll());223gItdoesnothingmuchbyitself,merelycallingaResolveAll()methodontheargumentenvironmentthenpassingtheresulttothe$body'sSubstitute()method.We'lltakealookatthatnewPScm::Env::ResolveAll()method rst.266subResolveAllf267my($self)=@_;268my%bindings; 322CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING269foreachmy$var($self->Keys)f270$bindingsf$varg=$self->Resolve(newPScm::Expr::Var($var));271g272return$self->new(%bindings);273gThisResolveAll()loopsovereachkeyintheenvironment,callingasubsidaryResolve()methodoneachandsavingtheresultinatemporary%bindingshash.ThenitcreatesandreturnsanewPScm::Envwiththosebindings.Ifyoureferbacktoourresolve()functionintheprototype,youcanseethatinthe rststage,ifthe$patternisavariable,itrepeatedlyattemptstoreplaceitwithavaluefromtheenvironmentuntileitheritisnotavariableanymoreoritcannot ndavalue.ThisResolveAll()ise ectivelypre-processingtheenvironmentsothatanysubsequentlookupforapatternvariablewillnotneedtoperformthatiteration.Keys()justcollectsallthekeysfromtheenvironment:275subKeysf276my($self,$seen)=@_;277$seen||=fg;278foreachmy$key(keys%f$self->fbindingsgg)f279$seen->f$keyg=1;280g281if($self->fparentg)f282$self->fparentg->Keys($seen);283g284return(keys%$seen);285gandResolve()doesexactlywhatresolve()didinourtestimplementation:itrepeatedlyreplacesthevariablewithitsvaluefromtheenvironmentuntilthevariableisnotavariableanymore,orcannotbefound.Ifit ndsanon-variablevalueitcallsitsResolveTerm()methodonit,passingtheenv$selfasargument,andreturningtheresult.287subResolvef288my($self,$term)=@_;289while($term->is_var)f290if(my$val=$self->LookUpNoError($term))f291$term=$val;292gelsef293return$term;294g295g296return$term->ResolveTerm($self);297gResolveTerm()givesanycompoundtermachancetoresolveanypatternvariablesitmaycontain.Therearetwode nitionsofResolveTerm().TheonlycompoundtermsinPSchemearelists,andpatternvariablesthemselveshavealreadybeenresolved,sothedefaultResolveTerm()inPScm::Exprjustreturns$self: 17.3.UNIFICATION323067subResolveTermf068my($self,$qenv)=@_;069return$self;070gThesecondde nitionofResolveTerm()is,notsurprisingly,inPScm::Expr::List::Pair:298subResolveTermf299my($self,$qenv)=@_;300return$self->Cons($qenv->Resolve($self->[FIRST]),301$qenv->Resolve($self->[REST]));302gItwalksitself,callingtheargument$qenv'sResolve()methodoneachcomponent,andreturninganewPScm::Expr::Listoftheresults.Sowe'retalkingabouthowsubstituteworks,andwesawthatprimitive'sapply()methodcalledtheargument$qenv'sResolveAll()methodtoreturnanewenvironmentwithanypatternvariablesinthevaluesreplaced,wherepossible.Thenapply()passedthatnewenvironmenttoitsargument$body'sSubstitute()method.We'vejustseenhowResolveAll()works,nowwecanlookatSubstitute()itself.Onlypatternvariablescanbesubstituted,butlistsneedtoexaminethemselvestoseeiftheycontainanypatternvariables.SoadefaultSubstitute()methodinPScm::Exprtakescareofallthethingsthatcan'tbesubstituted,itjustreturns$self:072subSubstitutef073my($self,$qenv)=@_;074return$self;075gTheSubstitute()inPScm::Expr::Varreturnseitheritsvaluefromtheenvironmentoritselfifitisnotintheenvironment:405subSubstitutef406my($self,$qenv)=@_;407return$qenv->LookUpNoError($self)||$self;408gFinally,theSubstitute()inPScm::Expr::List::PairrecursivelycallsSubstitute()oneachofitscomponents,constructinganewlistoftheresults:304subSubstitutef305my($self,$qenv)=@_;306return$self->Cons($self->[FIRST]->Substitute($qenv),307$self->[REST]->Substitute($qenv));308gAndthat'ssubstitute.Tosumup,ittriesashardasitcantoreplaceallpatternvariablesintheformwithvaluesfromtheenvironment,recursingnotonlyintotheformitissubstituting,butalsointothevaluesofthevariablesthemselves. 324CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGThereareafewmorethingsweneedtoaddtotheinterpreterbeforewecanshowo itsnewprowess.Firstlywewillhaveoccasiontopassanemptyenvironmentintounify(indirectly),andforthatwe'llneedanew-envprimitive.Thisisassimpleasitgets:258packagePScm::Primitive::NewEnv;259260usebaseqw(PScm::Primitive);261262sub_applyf263newPScm::Env();264gAnotherthingwe'llneedgoesbacktoapassingcommentImadeawhileback.Itcanbeaproblemifyoutrytounifytwopatternsthatinadvertantlyusethesamepatternvariablenames.Sometimesyouwantthevariablestoshareavalue,andsometimesyoudon't.Forthisreasonweneedsomethingthatwilltakeapatternandreplaceitsvariablenameswithnewvariablenamesthatareguaranteedtobeunique.Thesamevariableoccurringmorethanonceinthepatternshouldcorrespondtothesamenewvariableoccurringmorethanonceintheresult,butweshouldbereasonablycon dentthatthenewvariablenamewon'tappearanywhereelseintheprogrambyaccident.ThisnewPSchemefunctioniscalledinstantiate.ItcouldbewritteninthePSchemelanguage,butthatwouldrequireaddingotherlessgermaneprimitivesforcreatingsymbolsetc.soallinallitisprobablybettertobuilditintothecore.Itcanbeaprimitive,butitwillneedabitmorethanjustanapply()method:267packagePScm::Primitive::Instantiate;268269usebaseqw(PScm::Primitive);270271subnewf272my($class)=@_;273blessf274seen=>fg,275counter=>0,276g,$class;277g278279sub_applyf280my($self,$body)=@_;281$self->fseeng=fg;282$body->Instantiate($self);283g284285subReplacef 17.3.UNIFICATION325286my($self,$var)=@_;287return$varif$var->is_anon;288unless(exists($self->fseengf$var->valueg))f289$self->fseengf$var->valueg=290newPScm::Expr::Var($self->fcounterg++);291g292return$self->fseengf$var->valueg;293g2942951;RememberthatthereisonlyoneinstanceofanygivenprimitiveorspecialforminthePSchemeenviron-ment,andthatpersistsforthedurationoftherepl.Sobygivingthisprimitiveitsownnew()methodweprovideaconvenientplacetostoreasingletoncounterthatwecanusetogeneratenewsymbols.Theseen eldoftheobjecthowever,whichkeepstrackofwhichvariablestheinstantiatefunctionhasalreadyencountered,mustbere-initializedtoanemptyhashoneachseparateapplicationoftheprimitive.After(re-)initializingseenonLine281,apply()callsitsargument$body'sInstantiate()methodpassing$selfasargument.ObviouslytheonlyPScm::ExprtypethatwillavailitselfoftheinstantiateobjectisPScm::Expr::Var,andthatwillmakeuseofthecallbackmethodReplace()to ndorgenerateareplacementvariable.Replace()then, rstcheckstoseeifthevariableistheanonymousvariable(Line287).Ifsothenitjustreturnsthevariable,sincetheanonymousvariableneversharesandshouldneverbereplacedbyavariablethatwillshare.Thenunlessithasalreadyseenthevariableitcreatesanewaliasforitbyusingtheincrementingcounter(Lines288-291).ThisworkswellbecausethereaderwouldnevercreateaPScm::Expr::Varfromanumber.JustaswithUnify()andSubstitute(),adefaultmethodInstantiate()inPScm::Exprhandlesmostcasesandjustreturns$self:079subInstantiatef080my($self,$instantiator)=@_;081return$self;082gThePScm::Expr::Varversionofinstantiatecallsthe$instantiator'sReplace()callbacktogetanewvariable,passing$selfasargumentbecauseReplace()needstokeeptrackofthevariablesithasseenalready:412subInstantiatef413my($self,$instantiator)=@_;414return$instantiator->Replace($self);415gFinallyPScm::Expr::List::Pair::Instantiate()recursesonbothitsfirst()andrest()compo-nents,callingInstantiate()onbothandconstructinganewlistonthewaybackout:316subInstantiatef317my($self,$instantiator)=@_; 326CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING318return$self->Cons($self->[FIRST]->Instantiate($instantiator),319$self->[REST]->Instantiate($instantiator));320gThelastthingwe'regoingtoneed,forpragmaticreasons,isawaytocheckthetypeofvariousexpressionsfromwithinthePSchemelanguage.Aproperschemeimplementationhasafullsetofsuchtypecheckingfunctions,butwe'reonlygoingtoneedpair?,number?andvar?(notethequestionmarks.)Theyareallprimitives,andinfacthavesomuchincommonthatwewillcreateanabstractparentclasscalledPScm::Primitive::TypeCheckandputasharedapply()methodinthere:226packagePScm::Primitive::TypeCheck;227228usebaseqw(PScm::Primitive);229230sub_applyf231my($self,$body)=@_;232if($self->test($body))f233returnnewPScm::Expr::Number(1);234gelsef235returnnewPScm::Expr::Number(0);236g237gYoucanseethatitcallsatest()methodwhichwemustsubclassforeachtest,thenitreturnsanappropriatetrueorfalsevaluedependingonthetest.Here'sthetestforpair?inPScm::Primitive::TypeCheck::Pair:240packagePScm::Primitive::TypeCheck::Pair;241usebaseqw(PScm::Primitive::TypeCheck);242243subtestf$_[1]->is_pairgWe'vealreadyseenthatispair()isde nedfalseinPScm::ExprandoverriddentobetrueinPScm::Expr::List::Pairalone.Theequivalentnumber?andvar?PSchemefunctionsareboundtoPScm::Primitive::TypeCheck::NumberandPScm::Primitive::TypeCheck::Var,andmakeuseofequiv-alentisnumber()andisvar()methodsinPScm::Expr.Wehavenowimplementedthefourcomponentsweneedtogetonwithde ningalogicprogramminglanguage:unify,substitute,new-envandinstantiate.Alongwiththosewehavealsoaddedthethreetypetestspair?,var?andnumber?whichjustcheckiftheirargumentisofthattype.Theyareallwiredintothereplinthenormalway,here'stheadditions: 17.3.UNIFICATION327036subReadEvalPrintf037my($infh,$outfh)=@_;038039$outfh||=newFileHandle(">-");040my$reader=newPScm::Read($infh);041my$initial_env;042$initial_env=newPScm::Env(043let=>newPScm::SpecialForm::Let(),044'*'=>newPScm::Primitive::Multiply(),045'-'=>newPScm::Primitive::Subtract(),046'+'=>newPScm::Primitive::Add(),047if=>newPScm::SpecialForm::If(),048lambda=>newPScm::SpecialForm::Lambda(),049list=>newPScm::Primitive::List(),050car=>newPScm::Primitive::Car(),051cdr=>newPScm::Primitive::Cdr(),052cons=>newPScm::Primitive::Cons(),053letrec=>newPScm::SpecialForm::LetRec(),054'let*'=>newPScm::SpecialForm::LetStar(),055eval=>newPScm::SpecialForm::Eval(),056macro=>newPScm::SpecialForm::Macro(),057quote=>newPScm::SpecialForm::Quote(),058'set!'=>newPScm::SpecialForm::Set(),059begin=>newPScm::SpecialForm::Begin(),060define=>newPScm::SpecialForm::Define(),061'make-class'=>newPScm::SpecialForm::MakeClass(),062'call/cc'=>newPScm::SpecialForm::CallCC(),063print=>newPScm::SpecialForm::Print($outfh),064spawn=>newPScm::SpecialForm::Spawn(),065exit=>newPScm::SpecialForm::Exit(),066error=>newPScm::SpecialForm::Error(067$outfh,068bouncefrepl($initial_env,$reader,$outfh)g069),070amb=>newPScm::SpecialForm::Amb(),071'eq?'=>newPScm::Primitive::Eq(),072'>'=>newPScm::Primitive::Gt(),073'<'=>newPScm::Primitive::Lt(),074'>='=>newPScm::Primitive::Ge(),075'<='=>newPScm::Primitive::Le(),076and=>newPScm::SpecialForm::And(),077or=>newPScm::SpecialForm::Or(),078unify=>newPScm::SpecialForm::Unify(),079substitute=>newPScm::Primitive::Substitute(),080'new-env'=>newPScm::Primitive::NewEnv(),081instantiate=>newPScm::Primitive::Instantiate(), 328CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING082'pair?'=>newPScm::Primitive::TypeCheck::Pair(),083'var?'=>newPScm::Primitive::TypeCheck::Var(),084'number?'=>newPScm::Primitive::TypeCheck::Number(),085);086087$initial_env->Define(088PScm::Expr::Symbol->new("root"),089PScm::Class::Root->new($initial_env)090);091__PACKAGE__->new_thread(bouncefrepl($initial_env,$reader,$outfh)g);092trampoline();093g17.4LogicProgrammingYoumaywanttoreferbacktoSection17.1onpage299atthestartofthischapterwherewesawsomeexamplesoflogicpropgramminginaction.Howeverinordertoexplainhowthisallworks,let'smoveawayfromour rstexamplesandconsiderinsteadaverysimplelogicproblem:SocratesAllmenaremortal.Socratesisaman.Issocratesmortal?Whileamortalmanshouldhavenodicultyansweringyes"totheabovepuzzle,sqlqueriesmighthavesomediculty.Here'saformulationoftherulesinoursystem:(definethe-rules'(((mortalX)(manX))((mansocrates))))The rstruleonthelistshouldbereadas(mortalX)if(manX)thatis,XismortalifXisaman,"orcolloquiallyallmenaremortal".ThesecondruleisjustthebarefactSocratesisaman."Inresponsetoaquery(mortalsocrates)thesystemwillrespondinthearmative.Inresponsetoaquerylike(mortalaristotle)youwilljustgetError:nomoresolutions.Youalreadyknowwhatuni cationdoes,soyoushouldbeabletostarttoseewhatishappeninghere.Thesystemisgiventhequery((mortalsocrates))soitscansthroughthe-ruleslookingonlyattheheadofeachrule,tryingtounifyitwiththe rstterminthequery,(mortalsocrates).Itsucceedsinunifyingitwith(mortalX).Theresultofthatuni cationisanenvironmentwhereXisboundtosocrates.Inthecontextofthatenvironment,itdescendsintothebodyoftherule,attemptingtoproveeachcomponentofthebodyjustasifithadbeenenteredasadirectquery,butwiththevariableparts 17.4.LOGICPROGRAMMING329substitutedfortheirvalues.Inthiscasethatmeanstryingto ndarulethatmatches(manX)whereX=socrates.Thissucceedsmatchingthefact(mansocrates)andsotheentirequerysucceeds.WhataboutaskingWhoismortal?"Inresponsetothequery(mortalX)theheadofeachruleisagainscanned.ButIhaven'ttoldyouthefullstoryatthispoint.IfyourememberfromSection17.3.2onpage316wesaidtheremightbeproblemstryingtounifytwopatternswhichhappenedtocontainthesamevariablenames,andforthatreasonweimplementedinstantiatetoreplacethevariablesinapatternwithothersthatwereequivalent,butguaranteedtobeunique.Infactwhenthedatabaseofrulesisscanned,instantiateiscalledoneachrulebeforetheuni cationwiththeheadisattempted.Thishasnoe ectonour rstexamplequery(mortalsocrates)butitdoesmakeadi erencefor(mortalX),becausetheXappearsinboththequeryandtherule.Thankstoinstantiate,(mortalX)uni eswiththeheadofarulethatlookslike((mortalh0i)(manh0i)).SothevariableXuni eswiththevariableh0iratherthanitself,anditisinthisenvironmentthatthebodyoftherule(manh0i)isinvestigated.Thestatement(manh0i)succeedsinunifyingwith(mansocrates),andintheprocessh0iisboundtosocrates.Nowtheentirerulehassucceededandthequerysucceeds,resultinginanenvironmentwhereX=h0iandh0i=socrates.substituteisgiventheform(mortalX)andthatenvironment,andproducestheresult(mortalsocrates).Solet'sseehowtobuildthissystemfromourtoolkit"ofunify,substitute,instantiate,andnew-env.Beforedivingintothecode,hereisaslightlymoreformalde nitionofwhatweshallbedoing.Startwithanemptyenvironment.Foreachterminaqueryoftheform(hterm1i...htermni){ForeachruleInstantiateacopyoftheruleIfthetermuni eswiththeheadofthecopiedruleRecurseonthebodyoftheinstantiatedruleasanotherquery,withtheenvironmentaugmentedbyvariablesthatwereinstantiatedbyunifyingthetermwiththehead.Augmenttheenvironmentwithvariablesthatwereinstantiatedbysolvingtheterm.ElseFail(backtrack.){Fail(backtrack.)Soyoucanseethatthebodyofaruleistreatedexactlythesameasatop-levelquery,exceptthatthecurrentenvironmentmaycontainvariablesthatwereinstantiatedbyprioruni cations.Thereforeourimplementationcanrecurseonthebodyofarule,re-usingthecodethatweshallwritetoprocessatop-levelquery.Youshouldalsobeawarethattheenvironmentalwaysaccumulatesonitswaydownstreamtoasolution,onlybacktrackingcausesbindingsintheaccumulatingenvironmenttobediscardedonthewaybackupstream.Thisisthe rsttimewehavereallyusedthePSchemelanguagetoimplementanyseriouspieceoffunctionality.I'vechosentodoitthiswayfortworeasons.Firstlyitemphasisesaniceabstractionbarrier 330CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGbetweenourtoolkit"ofprimitivePerloperationsandthePSchemeglue"thatbindsthemintoalogicprogrammingsystem;secondlyandperhapsmoreimportantly,it'sactuallyeasiertodoinPSchemethanitwouldhavebeeninPerl,whichI'mhonestlyquitepleasedabout.Havingsaidallthat,thecodemaytakealittlemorestudyifyou'renotusedtoreadingSchemeprogramsyet,butitisblissfullyshortandsweet.Thetop-levelfunction,asyouhaveseenfromexamplesabove,iscalledprove,andhere'sitsde nition:(defineprove(lambda(goals)(substitutegoals(match-goalsgoals(new-env)))))proveisgivenalistofgoals(statementstobeproved.)Itcallsanotherfunctionmatch-goalspassingboththegoalsandanewemptyenvironment.Ifmatch-goalssucceeds,itwillreturnanenvironmentwithappropriatevariablesbound,andprovepassesthatenvironmentalongwiththeoriginalgoalstosubstitute,whichreplacesvariablesinthegoalswiththeirvaluesthenreturnstheresult.Ifmatch-goalsfails,controlwillbacktrackthroughproveandwewillseeError:nomoresolutions.Here'smatch-goals:(definematch-goals(lambda(goalsenv)(ifgoals(match-goals(cdrgoals)(match-goal(cargoals)env))env)))match-goalswalksitslistofgoals,callinganotherfunctionmatch-goaloneach,andcollectingtheresultingextendedenvironment.Iftherearenogoalslefttoprove,thenmatch-goalsucceedsandreturnstheenvironmentitwaspassed.Incidentallythismeansthatprovewithanemptylistofgoalswillalwayssucceedandreturnitsargumentenvironmentunchanged.Here'smatch-goal:(definematch-goal(lambda(goalenv)(match-goal-to-rulegoal(one-ofthe-rules)env)))Hereiswherewestarttoseeambcomingintoplay.match-goalusestheone-offunctionthatwede nedinSection16.1onpage249topickoneofthelistofrulestotrytomatchagainstthegoal.Itpassesthegoal,thechosenrule,andtheenvironmenttomatch-goal-to-rule,whichdoestheactualuni cationandrecursion.Here'smatch-goal-to-rule: 17.4.LOGICPROGRAMMING331(definematch-goal-to-rule(lambda(goalruleenv)(let*((instantiated-rule(instantiaterule))(head(carinstantiated-rule))(body(cdrinstantiated-rule))(extended-env(unify(substitutegoalenv)headenv)))(match-goalsbodyextended-env))))Ituseslet*to rstcreateaninstantiatedcopyoftherule,andthenextracttheheadandthebodyfromtheinstantiated-rule.Thenitcallsunifyonthe(substituted)goalandtheheadoftheinstantiatedrule.Ifunifysucceeds,thentheresultisanextendedenvironmentthatmatch-goal-to-ruleusestorecursivelycallmatch-goalsonthebodyoftherule.Thisisthepointofrecursiondiscussedabove,wherethebodyofaruleistreatedasanewquery.That'sallthereistoit!Ofcoursewhatisimplicitintheabovecodeisthebacktrackingthatbothunifyandambprovokeifauni cationfailsorthelistofrulestotryisexhausted.Thisismostapparentinmatch-goal-to-ruleabove:ifunifyfails,thencontrolsimplybacktracksoutofthefunc-tionatthatpoint.Likewiseinmatch-goal,whenone-ofrunsoutofoptions,controlbacktracksandmatch-goal-to-ruleisnevercalled.Thereareacoupleofre nementswecanmakehowver.Wewouldliketosupportrequireandisfromourexamplesatthestartofthechapter6.Additionally,itwouldbeusefulifwecouldcheckthattheresultreturnedbyprovedoesnotcontainanyunresolvedvariables.Ifitdoes,thisshouldbeconsideredafailure.Forthatreasonwemakeuseofthosetwotypecheckingfunctionspair?andvar?towritealittlerecursivetestcalledno-vars?:(defineno-vars?(lambda(expr)(if(pair?expr)(and(no-vars?(carexpr))(no-vars?(cdrexpr)))(not(var?expr)))))Wecanusethattowriteavariantofsubstititecalledsubstitute-allthat rstofallperformsthesubstitutionthenrequiresthattheresultcontainsnovars:(definesubstitute-all(lambda(exprenv)(let((subst-expr(substituteexprenv)))(begin(require(no-vars?subst-expr))subst-expr))))6(requrehexpressioni)failsifhexpressioniisfalse,and(hexpr1iishexpr2i)failsifhexpr1idoesnotequalhexpr2i,inbothcasesaftervariablesubstitutionandevaluation. 332CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGIftherequiresucceeds,thensubstitute-allreturnsthesubstitutedexpressionjustassubstitutedoes.Otherwisesubstitute-allbacktracks.Wecanusethisinsteadofsubstituteinthetop-levelprovefunction:(defineprove(lambda(goals)(substitute-allgoals(match-goalsgoals(new-env)))))Wewill ndotherusesforsubstitute-all.Nextup,rememberIsaidthatthesystemtreated(requirehexpri)and(hvariishexpri)spe-cially.Thisisnotdiculttoachieve.Theexistingmatch-goaljustcallsmatch-goal-to-rulewiththecurrentgoal,one-oftherules,andthecurrentenv.Allweneedtodoistoextendthisto rstlookoutforrequireandis.(definematch-goal(lambda(goalenv)(if(eq?(car(cdrgoal))'is)(match-isgoalenv)(if(eq?(cargoal)'require)(match-requiregoalenv)(match-goal-to-rulegoal(one-ofthe-rules)env)))))I'veaddedtwonewfunctionsmatch-isandmatch-requiretoactuallydealwiththosespecialrules.neitherofthemareparticularilycomplex.match-isextractsthevarandtheexpressionfromtherule,thenitcallssubstitute-allontheexpression.Itwillthereforefailandbacktrackiftheexpressioncontainsvariablesthathavenotbeenbound.Thismakessensebecausethenextthingitdoesistopasstheexpressiontoeval(de nedinSection9.2.3onpage117)anditmakesnosenseforevaltooperateonanexpressionwhichcontainsinstantiatedpatternvariables(h0i,h1ietc.)thatcannotpossiblyhavevaluesinthenormalpschemeenvironment.Ifitgetsthatfar,thenmatch-is nallyuni esthevartermwiththesubstitutedandevaluatedexpression,returningthenewenvironment:(definematch-is(lambda(goalenv)(let*((var(cargoal))(value(car(cdr(cdrgoal))))(svalue(substitute-allvalueenv)))(unifyvar(evalsvalue)env))))Noteparticularilythatis"isnotnecessariltyatest,itisauni cationthatwillfailifthelefthandexpressioncannotbeuni edwiththeright,soitcanbeconsideredbothanassertionandpotentiallyanassignment.Tobeclearmatch-isallowsustodealwithstatementslike: 17.5.MORELOGICPROGRAMMINGEXAMPLES333(Nis(+XY))providedXandYarebound.InthisexampleeitherNmustalreadyhaveanumericvalueequaltothesumofXandY,oritmustbeunbound,inwhichcaseitwillrecievethatvalue.match-requireisquitesimilartomatch-is.(definematch-require(lambda(goalenv)(let((sgoal(substitute-allgoalenv)))(begin(evalsgoal)env))))Ittoocallssubstitute-all,thistimeontheentireexpression,forthesamereasonsmatch-isdid.Thenitpassesthewholeexpressiontoeval.Iftherequireinthesubstitutedgoalfails,controlwillbacktrackfromthatpointasusual.Thatconcludesourimplementation.Let'stryitout!17.5MoreLogicProgrammingExamplesInthissectionwelookatapplyinglogicprogrammingtosomerealworld"problems,beginningwithparsing.17.5.1Parsing(again)WesawinSection16.2.4onpage260thatambbyitselfwasveryusefulforparsingbecauseofitsbuiltinbacktrackingcapability.Howevercombiningambwithuni cationaswehavedoneheremakesparsinganextraordinarilysimpletask,atleastifwearepreparedtoaccepttheaccompanyingineciencies.ConsiderthefollowingrulesforparsingasetofsentencesworkedthroughinthetestsfromChapter16onpage249,Listing16.7.3onpage290.Thisisadi erentsetofsentencestothoseworkedthroughinSection16.2.4onpage260,thesentencesinquestionare:Johnwillputhiscarinthegarage."Paulputacarinhisgarage."Paulhasputaveryveryoldcarinhisquitenewredgarage."Theseareobviouslymorecomplexthanthesentencesweworkedthroughwithamb,butwecandealwiththemeasilyenoughhere:(definethe-rules(list'((proper-noun(john)))'((proper-noun(paul)))'((noun(car)))'((noun(garage)))'((auxilliary(will))) 334CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING'((auxilliary(has)))'((verb(put)))'((article(the)))'((article(a)))'((article(his)))'((preposition(in)))'((preposition(to)))'((preposition(with)))'((degree(very)))'((degree(quite)))'((adjective(red)))'((adjective(green)))'((adjective(old)))'((adjective(new)))'((append()YY))'((append(A.X)Y(A.Z))(appendXYZ))'((sentenceS)(appendNPVPS)(noun-phraseNP)(verb-phraseVP))'((sentenceS)(append_XVPS)(appendNPAUX_X)(noun-phraseNP)(auxilliaryAUX)(verb-phraseVP))'((noun-phraseNP)(appendARTADJPNP)(articleART)(adj-phraseADJP))'((noun-phraseNP)(proper-nounNP))'((adj-phraseADJP)(nounADJP))'((adj-phraseADJP)(appendDGPADJP2ADJP)(degree-phraseDGP)(adj-phraseADJP2))'((degree-phraseDGP)(adjectiveDGP))'((deg-phraseDGP)(appendDEGDGP2DGP)(degreeDEG)(deg-phraseDGP2))'((verb-phraseVP)(append_XPPVP)(appendVBNP_X)(verbVB)(noun-phraseNP)(prep-phrasePP))'((prep-phrasePP)(appendPRNPPP) 17.5.MORELOGICPROGRAMMINGEXAMPLES335(prepositionPR)(noun-phraseNP))))Thisislittlemorethanadeclarationoftherulesofthegrammar.Let'slookatafewofthoserulesalittlemoreclosely.'((adj-phraseADJP)(nounADJP))'((adj-phraseADJP)(appendDGPADJP2ADJP)(degree-phraseDGP)(adj-phraseADJP2))Thisruleaboutadjectivalphrasesisintwoparts.The rstpartsaysthatADJPisanadj-phraseifADJPisanoun.ThesecondpartsaysADJPisanadj-phraseifsomeDGPandADJP2appendtoformADJP,andDGPisadegree-phrase,andADJP2isanadj-phrase.'((noun(car)))'((noun(garage)))Thispairofrulesde nesthenounsweknowabout.Theysay(car)isanounand(garage)isanoun.Thenounsthemselvesareinlistsbecausethesystemdealswithlists:considertheadj-phraserulesabove,whereADJPmustbealistforappendtoworkonit.Giventheabove,wecanasktheobviousquestion:>(prove'((sentence(johnputhiscarinthegarage))))((sentence(johnputhiscarinthegarage)))Yougetthedesiredresponse,eventually7.Interestingly,youcanalsoaskitwhatisasentence":>(prove'((sentenceS)))((sentence(johnputjohninthecar)))>?((sentence(johnputjohninthegarage)))>?((sentence(johnputjohnintheredcar)))>?((sentence(johnputjohnintheredgarage)))>?((sentence(johnputjohnintheredredcar)))>?((sentence(johnputjohnintheredredgarage)))>?((sentence(johnputjohnintheredredredcar)))Itgetsstuckinarecursiverutafterawhile,butit'sexcitingtoseethatithasthepotentialtogenerateallsentencesofagrammarifitcouldavoidthosetraps8.Howeverthat'snottherealproblem,thereal7ittookovertensecondsonmycomputer.8Onewaytoavoidrecursivetrapswouldbetorandomizetheorderinwhichone-ofreturnsitsvalues.Unfortunatelythatwillcauseotherproblemsingeneral. 336CHAPTER17.UNIFICATIONANDLOGICPROGRAMMINGproblemisthatitishorriblyinecient.Allofthosecallstoappend,mostofwhichproduceuselessresults,consumehugeamountsofresources.HoweverwecantakeahintfromthewaythatweimplementedparsingwithambinSection16.2.4onpage260.Inthatimplementation,theroutineparse-wordremovedtokensfromthefrontofthe*unparsed*global,e ectivelydirecting"theprogressoftheparse,sinceitexposedthenexttokenandonlycertainwordswouldmatchthatnewlyexposedtoken.Wecan'tuseglobalvariables"inalogicprogrammingsystem|theconcepthasnomeaning|butwecannonethelesskeeptrackofwhathasbeenparsedsofar.Wecandothisbymakingallofourgrammarrulestakeasecondargument.Forexample:(noun-phraseSR)WillsucceedifthereisanounphraseatthestartofS,andifRistheremainderofSafterremovingthatnounphrase.Perhapsthisapproachismosteasilydemonstratedforindividualwords.Here'sthenewde nitionofnoun:'((noun(car.S)S))'((noun(garage.S)S))Itwillmatchifits rstargumentisaliststartingwithcarorgarage,anintheprocessinstantiateitssecondargumenttotheremainderofthelist.Forexample:>(prove'((noun(garageisred)X)))((noun(garageisred)(isred)))Ifwewritetheotherrulesforsinglewordssimilarily,wecanstarttobuildupmorecomplexrulesontop:'((noun-phraseSX)(articleSS1)(adj-phraseS1X))ThissaysthatthereisanounphraseatthestartofS,leavingXremainingifthereisanarticleatthestartofSleavingS1remaining,andanadjectivalphraseatthestartofS1,leavingXremaining.Notethatwenolongerneedtouseappendtogenerateandtest".Wecanfurtherbuildontheseintermediaterulesjustaswiththepreviousgrammar:'((sentenceSX)(noun-phraseSS1)(verb-phraseS1X))Inordertousethisnewparser,wemustremembertopassanemptylistasthesecondargumenttosentence,toensurethatallofthetokensinthe rstargumentareconsumed:>(prove'((sentence(johnputhiscarinthegarage)())))((sentence(johnputhiscarinthegarage)()))Puttingallofthistogether,wecanseetheconsiderablyfaster(butuglier)versioninthetestattheendofListing17.8.2onpage342.Uglinessisnotjustanaestheticthing,itgetsinthewayofclearandreadablecode.ForthatreasonanyfullPrologimplementationprovidesrewritingrulesthatwillacceptasimplegrammarwithstatementslike9:9WhenIsaylike",I'mnotsuggestingthatProloglooksexactlylikethis,it'sactualsyntaxissomewhatdi erent.I'monlysayingtheseareconceptuallyalike. 17.5.MORELOGICPROGRAMMINGEXAMPLES337(sentence-->noun-phraseverb-phrase)Itwilltransformthemintotheinternalform:((sentenceX0X1)(noun-phraseX0X2)(verb-phraseX2X1))asitreadsthemin.I'mnotgoingtodothat,sinceitwouldrequiregeneratingsymbolsetc.butitshouldbeobviousthatthissortoftransformationisnotdicult.17.5.2SimplifyingAlgebraicExpressionsIfyourememberbackinSection17.1.5onpage304Theresultofdi erentiatingtheexpression(((x^2)+x)+1)was(((2*(x^1))+1)+0).Whilecorrect,thisissomewhatunwieldyasitcontainsredundantoperationssuchastheadditionofzero.Itisrelativelyeasytowriteasetofrulesthatcanautomaticallysimplifysuchexpressions,howeverwehavetobecarefuloftheorderinwhichwespecifytherules.Thisisnotonlyafeatureofourimplementation,theorderoftherules,whichistheorderinwhichtheywillbesearched,issigni cantinPrologtoo.Westarto bysayingthatwecannotsimplifyanythingunlessithassomeinternalstructure:'((simplifyEE)(require(not(pair?'E))))SoifEisnotapair,thenitwillsimplifytoitself.Wenextgettothemaindriverruleforsimpli cation:'((simplify(XOPY)E)(simplifyXX1)(simplifyYY1)(s(X1OPY1)E))Thissaysthatwecansimplifyanexpressionoftheform(XOPY)toEif1.wecansimplifyXtoX1;2.wecansimplifyYtoY1;3.wecansimplify(X1OPY1)toEusingtheauxilliarysimpli cationrules(s(hE1ihOPihE2i)hE3i).Theseauxilliarysimpli cationrulesoccupytherestofthede nitionofsimpli cation.Firstlyherearetherulesforaddition:'((s(X+0)X))'((s(0+X)X))'((s(X+Y)Z)(require(and(number?'X)(number?'Y)))(Zis(+XY)))'((s(X+Y)(X+Y)))The rstrulesaysthatXpluszeroisjustX.Thesecondruleisanecessaryrepetitionofthe rst,reversing0andX.Thethirdrulesaysthatwecansimplify(X+Y)toZifXandYarebothnumbers,bymakingZequaltotheirsum.Thelastruleisthereincasethetermcannotbesimpli ed,inwhichcasetheresultisjusttheoriginal.Therulesforsimplifyingmultiplicationareverysimilar,exceptthatwecansimplifybothmultipli-cationbyzeroandmultiplicationbyone: 338CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING'((s(X*0)0))'((s(0*X)0))'((s(X*1)X))'((s(1*X)X))'((s(X*Y)Z)(require(and(number?'X)(number?'Y)))(Zis(*XY)))'((s(X*Y)(X*Y)))Likewiseforexponentiation,exceptthatwedon'thavea^"operatorinPSchemesowecan'tperformanyactualnumericcomputationinthiscase:'((s(X^0)1))'((s(X^1)X))'((s(X^Y)(X^Y)))))Withtheserulesinplacewecanaskthesystemtosimplifythatunwieldyexpressionwesawbefore:>(prove'((simplify(((2*(x^1))+1)+0)X)))((simplify(((2*(x^1))+1)+0)((2*x)+1)))Notethattheserulesforsimpli cationareveryincomplete,mostnoticeablytheydonotdealwithsubtractionordivision.Howevertheyaresucienttodemonstratethattheyworkandyoucanaddtheextrarulesyourselfifyouwanttoextendorexperiment(thisexampleispartofthetestsinListing17.8.2onpage342.)17.6Summary,ShortcomingsandShort-cutsWhilewe'vespentsometimeexploringthepotentialoflogicprogramming,itwouldbewrongofmetosuggestthatPSchemeo ersanythinglikethepowerofarealPrologsystem.Infactitismissingsomefairlyfundamentalfeatures,anddi ersfromrealPrologquitesigni cantly.Ifyou'reexcitedbywhatyou'veseenhereI'dsuggestyougetacquaintedwitharealPrologsystem:I'vebarelyscratchedthesurface.YoumaybesurprisedtoknowthatPrologitselfiscommonlyusedtobuildcompilersforevenhigher-levellanguagesand,muchlikeScheme,itisrelativelyeasytode neameta-circularevaluatorforProloginProlog.I'mnotgoingtogoanywherenearthat,thisisgettingsillyenoughasitis.Rather,InthissectionI'dliketodiscusstheshortcomingsofthisimplementationbycomparingitwitharealProlog.17.6.1FunctorsandArityInProlog,factsandrulesarenotde nedassimplelists.Prologdistinguishesbetweenthefunctorofaruleanditsarguments.ForexamplethePSchemefact":((marylikescheese))wouldbewritteninPrologas: 17.6.SUMMARY,SHORTCOMINGSANDSHORT-CUTS339likes(mary,cheese).andtherule:((likesmaryX)(personX)(Xlikeschips))wouldbewrittenas:likes(mary,X):-person(X),likes(X,chips).Inthisexamplelikesiscalledthefunctoroftherule.Becauseittakestwoarguments,itissaidtohaveanarityof2,andisclassi edaslikes/2.Thisisdistinctfromanylikesfunctorwithadi erentnumberofarguments,Justasinourimplementationlistsofdi erentlengthcannotmatch.Thebigadvantageindistinguishingfunctor/aritylikethisisthatPrologcanindexitsdatabaseonthisbasis.Uni cationisexpensive,sowhensearchingforaruletomatchi.e.likes(mary,X)Prologneedonlyinspectrulesthatarelikes/2.Ofcoursethismeansthatthefunctorcannotbematchedbyapatternvariable.Theexpression:X(mary,wine)isnotvalidProlog.HoweverProloghasmechanismstoextractthefunctorfromatermasavariable,andtocallanexpressionconstructedwithavariablefunctor,sothisapparentlimitationcanbeworkedaround.Prologalsorecognisesoperatorsandoperatorprecedence,suchthatthenormalmathematicalopera-torsarein xandareparsedwiththecorrectprecedenceandassociativity.Prologoperatorsarefunctors,soforexampletheform:a+b*cisequivalentto:+(a,*(b,c))Prologalsoallowsyoutode nenewoperatorsasanysequenceofnon-alphanumericcharacters,andassignthemaprecedenceandassociativity.Theseuserde nedoperatorsdon'tactuallydoanything:theyjustmakeiteasiertowritePrologtermsastheytranslateinternallytonormalfunctorsjustasthebuilt-inoperatorsdo.17.6.2TheCutAnotherreasonthatPrologdistinguishesitsfunctorsistoallowshortcircuitingofthesearchspace.Prologprovidesaspecialrulecalledthecuttoaccomplishthis[3,pp69{92].Thecutiswrittenwithasingleexclaimationmark!".Aspartofthebodyofarule,thecutalwayssucceeds.Butifitisbacktrackedthrough,itbacktracksallthewaypastthepointwheretheheadoftherulewasuni ed,andpastthepointwhererulesofthatfunctor/aritywereconsidered,backtothedecisionpointbeforethat.Thisisextremelyusefulbehaviour.Forexample,returningtooursimplifyexampleofalgebraicsimpli cationfromSection17.5.2onpage337,ourvery rstrulewas: 340CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING'((simplifyEE)(require(not(pair?'E))))ThiswassayingthatyoucansimplifyEtoEifEisnotapair(isatomic.)WemighttranslatethisintoPrologas:simplify(E,E):-atomic(E).HoweverthisisbetterexpressedinPrologas:simplify(E,E):-atomic(E),!.Thissaysthatifthisrulesucceeds,thennoothersimplify/2rulesshouldbeconsideredifbacktrackingoccursthroughthecut.IftheexpressionEisatomic,thenitcannotbesimpli ed,endofstory.Thereareotherusesofthecut,buttheyarebestdescribedinabookonProlog.Inordertoproperlyimplementthecut,wewouldhavetopassathirdcutfgcontinuationaround,whichmakestheParameterObjectpatterndiscussedinSection16.5.1onpage285evenmoreattractive.17.6.3BacktrackingEciencyAsI'vealreadymentionedinSection16.5onpage285,thechronologicalbacktrackingexhibitedbyambishopelesslyinecient,andanalternativedependancy-directedbacktrackingispreferable.Thiskindofbacktrackingexaminesthecauseofanyfailure,andbacktracksimmediatelytothepointofexecutionthatmostrecentlya ectedthefailure.Dependancydirectedbacktrackingisoftenimplementedbyaconstraintnetwork,whichallowsthebacktrackingtoproceeddirectlytothelastplacethatthevalueinquestionwasaltered.Againthisisbeyondthescopeofthisbook10.17.7TestsThe rstsetoftestsinListing17.8.1onthenextpageexercisetheindividualadditionstothisversionoftheinterpreter.The rsttestprovesthatunifyelicitsbacktrackingonfailure.ThesecondtestprovesthatunifyreturnsaPScm::Envonsuccess.thethirdtestdemonstratesthatunifyplussubstitutecanresolvethevariabletermsinanexamplethatwe'veseenbefore.Thelasttestinthis leshowsinstantiateinaction.Althoughthedigitsintheresultlooklikenumbers,theyareactuallyPScm::Expr::VarsThesecondsetoftestsinListing17.8.2onpage342triesoutourlogicprogrammingsystem.Itworksthroughprettymuchtheexampleswe'vealreadycoveredinthischapter.10i.e.Idon'tknowhowtomakeitworkyet. 17.8.LISTINGS34117.8Listings17.8.1t/PScmUnify.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>5;006007BEGINfuseok('PScm')g008009evalok(<<'EOT',<<'EOR','simpleunify');010(unify'(abc)'(abd))011EOT012Error:nomoresolutions013EOR014015evalok(<<'EOT',<<'EOR','simpleunify2');016(unify'(abc)'(abc))017EOT018PScm::Env019EOR020021evalok(<<'EOT',<<'EOR','unifyandsubstitute');022(substitute023'((aA)(bB))024(unify'(f(gA)A)025'(fBabc)))026EOT027((aabc)(b(gabc)))028EOR029030evalok(<<'EOT',<<'EOR','instantiate');031(instantiate'((f(gA)A)(fBabc)))032EOT033((f(g0)0)(f1abc))034EOR035036#vim:ft=perl 342CHAPTER17.UNIFICATIONANDLOGICPROGRAMMING17.8.2t/AMBUnify.t001usestrict;002usewarnings;003useTest::More;004uselib'./t/lib';005usePScm::Testtests=>13;006007BEGINfuseok('PScm')g008009my$prereqs=<,279B,176>=,279B(),159,173{176,179,180,183apply(),27,28,32,54,55,66,67,88,96,99,backtracking,266108,147,195,197,198,208,214,281,282,backtracks,249323{326Backus-NaurFormat,7callmethod(),150begin,126checktype(),28,89bindings,12compare(),282body,300evalvalues(),77,202,203Bounce(),188{190,218,231,267,271lookupmethod(),145,146,150,151Bouncer,218,219lookupmethodhere(),145,146lookupmethodinsuper(),145,146C(),173{175,179,180,183lookupref(),75,278cA(),183mapbindings(),203call-with-current-continuation,198nexttoken(),18{21,103,318,319call/cc,198populatebindings(),100,101callmethod(),142,145,149{151,214,215populatemethodshash(),141CallMethodOrDie(),215,244read(),190,191,277car,88symbol(),68,109cB(),183cdr,88A(),159,174,176,179,180,183chainofcontinuations,220Ah(),176checkkeyseq(),310accumulator,162checktype(),240alternative,9Class::Apply(),145,213,243amb,249Class::makeinstance(),145Amb::Apply(),272closure,13,59,61and,279Closure::apply(),108,198,214anonymousvariable,313Closure::asstring(),109Apply(),109,220,317Closure::Function::Apply(),99,108,197applynext(),211,279,280Closure::Function::new(),64ApplyMethod(),145,147,150,151,214Closure::Macro::symbol(),109arity,339Closure::Macro::Apply(),208arrays(),308,309,320Closure::Method::ApplyMethod(),214asstring(),30Cons(),92,94,96,102,114,194,209,210354 INDEX355cons,89Expr::List::first(),88conscells,89Expr::List::Pair::Eq(),283consequent,9Expr::List::Pair::Instantiate(),325constraintnetwork,340Expr::List::Pair::mapeval(),206,268Cont(),187,188,190,219,220,232,267,270Expr::List::Pair::UnifyType(),321continuation,166Expr::List::value(),94continuationpassingstyle,160Expr::mapeval(),206Continuation::Cont(),192Expr::Quote(),209Expr::quoterest(),210D(),174,175Expr::String::asstring(),239Define(),131,132,150,273Expr::Symbol::Eval(),191,241define,10expression,7,124de nition,124Extend(),51{53,56,77,99{101,268dependancy-directedbacktracking,285,340Extend*(),97,99displaystring(),239ExtendIteratively(),83,101,204,205doerror(),239,240ExtendRecursively(),77,101dotnotation,89ExtendUnevaluated(),67,99,100,145,242Env::populatebindings(),242factorial(),161,162,166{168,170{172,177,178,Env::Apply(),214186,192,194Env::Assign(),211,241factorialhelper(),162,172Env::callmethod(),214Fail(),267,271Env::CallMethodOrDie(),215,244fail(),310Env::Define(),212File::Find,224Env::Extend(),53,67,196{198first(),26,89,94,95,99,209,283,321,325Env::ExtendIteratively(),204 rstclassobjects,185Env::ExtendRecursively(),202,204function,59,61Env::ExtendUnevaluated(),202functor,338,339Env::LookUp(),50Env::LookUpHere(),273getmethod(),146,147,151Env::new(),51globalvariables,10Env::ResolveAll(),321hash(),308Env::Super::Apply(),215hashes(),308,310Environment,15head,300Eq(),283,320hygenicmacros,112eq?,253,279Error(),239{241,245,266,275,277if,9error,237implicitlytypedlanguage,316escapeprocedures,220init,136Eval(),54,190,219,268,275Instantiate(),325Eval,15instantiate,324eval,117IO::String,33evalok(),33isanon(),319,320Evaluator,15isclosetoken(),20,21exit,232isdottoken(),103Expr::List::Cons(),101,214isexpr(),116Expr::List::Eval(),26,192,218isnumber(),326 356INDEXisopentoken(),20,21number?,326ispair(),95,326isquotetoken(),115,116operatorprecedence,9,339isretry(),277,278operators,339isunquote(),114or,279isunquotetoken(),115,116isvar(),319,326pair?,326isTrue(),29,94,196,197pairs,89iterator,224ParameterObject,285pattern,306Keys(),322patternmatching,299lambda,11patternvariables,300,306lambdacalculus,12polymorphism,15let,12pop(),50let*,82PrimitiveOperations,15LetRec,76Primitive::Apply(),96,193,240letrec,74Print(),16,29,32,190,215,276,277Like::This,5Print,15Lisp,4print,215list,7PrintSystem,15list,88Prolog,160ListEvalRestCont,219PScheme,7ListFirstEvalCont,219PScm,15,21,27,31,32,68,188,189,231,239LookUp(),17,26,49,51,75,76,191,241PScm::Class,140{146,150,151,242lookupmethod(),140PScm::Class::Root,144,150,243LookUpHere(),278PScm::Closure,64,66,68,69,108,146,147LookUpNoError(),275,278PScm::Closure::Function,64,68,108,109,147,193,197macro,108PScm::Closure::Macro,108,109,208make-class,135PScm::Closure::Method,146,150,151makeinstance(),142{144,150,214,242,243PScm::Continuation,188,200,221,266,267,mapeval(),95,96,99,100,147,194,195,197,198,270200,203,206,211,214,318PScm::Continuation::Bounce,267,270,271match(),308{310,313{315PScm::Continuation::Cont,267,270matcharrays(),309,310PScm::Continuation::Fail,267,270matchhashes(),310PScm::Env,16,26,51,56,75,77,83,99,100,131,matchstrings(),310140,142,143,145,146,149{151,214,241,matchvar(),309,314273,275,278,317,322,340Math::BigInt,23,24,28PScm::Env::Super,143,144,146,148,150,151,new(),23214new(),16,18,21,23,24,51,52,92,94,95,141,PScm::Expr,22,23,25,30,32144,147,150,188,216,218,219,325PScm::Expr,19{22,24,25,27{31,54,94,95,113,new-env,324114,116,191,193,196,206,239,268,277,newthread(),231,232,234,277,283278,282,283,318{320,322,323,325,326NullObjectPattern,95PScm::Expr::Atom,22{24,30{32,283number,7PScm::Expr::FileHandle,215 INDEX357PScm::Expr::List,20,22,24,26,27,30{32,54,PScm::SpecialForm::LetStar,82,9868,88,91{94,96,99,114,116,206,211,PScm::SpecialForm::Macro,108214,323PScm::SpecialForm::MakeClass,140,150PScm::Expr::List::Null,92,94,95,114,194,PScm::SpecialForm::Or,281206,283PScm::SpecialForm::Print,215,216PScm::Expr::List::Pair,92{94,113,206,209,PScm::SpecialForm::Quote,87,99,208320,323,326PScm::SpecialForm::Sequence,279PScm::Expr::Literal,23PScm::SpecialForm::Set,125PScm::Expr::Null,206PScm::SpecialForm::Spawn,232PScm::Expr::Number,19,23{25,27,28,31PScm::SpecialForm::Unify,317PScm::Expr::String,19,23,25,31,32,239PScm::Test,33PScm::Expr::Symbol,19,23,25,31,54,114,PScm::Token,19{21,103,115,116191,277,278,318,319PScm::Token::Close,19,21PScm::Expr::Var,318{320,323,325,340PScm::Token::Dot,103PScm::Primitive,27,28,32,54,88,96,193,281PScm::Token::Open,19,21PScm::Primitive::Car,88PScm::Token::Quote,115,116PScm::Primitive::Cdr,89PScm::Token::Unquote,115,116PScm::Primitive::Compare,281pythagoreantriples,258PScm::Primitive::Cons,96Quote(),113,114,209,210PScm::Primitive::Eq,282quote,87PScm::Primitive::Ge,282quoterest(),114,209,210PScm::Primitive::Gt,282PScm::Primitive::Le,282Read(),18{20,22,31,101,190,191,251,266,276{PScm::Primitive::List,88278,318PScm::Primitive::Lt,282readevalprintloop,15PScm::Primitive::Multiply,26,27,195Read::nexttoken(),114PScm::Primitive::Substitute,321Read::Read(),116,187PScm::Primitive::Subtract,26,28readlist(),102PScm::Primitive::TypeCheck,326readlistelement(),102PScm::Primitive::TypeCheck::Number,326Reader,15PScm::Primitive::TypeCheck::Pair,326ReadEvalPrint(),16,25,29,33,53,63,78,104,PScm::Primitive::TypeCheck::Var,326118,127,132,149,190,215,275,283PScm::Read,16,18,31,190,318repl,15PScm::SpecialForm,32,63,76,87,108,125,126,repl(),275131repl(),189,190,238,275{277,283PScm::SpecialForm::And,280Replace(),325PScm::SpecialForm::Begin,126,279Resolve(),322,323PScm::SpecialForm::De ne,131resolve(),315{317,322PScm::SpecialForm::Error,238,239ResolveAll(),321{323PScm::SpecialForm::Eval,117ResolveTerm(),322,323PScm::SpecialForm::If,26,29,98,193rest(),26,88,89,94,95,99,209,283,321,325PScm::SpecialForm::Lambda,63,64,108,193root,136PSCm::SpecialForm::Lambda::Apply(),208rule,300PScm::SpecialForm::Let,52,76,77,82,97,193,195Scheme,4PScm::SpecialForm::LetRec,77,98sequence,126 358INDEXsequences,124uni cation,285,299sidee ects,123,124Unify(),317{321,325signi cantexpressions,190unify(),313{318,320simpleexpression,190unify,316spawn,231unifyarrays(),314,315,321specialform,10unifyhashes(),314SpecialForm::Amb::Apply(),271unifystrings(),314,315SpecialForm::Begin::Apply(),279unifyvar(),314,315,318,319SpecialForm::Define::Apply(),212,273,275UnifyType(),320SpecialForm::If::Apply(),29,55,196,197UnPack(),76,77,97,98SpecialForm::Lambda::Apply(),99,197unpackbindings(),97SpecialForm::Let::Apply(),55,76,97,268unquote,112SpecialForm::Let::UnPack(),76UnSet(),273,278SpecialForm::LetStar::Apply(),204SpecialForm::Macro::Apply(),208value(),23SpecialForm::MakeClass::Apply(),213value(),17,23,24,26,27,29,30,94{97,99,114,SpecialForm::Primitive::Apply(),195277,278SpecialForm::Print::Apply(),217var(),308,309,314SpecialForm::Quote::Apply(),113,206,208var?,326SpecialForm::Set::Apply(),241,274variableassignment,124statement,124X(),180,183string,7strings(),92,94,95,308,310Y(),180Structure,15structure,306Z(),180,183Substitute(),321,323,325substitute,317super,137symbol,7tailcall,162TailCallElimination,163tailcalloptimization,160tailposition,162tailrecursion,160tailrecursive,162tail-calloptimization,162Term::ReadLine::Gnu,33test,9test(),326thecut,339this(),5this,137times(),168top(),117,118trampoline,177trampoline(),188,189,231,234

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
大家都在看
近期热门
关闭