le langage caml

387
 Pierre Weis Xavier Leroy LE LANGAGE CAML Deuxi` eme ´ edition

Upload: romain-rousseau

Post on 09-Jul-2015

149 views

Category:

Documents


0 download

TRANSCRIPT

PierreWeisXavierLeroyLELANGAGECAMLDeuxi`eme editionCopyright1992,1993,2009PierreWeisetXavierLeroy.CetexteestdistribuesouslestermesdelalicenceCreativeCommonsBY-NC-SA.Letextecompletdelalicenceestdisponible`aladressesuivante :http://creativecommons.org/licenses/by-nc-sa/2.0/fr/legalcodeVoiciunresumedesdroitsetconditionsdecettelicence. Vous eteslibres :dereproduire,distribueretcommuniquercettecreationaupublicdemodiercettecreation Selonlesconditionssuivantes :Paternite. Vous devez citer le nom de lauteur original de la mani`ere indiqueepar lauteur de loeuvre oule titulaire des droits qui vous conf`ere cetteautorisation (mais pas dune mani`ere qui suggererait quils vous soutiennentouapprouventvotreutilisationdeloeuvre).Pas dUtilisation Commerciale. Vous navez pas le droit dutiliser cettecreation`adesnscommerciales.Partage des Conditions Initiales `a lIdentique. Si vous modiez, transformezouadaptezcettecreation,vousnavezledroitdedistribuerlacreationquienresultequesousuncontratidentique`acelui-ci. Achaque reutilisationoudistributionde cette creation, vous devez faire ap-paratre clairement au public les conditions contractuelles de sa mise `a disposition.Lameilleuremani`eredelesindiquerestunlienlapageWebci-dessus. Chacunedecesconditionspeutetreleveesivousobtenezlautorisationdutitu-lairedesdroitssurcetteoeuvre. Riendanscecontratnediminueounerestreintledroitmoraldelauteuroudesauteurs.`Amesparents,`ASuzanneetMichel,`ALise,Marie,Jean-BaptisteetIr`ene,`AHel`ene.PierreWeisTabledesmati`eresAvant-propos xiI ProgrammerenCaml 1Avertissement 31 Premierspas 51.1 IdeesgeneralessurCaml 51.2 DialogueravecCaml 61.3 Lesdenitions 61.4 Fonctions 81.5 Valeursetprogrammes 131.6 Impression 131.7 Conventionssyntaxiques 151.8 Diagrammessyntaxiques 172 Recursivite 192.1 Fonctionsrecursivessimples 192.2 Denitionsparcas :leltrage 272.3 LestoursdeHanoi 282.4 Notionsdecomplexite 313 Programmationimperative 373.1 Laprogrammationimperative 373.2 Boucles 393.3 Manipulationdepolyn omes 403.4 Impressiondespolyn omes 423.5 Caract`eresetchanesdecaract`eres 463.6 Lesreferences 473.7 Unprogrammeutilisantdesreferences 493.8 Recursiviteetboucles 503.9 R`egledextensionnalite 523.10 Eetset evaluation 534 Fonctionnellesetpolymorphisme 574.1 Notiondepolymorphisme 574.2 Fonctionsdordresuperieur 594.3 Typageetpolymorphisme 614.4 Currycation 64viii Tabledesmati`eres4.5 Unefonctionnelledetripolymorphe 654.6 Lapleinefonctionnalite 674.7 Compositiondefonctions 705 Listes 755.1 Presentation 755.2 Programmationassisteeparltrage 775.3 Triparinsertion 785.4 Fonctionnellessimplessurleslistes 815.5 Lespolyn omescreux 835.6 Filtrageexplicite 845.7 Operationssurlespolyn omescreux 855.8 AnimationdestoursdeHanoi 885.9 Fonctionnellescomplexessurleslistes 915.10 Ecacitedesfonctionssurleslistes : etudedecas 985.11 Listesetrecurrence 1035.12`Alarecherchedeliterateurunique 1056 Lesstructuresdedonnees 1096.1 Polyn omespleinsetpolyn omescreux 1096.2 Typessommes elabores 1136.3 Lestypessomme 1166.4 Lestypesproduit 1166.5 Melangedetypessommeettypesproduit 1186.6 Structuresdedonneesmutables 1186.7 Structuresdedonneesetltrage 1206.8 Structuresdedonneesetrecurrence 1227 Ledocteur 1257.1 Vuedensemble 1257.2 Lesexceptions 1267.3 Fonctionsderecherchedansleslistes 1307.4 Traitementsdechanesdecaract`eres 1337.5 Camelia 1357.6 Dialogueaveclutilisateur 1407.7 Exempledesession 1437.8 Pourallerplusloin 1448 Graphisme 1478.1 Fractales 1478.2 LegraphismedeCaml 1488.3 Lesnombresenrepresentationottante 1498.4 Lecrayon electronique 1498.5 Premiersdessins 1528.6 LeocondevonKoch 1549 Syntaxeabstraite, syntaxeconcr`ete 1559.1 Presentation 1559.2 Leretard`alevaluation 156ix9.3 Levaluationdesordresdulangagegraphique 1589.4 Syntaxeetsemantique 1599.5 Notionsdanalysessyntaxiqueetlexicale 1609.6 Analyselexicaleetsyntaxique 1619.7 Ajoutdesprocedures 16810Programmesindependantsetmodules 17910.1 Chargementdechiers 17910.2 Programmesindependants 18010.3 Entrees-sortiesdebase 18110.4 Programmesenplusieursmodules 18310.5 Interfacesdemodules 18710.6 Compilationsinteractives 19011Interfacesgraphiques 19311.1 Structureduneinterfacegraphique 19311.2 Relierdescomposantsentreeux 19411.3 Unconvertisseurdedevises 19611.4 Lejeudutaquin 19911.5 Pourallerplusloin 201II Exemplescomplets 203Avertissement 20512Demonstrationdepropositions 20712.1 Lalogiquemathematique 20712.2 Calculsdetablesdeverite 21012.3 Leprincipedesdemonstrations 21212.4 Representationetvericationdespropositions 21312.5 Syntaxeconcr`etedespropositions 21712.6 Levericateurdetautologies 22112.7 Exemplesdetheor`emes 22312.8 Pourallerplusloin:lanalyseurlexicaluniversel 22812.9 Pourallerencoreplusloin:lehachage 23213Compressiondechiers 23713.1 Lacompressiondedonnees 23713.2 Planduprogramme 23813.3 LalgorithmedeHuman 24013.4 Annexes 24713.5 Miseenpratique 25213.6 Pourallerplusloin 25214Simulationdunprocesseur 25514.1 Lepico-processeur 25514.2 Lesimulateur 26014.3 Lassembleur 26714.4 Pourallerplusloin 275x Tabledesmati`eres15Compilationdemini-Pascal 27715.1 Syntaxeabstraite,syntaxeconcr`ete 27715.2 Typage 28315.3 Compilation 28915.4 Pourallerplusloin 30416Recherchedemotifsdansuntexte 30516.1 Lesmotifs 30516.2 Syntaxeabstraiteetsyntaxeconcr`etedesmotifs 30616.3 Lesautomates 30916.4 Desexpressionsrationnellesauxautomates 31016.5 Determinisationdelautomate 31316.6 Realisationdelacommandegrep 31916.7 Annexe 32016.8 Miseenpratique 32116.9 Pourallerplusloin 321III Introspection 32317Executiondunlangagefonctionnel 32517.1 Lelangagemini-Caml 32517.2 Levaluateur 32617.3 Laboucledinteraction 33117.4 Miseenuvre 33317.5 Pourallerplusloin 33417.6 Annexe 33618Unsynthetiseurdetypes 33918.1 Principesdelasynth`esedetypes 33918.2 Lalgorithmedesynth`esedetypes 34418.3 Representationdestypes 34818.4 Lunication 35318.5 Inconnues,generalisationetspecialisation 35618.6 Impressiondestypes 35718.7 Laboucledinteraction 35818.8 Miseenuvre 35918.9 Pourallerplusloin 36019Enguisedeconclusion 36519.1 Unemethodologiedeprogrammation 36519.2 LacompilationdeCaml 367Index 373Avant-proposOnprononceCaml avecle ca decafeetle mel demelba.aml est un langage de programmation de conception recente qui reussit `a etre `ala fois tr`es puissant et cependant simple `a comprendre. Issu dune longue reexionsurleslangagesdeprogrammation, Caml sorganiseautourdunpetitnombrede notions de base, chacune facile `acomprendre, et dont lacombinaisonse rev`eleextremement feconde. Lasimpliciteet larigueur deCaml lui valent unepopularitegrandissantedanslenseignementdelinformatique,enparticuliercommepremierlan-gagedansdescoursdinitiation`alaprogrammation. Sonexpressiviteetsapuissanceenfontunlangagedechoixdansleslaboratoiresderecherche,o` uilaeteutilisepourtraiterdesprobl`emesparmi lesplusardusdelinformatique : demonstrationassisteepar ordinateur, analyses automatique de programmes, syst`emes de reecriture, compila-tionetmetacompilation.Enbref,Camlestunlangagefacileaveclequelonresoutdesprobl`emesdiciles.Longtemps reserve `ade grosses machines co uteuses, le langage Caml est main-tenant disponible gratuitement sur toute une gamme de machines, du micro-ordinateurpersonnel (PC, Macintosh, . . . ) auxstations detravail lesplus puissantes, cequi lerendaccessible`aunvastepublic, delamateurcurieuxauprofessionnel chevronneenpassant par letudiant informaticien.`Ace vaste public, Caml apporte une nouvelleapprochedelaprogrammation, desplusfructueuses. LinvestissementquevousferezenapprenantCaml neserapasvain: vousconstaterezquelelangagevousouvredeshorizons nouveaux et quil est assez puissant pour que vous y exprimiez simplement desideescomplexes.Cequisecon coitbiensenonceclairementetlesprogrammespourledirevousviennentaisementenCaml.CelivreseproposedoncdefairedecouvrirCaml`atousceuxqui sinteressent` alaprogrammation. Nous nous sommes eorces decrire un livre accessible `a tout honnetehomme , mais qui permette cependant de matriser le langage et den saisir les beautes.Pour ce faire, nous avons combine une introduction progressive aux principaux traits dulangageavecunveritable coursdeprogrammation,illustredetr`esnombreuxexemplesdeprogrammes qui vous permettront desaisir comment onutiliseCaml et devousapproprier petit `a petit ce merveilleux outil. Les exemples vont jusquau developpementde programmes complets et dune longueur respectable. Nous nous eor cons de justierces exemples, en les repla cant dans leur contexte et en analysant la clarte et lecacitexii Avant-proposdessolutionsproposees.Cetouvragesorganisecommesuit : La partie I, Programmer enCaml , introduit progressivement les traits dulangageetlesmethodesessentiellesdeprogrammationenCaml. La partie II, Exemples complets , montre comment resoudre en Caml un certainnombredeprobl`emesrealistesdeprogrammation. LapartieIII, Introspection , ebaucheuneimplementationdeCaml enCaml,expliquantainsiletypageetlevaluationdeCaml.Encomplementdecelivre, lesauteursontecritunsecondouvrage, intituleManuelde reference dulangage Caml et publiepar lememeediteur, contenant tout cequiestnecessaireauprogrammeurCamlexperimente :unmanueldereferencedulangageCaml et un manuel dutilisation du syst`eme Caml Light, le compilateur Caml que nousutilisons dans ce livre. Les deux livres sont con cus pour etre utilises ensemble : le presentouvragerenvoieaumanueldereferencepourunedescriptionexhaustivedulangageetdes explications detaillees de certains points techniques ; le manuel de reference supposeconnueslesnotionsintroduitesdanscetouvrage.Touslesexemplesdecelivresontpresentesdanslesyst`emeCaml Light, unen-vironnementdeprogrammationenCaml fonctionnant`alafoissurmicro-ordinateurs(Macintosh et PC) et sur mini-ordinateurs et stations de travail Unix. Il existe dautresimplementations dulangageCaml, commepar exempleObjectiveCaml, qui ajoute`aCamlLightdesobjetsetdesclasses,ainsiquunsyst`emedemodulespluspuissant.Lessentiel de ce qui est dit dans ce livre porte sur le langage et sapplique donc `a touteslesimplementations.Noussignaleronslesquelquespointsspeciquesausyst`emeCamlLight. Les lecteurs qui souhaitent consulter ladocumentationcompl`ete dusyst`emeCaml LightpeuventsereporterauManuel dereferencedulangageCaml, ou`anotresiteWebhttp://caml.inria.fr/.Lesyst`emeCamlLightestdistribuegratuitementetpeut etrereproduitlibrement`adesnsnoncommerciales. Pourceuxqui ontacc`esaureseauInternet, Caml LightestdisponiblesurleWeb`aladressehttp://caml.inria.fr/. LInstitutNationaldeRechercheenInformatiqueetenAutomatique(INRIA)enassureegalementladistri-butionsurcederoms.Pourobtenircecederom,reportez-vous`alencadrequigureenpagedecopyright.Nousencourageonslelecteur`aseprocurerlesyst`emeCaml Lightet`alinstallersursamachine, suivantlesinstructionsdonneesparexempledanslechapitre12duManuel de reference. Il pourra ainsi essayer les exemples et experimenter par lui-meme,cequiluifaciliteragrandementlalecturedecelivre.RemerciementsNoustenons`aremercierChristianQueinnec,BernardSerpetteetGerardHuetquise sont astreints `a relire ce livre, Valerie Menissier-Morain qui a participe `a lillustration,Ian Jacobs pour son assistance typographique et Christian Rinderknecht qui a restaureleslettrines, unecalligraphieanglaiseduhuiti`emesi`ecle. Lejeudetaquindelasec-tion11.4estd u`aFran coisRouaix ; lexempledelasection11.2esttraduitdunpro-grammedeJohnOusterhout.IProgrammerenCamlAvertissementapremi` erepartiedecelivreestuneintroductionprogressiveaulangageCaml. Onnysuppose pas de connaissances prealables autres que des no-tions elementaires de mathematiques du niveau du lycee. Les exemples de pro-grammes que nous vous presentons vont de lexemple dune ligne au vrai programme deplusieurspages.Touslesexemplesont etem urementreechispour etresoit etonnants(voire amusants, pourquoi pas ?) soit reellement utiles ou representatifs des programmesquon ecritvraiment. Si bien que nous esperons que tous pourront nous lire avec prot,du debutant en programmation, ignorant compl`etement Caml et desirant sen faire uneidee, `aletudiantconrmequi trouveramati`ere`areexiondansdesprogrammesnontriviaux.Ensadressant`aunsivastepublic,nousavonstentedaccelererlalecturedetous :ledebutantverrasouventdessectionsquonlui sugg`eredenepaslire, carellessontcompliquees et pas indispensables pour lasuite, tandis quelespecialisteserainvite`asauterdeschapitresentierssi sesconnaissanceslelui permettent. Parexemple, leprochainchapitredebuteparunavertissementauspecialiste :Si vous savez dej` a que 2+2 ; ; font - :int=4 , . . . , vous pouvezsautercechapitre.Enrevanche,lechapitre3contientunesection Eetset evaluation ,quisouvreparunavertissementaudebutant : Cettesectionpeut etresauteeenpremi`erelecture. Lademarchequenousavonsadoptee,cest-` a-direlapprentissagepardesexemplesinteressants,nousaconduits` apresenterlesnotionsdulangageparnecessite :nouslesexpliquonslorsquellesinterviennentetuniquementl` a. Il sepeutdoncquecertainesnotions, inutiles `a nos programmes, ne soient pas passees en revue. Cela indique claire-mentquelles ne sontpas essentielles. Si lon desire absolumentune vue exhaustive despossibilites de Caml, on consultera le ManueldereferencedulangageCaml auquel nousavonsdej` afaitallusion.1PremierspasO` ulonverieque2et2font4.i voussavezd ej` aque 2 + 2 ; ; font - : int = 4 etque let f= function x ->. . . signie let f x = . . . , vouspouvezsautercechapitre.Sinon,ilvousinitiera`alinteractionavecCaml.1.1 IdeesgeneralessurCamlCaml est unlangagesimple : il yapeudeconstructions mais ces constructionssont les plus generales possibles. Caml utilise des notations intuitives ou consacrees parlusageetsouventprochesdecellesdesmathematiques. Parexemple, pourajouter1et2,ilsutdecrire1 + 2.Etleschanesdecaract`eres,cest-` a-direlestextesquinedoivent pas etre interpretes par le langage, sont ecrites entre des guillemets", notationclassiqueeninformatique.BienquerealiseenFrance, Caml est anglophone : ses mots-cles sont enanglais.Ainsi, lesvaleursdeveritedelalogiquemathematique, levrai etlefaux, deviennenttrueetfalseenCaml. Cenestpasunereellediculte, carlesmots-clessontpeunombreuxetnouslestraduironsaufuret`amesure.Caml apporteunegrandeaideauprogrammeur, enseor cantdedetecterlepluspossiblederreurs :lelangageanalyselesprogrammesquiluisontsoumispourverierleur coherence avant toute tentative de compilation ou dexecution. La principale anal-yse de coherence quil eectue se nomme le typage, mecanisme qui verie que lesoperationsquonutilisesontdej` adeniesetquelesvaleursquonleurappliqueontunsens. Par exemple, laddition nest denie que pour les nombres, pas pour les valeurs deveritenipourleschanesdecaract`eres.Donctrue + 1serarejete,delamemefa conque1 + "oui".VousconstaterezvitequilestainsiplusdiciledecrireenCamldesprogrammes manifestement faux : le langage les rejette automatiquement. Le corollaireest evidemmentquilestplusfaciledecriredesprogrammescorrects !Si vousetesfamilieravecunlangagealgorithmiqueclassique, commePascal parexemple, vous neserezpas compl`etement depaysepar Caml : vous yretrouverezlanotion de fonction et une notion similaire `a celle de procedure ; dautre part nous avons6 Premierspasdej` avuqueCaml est unlangagetype. Ces notions sont simplement generalisees etsimpliees : parexempleletypageestautomatiqueetnenecessitepasdannotationsdanslesprogrammescommecestlecasenPascal.1.2 DialogueravecCamlCaml ore non seulement un compilateur traditionnel, qui transforme des chiers decode source en code compile executable par la machine, mais aussi un syst`eme interactifqui vouspermetdedialoguerdirectementavecCaml, sanspasserparlintermediairedun chier. Dans ce mode, le langage sutilise comme une calculette : vous tapez desphrasesauclavierdelordinateuretCaml reagitenvousdonnantimmediatementlesresultatsdevosprogrammes.Nousutiliseronsdabordcettemethodedinteractiondi-recte car elle facilite lapprentissage. Nous verrons plus tard lutilisation du compilateurindependant, `a partir du chapitre 10. Vous pouvez donc entrer au terminal les exemplesquisuivent,sivousavezdej` ainstallelesyst`emeCamlLightsurvotremachine.Touteslesphrasessoumises`aCamldoiventetremuniesduneindicationdendephrase, ce quon note en Caml par;; (deux points-virgules accoles). Cest justie pourunsyst`emequi oreuneversioninteractive, dans lamesureo` uil est impossiblededeviner quand lutilisateur a termine sa phrase : par exemple apr`es1 + 2, il est permisdecrireencore+ 3pourevaluer1 + 2 + 3. Dautrepart, unephrasepeutsetendresurautantdelignesquenecessaire ; landelaligneneseconfonddoncpasaveclandelaphrase.Ondevradonc ecrire;;poursignalerlandelaphrase.Ilfautbienentenduegalementappuyersurlatouche retourchariot (returnenanglais)commecesttraditionnelpourtouteinteractionavecunordinateur.EnreponseausignedinvitedeCaml(lecaract`ere#queCamlimprimepourindi-querquilattendquenoustapionsquelquechose),demandons-luideectuerunpetitcalcul : laddition de 2 et de 2. Pour cela nous entrons simplement loperation `a eectuer,2 + 2,suiviedelamarquedendephrase;;.# 2 + 2;;- : int = 4Caml nous repond immediatement, en indiquant par un signe - que nous avons simple-mentcalcule unevaleur, quecette valeurestdetypeentier(:int)etquelle vaut4(=4).VousconstatezqueCamladeduittoutseulletypeduresultatducalcul.Pourunexemplesisimple,cenestpasvraimentimpressionnant,maiscestunmecanismeab-solument general : quelle que soit la complexite du programme que vous lui soumettrez,Camlendeduiraletypesansaucuneinterventiondevotrepart.1.3 LesdenitionsVouspouvezdonnerunnom`aunevaleurquevouscalculez, pournepasperdreleresultat devotrecalcul. Laconstructionqui permet ainsi denommer des valeurssappelleunedenition.Lesdenitions 7DenitionsglobalesDememequenmathematiqueson ecrit : soitslasommedesnombres1,2et3 ,on ecritenCaml( soit setraduitparletenanglais) :# let s = 1 + 2 + 3;;s : int = 6Camlnousrepondquenousavonsdeniunnouveaunoms,quiestdetypeentier(:int)etvaut6(= 6).Maintenantquelenomsestdeni,ilestutilisabledansdautrescalculs ;parexemple,pourdenirlecarredes,on ecrirait :# let s2 = s * s;;s2 : int = 36Lesdenitionssontdesliaisonsdenoms`adesvaleurs. Onpeutconsiderercesnoms(quon appelle aussi identicateurs, ou encore variables) comme de simples abreviationspourlavaleurquileurestliee.Enparticulier,unedenitionnestpasmodiable :unnomdonnefaittoujoursreference`alamemevaleur, cellequonacalculeelorsdeladenitiondunom. Lemecanismedu let estdoncfondamentalementdierentdumecanismedaectation,quenous etudieronsplusloin.Ilestimpossibledechangerlavaleurliee`aunnom;onpeutseulementredenircenomparunenouvelledenition,doncunnouveau let .Unefoisdeni,unnomatoujourslamemevaleurInformatiqueetmathematiquesLagrandedierenceentrelesmathematiques et leslangages deprogrammation,memeceuxqui serapprochent des mathematiques commeCaml, est quunlangagedeprogrammationcalculeavecdes valeurs et nonpas avecdes quantites formelles.Parexemple, enmathematiques, si xestunentier, alorsx xestegal `a0. Il nestpasnecessairedeconnatrelavaleurdexpourobtenirleresultatducalcul ;onparledailleurs plut ot de simplication que de calcul. Au contraire, en Caml, on ne peut parlerdunnomsilnapaseteprecedemmentdeni.Onnepeutdoncpascalculerx xsixnestpaslie`aunevaleurprecise,carilestalorsimpossibledefaireeectivementlasoustraction:# x - x;;Entree interactive:>x - x;;>^Lidentificateur x nest pas d efini.Lelangageindiqueici quenotrephraseesterroneepuisquelenomxnajamaisetedeni ; on dit encore quex nest pas lie . Mais le resultat est evidemment le bon, d`esquelidenticateurestdeni ;nouspouvonsparexemplefairelecalculavecs :# s - s;;- : int = 0Une autre dierence essentielle entre un programme et une denition mathematiqueresidedanslanotiondecacite :unlangagedeprogrammationcalculedesvaleursde8 Premierspasmani`ereeective, cequi demandeuncertaintemps. Si cetemps devient prohibitif,onpeut considerer queleprogrammeest incorrect, memesi lonpeut prouver quildonneraitunjourlebonresultat.Enmathematiques,cettenotiondetempsdecalculestsansimportance. Unautreecueil majeurdelinformatiqueestquelleneconnatpaslinni.Parexemple,lalimitequandntendverslinnidef(n),cequonnoteenmathematiques limnf(n) et qui signie la valeur de f(n) quand n devient arbitraire-mentgrand,existeeventuellementenmathematiques,maisnepeutquetreapprocheeparunemachine. Enn, levaluationdesexpressionsdunlangagedeprogrammationtel que Caml netermine pas toujours : les calculs peuvent boucler et donc ne jamaissachever. Autrementdit,les fonctionsdenissablespar unprogramme sonten generaldesfonctionspartielles(nondeniespourcertainesvaleurs)plut otquedesfonctionstotales(toujoursdenies).DenitionslocalesLesdenitionsdenomsquenousvenonsdevoirsont permanentes : ellesrestentvalidestantquevousnabandonnezpaslesyst`emeCaml.Cesdenitions denitives sontqualiees deglobales. Cependant,pour faire un petitcalcul, il est inutile dutiliserdes denitions globales : on dispose donc en Caml dun moyen de denir temporairementdes noms, pour la seule duree du calcul en question. Ces denitions temporaires sont lesdenitionslocales, qui disparaissent`alandelevaluationdelaphrasedanslaquelleellessetrouvent. Cesdenitionslocalesnesont doncplusvalidesapr`eslecalcul delexpressionquilessuit(apr`eslemot-clein,quisignie dans ) :# let s = 20 in s * 4;;- : int = 80Lenomsaetelie`a20pendant le calcul de s * 4, mais ladenitionprecedentedesresteinchangee. Poursenrendrecompte, il sutdedemanderlavaleurdes,cest-` a-direleresultatducalculreduit`as :# s;;- : int = 6Ladenitionlocaledunnomestcompl`etementindependantedutypeactueldunom:par exemple,s ets2 qui sont actuellement de typeint peuvent etre denis localementavecletypestring :# let s = "Le langage " and s2 = "Caml" in s ^ s2;;- : string = "Le langage Caml"Cet exemple utilise loperateur^ qui met deux chanes de caract`eres bout `a bout (con-catenation). Notezegalement que les denitions multiples consistent enune simplesuccessiondedenitionssepareesparlemot-cleand(quisignie et ).1.4 FonctionsLesfonctionsformentlesconstituantselementairesdesprogrammesenCaml. Unprogrammenestriendautrequunecollectiondedenitionsdefonctions,suiviedunappel`alafonctionquideclenchelecalculvoulu.Fonctions 9DenirunefonctionDenirunefonctionenCaml estsimpleetnaturel, carlasyntaxeesttr`esprochedesnotationsmathematiquesusuelles.`Aladenitionmathematique soitsuccesseurla fonction denie par successeur(x) = x+1 correspond la denition Caml suivante :# let successeur (x) = x + 1;;successeur : int -> int = Caml nous indique encore une fois que nous avons deni un nom: successeur. Ce nomapourtypeint -> int(->seprononce `eche ), qui estletypedesfonctionsdesentiers (int) vers les entiers (-> int) et ce nom a pour valeur une fonction (= ).Le syst`eme atrouve tout seul le type de lafonction, mais il ne sait pas commentimprimerlesvaleursfonctionnelles, parcequeleurrepresentationinterneestfaitedecode machine ; il ache donc simplement sans plus de precisions. Eectivement,lenomsuccesseurposs`edemaintenantunevaleur :# successeur;;- : int -> int = Une denitionde fonctionnest donc pas essentiellement dierente dune denitiondentier ou de chane de caract`eres. Elle denit simplement le nom de la fonction et luidonneunevaleurquiestunefonction,cequonappelleunevaleurfonctionnelle.ApplicationdefonctionsLapplication dune fonction `a son argument suit aussi la convention mathematique(rappelonsque f(x) seprononcefdex) :# successeur (2);;- : int = 3Le langage Caml fournit une syntaxe plus souple pour utiliser et denir les fonctions : onpeut supprimer les parenth`eses autour des noms des arguments des fonctions aussi bienau cours dune denition que lors dune application.Etant donnee la paresse legendairedes programmeurs, cest bien s ur cette habitude qui predomine ! Avec cette convention,on ecritsimplement# let successeur x = x + 1;;successeur : int -> int = # successeur 2;;- : int = 3DenitionslocaledefonctionsRien nempeche de denir une fonction localement, bien que cela surprenne souventlesdebutantsenCaml.Voiciunexempledefonctionlocale :# let pr ed ecesseur x = x - 1 in(pr ed ecesseur 3) * (pr ed ecesseur 4);;- : int = 6La fonction predecesseur nest denie que pendant le calcul du produit despredecesseursde3et4.10 PremierspasLesdenitionslocalessontaussiutiliseesdanslesdenitionsglobales,parexemplepourcalculerlaformulequidenitunefonction(cequonappellelecorpsdelafonc-tion). Denissonsparexemplelafonctionpredecesseur_carrequi retournelecarredupredecesseurdunnombre(lafonctionx(x 1)2). Nousdenissonslocalementlepredecesseurdelargument,puislelevonsaucarre :# let pr ed ecesseur_carr e x =let pr ed ecesseur_de_x = x - 1 inpr ed ecesseur_de_x * pr ed ecesseur_de_x;;pr ed ecesseur_carr e : int -> int = # pr ed ecesseur_carr e 3;;- : int = 4Unefonctionpeutaussi denirlocalementuneautrefonction. Parexemple, pourdenir lafonctionpuissance4qui el`evesonargument `alapuissance quatre, il estnatureldutiliserlaformulex4=(x2)2,doncdeleveraucarrelecarredelargument.Pourcela,ondenitlocalementlafonctioncarreetonlutilisedeuxfois :# let puissance4 x =let carre y = y * y in (* d efinition locale dune fonction *)carre (carr e x);;puissance4 : int -> int = # puissance4 3;;- : int = 81Commeonlevoitsurcetexemple, lescommentairesenCaml sontencadresentre(*et*). Ilspeuventcontenirnimportequel texte, ycomprisdautrescommentaires, etsetendresurplusieurslignes.Fonctions`aplusieursargumentsLes fonctions possedant plusieurs arguments ont simplement plusieurs nomsdargumentsdansleurdenition:# let moyenne a b = (a + b) / 2;;moyenne : int -> int -> int = # let p erim`etre_du_rectangle longueur largeur =2 * (longueur + largeur);;p erim`etre_du_rectangle : int -> int -> int = Letypedecesdeuxfonctions, int -> int -> int, indiquequellesprennentdeuxargumentsdetypeint(int -> int ->)etcalculentunentier(-> int).Lorsque des fonctions ont plusieurs arguments, il faut evidemment leur fournir aussileur compte darguments quand on les applique. Ainsi, un appel `a p erim`etre_du_rectangleoumoyennecomporteradeuxarguments :# p erim`etre_du_rectangle 3 2;;- : int = 10# moyenne 5 3;;- : int = 4Fonctions 11FonctionsanonymesUne fonction Caml est un citoyen `a part enti`ere , on dit aussi citoyen de premi`ereclasse , cest-` a-dire une valeur comme toutes les autres. Une fonction a le meme statutquun nombre entier : elle est calculee, on peut la passer en argument ou la retourner enresultat.Lesvaleursfonctionnellessontcreeeslorsdesdenitionsdefonctions,commenousvenonsdelevoir.Cependant,onpeutaussiconstruiredesvaleursfonctionnellessansleurdonnerdenom, enutilisantdesfonctionsanonymes. Cesfonctionssontin-troduitesparlemot-clefunction,suividelaformulequilesdenit :# (function x -> 2 * x + 1);;- : int -> int = Encoreunefois, Caml nousindiqueparlesymbole-quenousavonsfaitunsimplecalcul, dontleresultatestdetypeint -> intetdontlavaleurestunefonction(=).Onappliquelesfonctionsanonymescommetouteslesautresfonctions,enlesfaisantsuivredeleur(s)argument(s) :# (function x -> 2 * x + 1) (2);;- : int = 5Denitiondefonctions`alaidedefonctionsanonymesIlexisteunautrestylededenitionsmathematiquesdefonctions : Soitsuccesseur : Z Zx x + 1 Cestyleinsistesur lefait quesuccesseurest unefonctionqui `atoutelement xdelensembleZ des entiers associe x +1.`A laide des fonctions anonymes, cette denitionsetraduittr`essimplementenCaml :# let successeur = function x -> x + 1;;successeur : int -> int = ContraintesdetypePourserapprocherencoredustyledeladenitionmathematique, onpeutmemeajouter une contrainte de type sur le nom de la fonction, qui rend compte de lindicationsuccesseur : ZZdes mathematiques. Une contrainte de type(ouannotationdetype)estuneindicationexplicite dutype duneexpression Caml.Vouspouvez,sivouslesouhaitez,ajouterdesannotationsdetypedansvosprogrammes,parexemplepouraider `alarelecture. Pour annoter unmorceaudeprogrammeavecuntype, il sutdemettrecemorceaudeprogrammeentreparenth`esesavecsontype, aveclamemeconventionquelesyst`emeinteractif,cest-` a-direun : suividunnomdetype :# ("Caml" : string);;- : string = "Caml"Nousobtenonsmaintenantunedenitiondelafonctionsuccesseurtr`esd`ele`acelledesmathematiques :12 Premierspas# let (successeur : int -> int) = function x -> x + 1;;successeur : int -> int = Cestylerevient`adenirlenomsuccesseurcommeunnomordinaire, maisdontlavaleur est unefonction. Cettedenitionest absolumentequivalente`alaprecedentedenitiondesuccesseur :let successeur (x) = x + 1;;Fonctionsanonymes`aplusieursargumentsLe choixentre les deuxmodes de denitiondes fonctions est donc, comme enmathematiques, une simple aaire de style. En r`egle generale, le style letsuccesseur(x) = est plus concis, particuli`erement lorsque lafonctionaplusieurs arguments,puisquelautrestyleoblige`aintroduirechacundes arguments par uneconstruction function argument -> . Par exemple, denir lafonctionmoyennedans le style function x -> conduirait`a ecrire :# let moyenne = function a -> function b -> (a + b) / 2;;moyenne : int -> int -> int = Aupassage,nousremarquonsquunefonctionanonymeaparfaitementledroitdavoirplusieursarguments. Attention: il nestpaspermisdecrirefunction a b ->, il fautimperativement repeter lemot-clefunction, unefois par argument. Cest pourquoinousutiliseronslaplupartdutempslestyleleplusleger,celuiqui evitedemployerlemotfunction.LestestsetlalternativeCaml fournit uneconstructionpourfairedescalculsqui dependent dunecondi-tion: cestlalternative, leclassique if. . . then. . . else. . . . Cetteconstructioncorrespondaucalcul si conditionalors expression1sinonexpression2 , qui signiesimplement quil faut calculerexpression1si la condition est vraie etexpression2sinon.Nousillustronscetteconstructionenimplementant (cest-` a-direenrealisantsurma-chine) la fonction valeur absolue , qui calcule la valeur dun nombre independammentdesonsigne.Cettefonction,notee|x|enmathematiques,estdeniecomme :|x| =_x si x 0x sinonSachant quen Caml les comparaisons entre nombres entiers suivent les notationsmathematiques(, =, >=et= 0 then x else -x;;valeur_absolue : int -> int = # valeur_absolue (3);;- : int = 3# valeur_absolue (-3);;- : int = 3Valeursetprogrammes 13ValeursdeveriteRemarquonsquelestestscalculentunresultat,unevaleurdeverite.Unevaleurdeveriteestsoit vrai ,soit faux ,cequisenotetrueetfalseenCaml.Onappelleaussilesvaleursdeverite valeursbooleennes ,enlhonneurdulogicienBoole ;ellessontdutypebool.Onpeutdoncemployerlestestspourcalculerunbooleen :# 2 < 1;;- : bool = false# (valeur_absolue (3)) = (valeur_absolue (-3));;- : bool = true1.5 ValeursetprogrammesNousvenonsdefairedescalculs.Maiso` usontdonclesprogrammes ?Cesonttoutsimplement les fonctions ! Unprogrammeconsisteenunedenitiondefonctionquicalcule le resultat desire. En general cette fonction utilise `a son tour dautres fonctions,qui correspondent `a la notion de sous-programmes. Par exemple, si vous desirez calculerlasommedescarresdedeuxnombres,vousdenirezdabordlafonctioncarre :# let carre (x) = x * x;;carre : int -> int = pourensuitedenirlafonctiondesiree :# let somme_des_carr es x y = carre (x) + carre (y);;somme_des_carr es : int -> int -> int = etennlappliquerdanslecasquivousinteresse :# somme_des_carr es 3 4;;- : int = 25En resume : une fonction manipule des valeurs (entiers, chanes de caract`eres, booleens)quionttoutesuntype ;lafonctionelle-memeestunevaleuretposs`ededoncuntype.Encesens,lesprogrammesenCamlsontdesvaleurs !1.6 ImpressionNotiondeetCaml propose bien s ur le moyen dimprimer des valeurs `a lecran ou dans des chiers.Onutilisepourceladesfonctionsdontlebutnestpasdeectuerdescalculsmaisdeproduiredes eets, cest-` a-direuneactionsur lemondeexterieur, par exempleuneinteractionaveclutilisateurduprogramme,lecrituredunchieroudunmessageauterminal.UnpremiereetNous allons realiser un premier eet tr`es simple : nous ecrivons Bonjour ! `alecranenutilisant lafonctionpredenie print_stringqui ajustement pour eetdimprimer sonargument auterminal. Une fonctionpredenieest unefonctionqui14 Premierspasvous est fourniepar lesyst`emeCaml ; vous navezdoncpas besoindelecrire. CesfonctionssontdecritesendetailsdansleManuel dereferencedulangageCaml. Ellessontaussi appelees fonctions primitives ou toutsimplement primitives . Essayonslaprimitiveprint_string :# print_string "Bonjour!";;Bonjour!- : unit = ()Limpressionsest produite comme prevu. Cependant Caml nous indique aussi quenous avons calcule unresultat de type unitet qui vaut (). Le type unitest untypepredeni qui necontient quunseul element, () , qui signiepar convention rien . Nousnavonspasdemandeceresultat : toutcequenousvoulions, cestfaireuneimpression(uneet). MaistouteslesfonctionsCaml doiventavoirunargumentet rendre unresultat. Lorsquune fonctionop`ere uniquement par eets, ondit quecettefonctionestuneprocedureOnutilisealors rien , cest-` a-dire(), enguisederesultatoudargument. (Enpositiondargumentdansunedenitiondefonction, onpeut considerer ()comme unargument minimal : largument (x)auquel onauraitmemeretirelavariablex ; dememeenresultat, ()gureuneexpressionparentheseedonttoutletexteauraitdisparu.)Impressionssuccessives : sequencementSupposezquil nousfailleimprimerdeuxtextessuccessifs`alecran: parexemple, Bonjour puis tout lemonde ! . Nous devons fairedeuxeets `alasuitelundelautre,ensequence.Evaluerensequencedeuxexpressionse1ete2signiesimplementles evaluer successivement : dabord e1, puis e2. Comme dans la plupart des langages deprogrammation, lasequenceestnoteeparunpointvirguleenCaml. Loperation e1puise2 secritdonce1;e2.Nous ecrivonsdonc :# print_string "Bonjour "; print_string "tout le monde!";;Bonjour tout le monde!- : unit = ()La machine a dabord imprime Bonjour puis toutlemonde!, comme on sy attendait.Le resultat de toute loperation(de toute lasequence) est rien. Celasexpliquenaturellementparcequeleresultatdelapremi`ereimpression(unpremier rien)aeteoublie. Demani`eregenerale, lasequence jette leresultatdupremiercalcul etrenvoieleresultatdusecond: e1;e2sevalueenlamemevaleurquee2. Commeleresultatdee1estdetruit, il estclairquelexpressione1nestutilequesi elleproduitdeseets :ilseraitstupidedevaluerlasequence(1 + 2); 0quirendraitexactementlememeresultatque0.# (1 + 2); 0;;Entree interactive:>(1 + 2); 0;;> ^^^^^Attention: cette expression est de type int,mais est utilis ee avec le type unit.- : int = 0On constate dailleurs que le compilateur emet une alerte pour indiquer que lexpression(1 + 2)produitunresultatquiseraignore !Conventionssyntaxiques 15Pourdelimiterprecisementunesequence, onlencadresouvententrelesmots-clesbegin(debut)etend(n) :# beginprint_string "Voil` a ";print_string "Caml!";print_newline ()end;;Voil`a Caml!- : unit = ()La derni`ere expression, print_newline(), fait imprimer un retour chariot. La fonctionprint_newline op`ere enti`erement par eets, elle na donc pas de param`etre signicatif,nideresultatsignicatif.1.7 ConventionssyntaxiquesResumonsquelquesconventionssyntaxiquesquil estbondavoirentetepourlasuite.DenitionsdefonctionsPourlesdenitionsdefonctions,nousavonslaconventionsuivante :let fx =. . . est equivalent`a let f= function x ->. . .Onpeutiterercetteconventionpourdenirlesfonctions`aplusieursarguments :let fx y=. . . est equivalent`a let f= function x -> function y->. . .ApplicationdefonctionsPour ce qui est de lapplicationde fonction, nous avons vuque les parenth`esesautourdelargument etaitfacultatives :Sixestunevariableouuneconstante,fx est equivalent`a f(x)Attention: cetteconventionnestvalablequelorsquexestunevariableouunecon-stante. Si vous employez cette conventionavec une expressionplus compliquee, lesparenth`eses retrouvent leur sens habituel en mathematiques (le groupement des expres-sions)etlapresencedeparenth`esesmodiealorsleresultatducalcul. Parexemple,largument negatif dunefonctiondoit imperativementetreparenthese : f (1) sansparenth`esesestcompriscommelasoustractionf1.Dememe,silargumentestuneexpressioncomplexe, vous nepouvezpas supprimer les parenth`eses sans changer leresultat :# successeur (2 * 3);;- : int = 716 Premierspas# successeur 2 * 3;;- : int = 9Cettederni`erephraseestcompriseparCaml commesi vousaviezecrit(successeur2) * 3. Cestdailleursunphenom`enegeneral pourtouslesoperateursdeCaml : lesapplicationsdefonctionsenargumentdesoperationssontimplicitementparenthesees.Par exemple successeur 2 - successeur 3 est lu comme (successeur 2) -(successeur3), et de meme pour tous les operateurs : successeur2>=successeur3estcorrectementinterprete.fx + gy est equivalent`a (fx) + (gy)Applicationdefonctions`aplusieursargumentsLapplication de fonctions `a plusieurs arguments suit les memes conventions :moyenne (2) (6) est equivalent `a moyenne 2 6, mais vous devez conserver lesparenth`esessivousvoulezcalculermoyenne (2 * 3) (3 + 3).Techniquement, on dit que lapplication associe `a gauche en Caml, ce qui signieque les parenth`eses peuvent etre omises dans (fx) y, qui correspond au resultat de fdexapplique`ay,maisquellessontindispensablesdansf(gx),quisignieaucontrairefappliqueeauresultatdelapplicationdelafonctiong`ax.fx y est equivalent`a (fx) yAulieudemoyenne 2 6,onpeutdonc ecrire(moyenne 2) 6.Ladeuxi`emeformeestevidemmentunpeuetrange, maisellealamemesignicationquelapremi`ere. Onadonc beaucoup de mani`eres equivalentes dexprimer lapplication de la fonction moyenne`a2et`a6.Laplussimpleest evidemmentsansparenth`esesaucunes : moyenne 2 6 .Mais lon peut ecrire aussi moyenne (2) (6) ou, en utilisant la r`egle precedente pourajouter encore des parenth`eses, (moyenne (2))6 ou meme (moyenne (2))(6) .Enpratique,nousutiliseronstoujourslaformelaplussimple,sansparenth`eses.Enrevanche,onnepeutabsolumentpasgrouperlesarguments2et6`alinterieurdeparenth`eses : moyenne (2 6)esterrone. Celasignieraiteneetquondesireap-pliquer moyenne`aunseul argument 2 6 . Qui plus est, celavoudrait direquontentedappliquerlenombre2aunombre6 !Desexpressionsconstruitessurlemod`elemoyenne (2 6), cest-` a-dire, plus generalement, dugenref (g y), ont pourtant unsens. Considerez, par exemple le calcul dusuccesseur dusuccesseur de 1. Onecritnaturellement :# successeur (successeur 1);;- : int = 3Mais si lonotelesparenth`eses, onecrit successeur successeur 1et celasigniemaintenantquenousvoulonsappliquerlafonctionsuccesseur`adeuxarguments(lepremier argument serait la fonction successeur elle-meme et le second argument serait1).Cependantlafonctionsuccesseurnadmetquunseulargument ;sinousretironslesparenth`eses(sciemmentouparinadvertance),Camlnousindiquedoncuneerreur :Diagrammessyntaxiques 17# successeur successeur 1;;Entree interactive:>successeur successeur 1;;> ^^^^^^^^^^Cette expression est de type int -> int,mais est utilis ee avec le type int.Lemessageindiqueeneetquelexpressionsoulignee(successeur)estunefonctiondetypeint->int :ellenepeutpas etreutiliseecommeunargumententier.Retenonsdetoutefa conque :f(gy) nestpas equivalent`a fgy1.8 DiagrammessyntaxiquesNous resumons la mani`ere decrire les constructions de Caml au moyen de denitionssimplieesdesyntaxe,tellesque :expression ::= entier| chane-de-caract`eres| booleen| . . .CettedenitionsigniequuneexpressiondulangageCaml (expression)est(::=)oubienunentier(entier),oubien(|)unechanedecaract`eres(chane-de-caract`eres),oubien(|)unbooleen(booleen),oubien. . . Cecinestquunexempleetnousnefaisonsevidemmentpasgurertouteslesconstructionsconcernantlesexpressions.Dailleurs,nous savons dej` aquunesequenceest uneexpression; elledevrait doncgurer danslensemble des expressions, de meme que les expressions parenthesees (toute expressionentouree de parenth`eses est une expression). Avec ce formalisme, une sequence se decritpar :sequence::=expression;expressionouencore,sielleestdelimiteeparlesmots-clesbeginetend :sequence::=beginexpression;expressionendCettemethodededescriptiondelasyntaxedunlangageestappelee syntaxeBNF,pourBackus-NaurForm,desnomsdeJohnBackusetPeterNaurquilontrepandue.2RecursiviteO` ulonapprend` aparlerdecequonneconnatpasencore. . .ous ne connaissez pas les fonctions recursives, ou ny avez jamais riencompris, oubienvous vous passionnez pour les tours de Hanoi ?Alors lisezcechapitre, il est fait pour vous. Nous yapprendrons unenouvellemani`eredutiliser lordinateur. Jusqu`a present nous nous en servions pour obtenir les solutions`a des probl`emes que nous savions resoudre, mais qui demandaient tropde calculsmathematiquespourquenouslestraitions`alamain.Cestlemploidelordinateurleplusrepanduactuellement : lamachinesert`aeectueruntr`esgrandnombredefoisdesoperationssimples(pensez`adeslogicielsdegestion,payeoucomptesenbanque).Aveclarecursivite, onel`evelaprogrammation`aunrangautrement plus noble : onecrit des programmes qui resolvent des probl`emes que lon ne sait pas forcementresoudresoi-meme. Parvenir`adirigerunemachinepourquelletrouvepournouslasolutiondunprobl`emeestreellementfascinant,ilestmemegrisantdevoirapparatrecettesolutionauterminal, quandelleestleresultatdelexecutiondunprogrammequonasoi-meme ecritsansavoirconsciencedesavoirresoudreleprobl`eme.2.1 FonctionsrecursivessimplesNotionderecursiviteUnedenitionrecursiveestunedenitiondanslaquelle intervientlenomquonesten train de denir. Cela correspond dans le langage courant `a une phrase qui se mordla queue . Lexemple typique est la reponse `a la question Quest-ce quun egoste ? : Quelquun qui ne pense pas `a moi ! . Il est clair quon soup conne legitimement ce genredephrasesdetredenueesdesensetquecestsouventlecas.Pourtant,lesdenitionsrecursivessonttr`esemployeesenmathematiqueseteninformatique,domaineso` ulonse mee beaucoup des phrases denuees de sens . Il existe donc forcement des phrasesqui se mordent la queue et pourtant poss`edent une signication precise, utilisable enmathematiquesouinformatique. Touteladicultedesdenitionsrecursivesprovientdeladetectiondescaso` ularecursiviteentranelenon-sens. Unedenitionrecursivesenseeestqualieedebienfondee.Nousverronsparlexemplecequecelaveutdire.20 RecursiviteLintuition la plus simple quon puisse donner de la recursivite est lidee de recommencer la meme chose. La recursivite est presente aussi dans le domainegraphique, unexemple nous est donne par le cel`ebre dessinqui orne les couverclesde Vachequi rit , gurant unevachequi porteenbouclesdoreillesdesbotesde Vachequi rit (dont les couvercles comportent doncledessinlui-meme). Dans ledomainephysique, linnitedimagesqui apparatdansdeuxmiroirsquasi parall`elesestaussiunebonneanalogie(experiencehabituellechezlecoieur).Prenons un exemple plus informatique : la tr`es cel`ebre fonction factorielle ,qui retourneleproduitdesnombresentiersinferieursouegaux`asonargument. Enmathematiques, elle est notee par un point dexclamation (!) place apr`es son argument.Onaparexemple4! = 4 3 2 1.Lafonctionfactorielleestdeniepar :n! =_1 si n = 0n (n 1)! sinon.Cette denition est recursive : le nom ! intervient dans le corps de sa propredenition. Donc, pour comprendrelasignicationden! il faut comprendrecellede(n 1)!. Celasembledicile, caronexigelacomprehensiondunenotionquonestjustement entraindexpliquer . . . Enfait, celasejustieparcequelecalcul den!termine toujours : il sut detre patient et de continuer le calcul jusqu`a atteindre 0, cequiarriveraforcementpuisquonexpliquelasignicationden!enfonctionde(n 1)!.Parexemple :3! = 3 (3 1)! = 3 2!= 3 2 (2 1)! = 3 2 1!= 3 2 1 (1 1)! = 3 2 1 0!= 3 2 1 1 = 6Ladenitionmathematiquerecursivedelafonctionfactorielleestdoncbienfondee :onobtientnalementunresultatpourtoutargumententiernaturel.Ce genre de denitionse traduit tr`es facilement enCaml ; mais pour celanousdevonsexplicitementprevenirCaml quenousdesironsfaireunedenitionrecursive,gr ace `a une nouvelle construction:let rec. Avant de lemployer, expliquons pourquoiilestnecessairedintroduireunenouvelleconstruction.PorteestatiqueetdenitionsrecursivesEnmathematiques, vousnepouvezparlerduneentitemathematiqueavantdenavoirdonneladenition. Lememeaxiomeestvrai enCaml : vousnepouvezutiliserunidenticateursilnare cuunedenitionprealable.Cestcequonappellelaporteestatique, parcequevouspouveztrouverladenitiondenimportequel identicateurindependamment ducomportement duprogramme`alexecution(aucontrairedelaporteedynamique, o` ulavaleurdunidenticateurdependdelafa condontlecalculsederoule).EnCaml,pouruneoccurrencequelconquedunidenticateur,disonsx,ilvoussutdexaminerletexteduprogrammequiprec`edexpourtrouverlaliaisonquiadeni x. Cetteliaisonestsoitunedenitiondex : let x =, soitunedenitiondefonctionquiaxpourparam`etre :function x ->.Fonctionsrecursivessimples 21# let x = 1 inlet x = x + 1 inx + 3;;- : int = 5Lidenticateurx qui apparat dansx + 1 dans la denitionlet x = x + 1 est lie `a1(parlaprecedentedenitionlet x = 1),alorsqueledernierxdansx + 3estlie`a2parlaprecedentedenitionlet x = x + 1,commelesugg`ereceschema :let x = 1 inlet x = x + 1 inx + 3iTiTLe mecanisme de liaison est similaire pour des denitions simultanees (denitionssepareesparlemot-cleand).# let x = 1 inlet x = x + 1and y = x + 2 in x + y;;- : int = 5Lesdeuxxintervenantdanslesdenitionslet x = ... and y = ...fonttouslesdeuxreferenceaunomxprecedemmentdeniparlet x = 1.Lesliaisonssontmisesen evidencedansceschema :let x = 1 inlet x = x + 1and y = x + 2 inx + yiiTiTiTOnretiendraque,dansunedenitionCaml(ycomprisunedenitionsimultanee),Unnomfaittoujoursreference`aunedenitionprealable.Laconstructionlet recCeci pose evidemment probl`eme pour denir des fonctions recursives : nous ne pou-vons utiliser une denition introduite par un let, `a cause de la r`egle de portee statique.Eneet, si nousecrivons let f = ... f ..., loccurrencede fdans lexpressiondenissante ... f ...necorrespondpas aunomfquenous sommes entraindedenir(enparticulierparcequefnestpasencoredenie !)maisdoitcorrespondre`aunedenitionprecedentedef. Unpetitschemavautmieuxquunlongdiscours : laliaisondefsetablitverslepasse.let f = ... f ...iniTCest pourquoi une simple constructionletne permet pas de denir une fonctionrecursive :22 Recursivite# let factorielle n = if n = 0 then 1 else n * factorielle (n - 1);;Entree interactive:>let factorielle n = if n = 0 then 1 else n * factorielle (n - 1);;> ^^^^^^^^^^^Lidentificateur factorielle nest pas d efini.Enbref : uneliaisonletnest pas recursive ; il yadoncenCaml uneconstructionspeciale, let rec, destinee`aintroduirelesdenitionsrecursives. Cetteconstructionetablituneliaisondelaformesuivante :let rec f = ... f ...inicMaintenant,nousdenissonsfacilementlafonctionfactorielle :# let rec factorielle n =if n = 0 then 1 else n * factorielle (n - 1);;factorielle : int -> int = # factorielle 3;;- : int = 6Compter`alendroitet`alenversPourcomprendrecommentsexecuteunappel`aunefonctionrecursive,denissonsunefonctionquienum`erelesnombresparordredecroissantjusqu`a1, `apartirdunecertaine limite : par exemple, pour une limite de 5 nous voulons obtenir limpression de 54321 surlecran.Bienque le but de ce programme soit exclusivement de produire des eets, larecursivite sy introduit naturellement, puisque enumerer `a partir dune certaine limiten, cest : si lalimiteest 0, alors nerienfaire ; sinon, imprimer n, puisenumerer lesnombresprecedents. Si lonserendcomptequeenumererlesnombresprecedents consistetout simplement `aappeler notrefonctionaveclanouvellelimiten 1, onobtientleprogrammesuivant :# let rec compte_` a_rebours n =if n = 0 then () elsebeginprint_int n; print_string " ";compte_` a_rebours (n - 1)end;;compte_` a_rebours : int -> unit = # compte_` a_rebours 10;;10 9 8 7 6 5 4 3 2 1 - : unit = ()La fonction dimpression des entiers au terminal se nomme tout naturellementprint_int, par analogie avec la fonction dimpression des chanes print_string. Vousdevinezleschemadenommagedecesfonctions,quiconsiste`asuxerlenomprint_parletypedelargument. Ainsi, lafonctiondimpressiondesnombresottants(lesvaleursdutypefloatquenousverronsauchapitre8)sappelleprint_float,etcellepourlescaract`eres(typechar)sappelleprint_char.Telle quelle est ecrite, la fonction compte_` a_rebours ne termine pas si on lui passeunargument negatif (ladenitionnest doncpas bienfondee). Il serait plus s ur deremplacerletestn = 0parletestn unit = # compte 10;;1 2 3 4 5 6 7 8 9 10 - : unit = ()Cettefonctioncompte`alendroit ! Cest plus dicile`acomprendre : limpressionseproduit au retour des appels recursifs. On doit dabord atteindre n = 0 avant dimprimerle premier nombre, qui est alors 1. Eneet, cest toujours pendant levaluationdecompte 1quenousappelonscompte 0. Apr`esavoirimprime1, compte 1retourne`alevaluationdecompte 2,qui ecrit2etretourne`acompte 3,etainsidesuite.Nous allons utiliser le mecanisme de trace de Caml pour suivre les appels recursifsetlesimpressions. Cemecanismeimprime`alecranlesappelssuccessifsdunefonc-tion, ainsi quelesresultatsquelafonctioncalcule. Tra consparexemplelafonctionsuccesseur :# let successeur x = x + 1;;successeur : int -> int = # trace "successeur";;La fonction successeur est dor enavant tracee.- : unit = ()# successeur 2;;successeur 3- : int = 3Vous lavez devine, lappel dune fonction est indique par le signe signaleunretourdefonctionetacheleresultat obtenu. Nous suivons maintenant lederoulement des appels `acompteetcompte_` a_rebours :# trace "compte"; trace "compte_`a_rebours";;La fonction compte est dor enavant tracee.La fonction compte_` a_rebours est dor enavant tracee.- : unit = ()# compte 3;;compte ()- : unit = ()On voit clairement que compte i sach`eve avant limpression de i 1 et que compte 0 setermineavanttouteimpression. Celacontrasteaveccompte_` a_rebours, qui imprimeiavantlappelcompte_` a_rebours(i 1) :# compte_` a_rebours 3;;compte_` a_rebours ()- : unit = ()Epeler`alendroitet`alenversNous allons maintenant montrer la recursion `a luvre sur les chanes de caract`eres.Pourcefaire, nousavonsbesoindoperationssupplementairessurleschanesdecar-act`eres. La fonction predeniestring_length renvoie la longueur dune chane de car-act`eres. La notation s.[i] designe le ii`emecaract`ere de la chane de caract`eres s. Le pre-mier caract`ere a pour numero 0 ; le dernier a donc pour numerostring_length s 1.Acc`esdansunechanes ::= s.[indice]# let le_langage = "Caml";;le_langage : string = "Caml"# string_length le_langage;;- : int = 4# le_langage.[0];;- : char = CUncaract`ere en Caml est donc un signe typographique quelconque compris entre deuxsymboles.Voici deuxfonctionsquiepellentdesmots. Lapremi`ereepelle`alenvers, encom-men cant par ledernier caract`eredelachaneet ensappelant recursivement sur lecaract`ereprecedent.# let rec epelle_envers_aux s i =if i >= 0 thenbeginprint_char s.[i]; print_char ;epelle_envers_aux s (i - 1)end;;epelle_envers_aux : string -> int -> unit = # let epelle_envers s = epelle_envers_aux s (string_length s - 1);;epelle_envers : string -> unit = Fonctionsrecursivessimples 25# epelle_envers "snob";;b o n s - : unit = ()Laseconde epelle`alendroit,encommen cantparlepremiercaract`ereetensappelantrecursivementsurleprochaincaract`ere.# let rec epelle_aux s i =if i < string_length s thenbeginprint_char s.[i]; print_char ;epelle_aux s (i + 1)end;;epelle_aux : string -> int -> unit = # let epelle s = epelle_aux s 0;;epelle : string -> unit = # epelle "snob";;s n o b - : unit = ()Ces deuxexemples utilisent uneformenouvelledelalternative : laconstruction if . . . then . . . sans partieelse. La partieelse omise est implicitement completeeparlecompilateur,quiajouteelse (),autrementdit sinonrien.Ainsi,lecode :if i >= 0 then begin ... end;;estcomprisparCamlcommesinousavions ecrit :if i >= 0 then begin ... end else ();;Cette completion automatique vous explique pourquoi la phrase suivante est mal typee :# if true then 1;;Entree interactive:>if true then 1;;> ^Cette expression est de type int,mais est utilis ee avec le type unit.Retenonsladenitiondunealternativesanspartieelse :if cond then e est equivalent`a if cond then e else ()LespalindromesUnpalindromeestunmot(ouungroupedemotssansblancs)quiselitaussibien`alendroitqu`alenvers. Pourdeterminersi unmotconstitueunpalindrome, il sutdeverierquesespremieretderniercaract`eressontidentiques,puisdeverierquelemotsitueentrecesdeuxcaract`eresestluiaussiunpalindrome.c chanesmemescaract`erescpalindromesIlfautaussiremarquerquunmotreduit`aunseulcaract`ereestunpalindromeetquelachanevideestelleaussiunpalindrome(puisquelleselitaussibien`alendroitqu`a26 Recursivitelenvers). Pour obtenir une sous-chane dune chane de caract`eres, on utilise la fonctionpredeniesub_string(sous-chane), qui extraitunesous-chanepartantdunindicedonneetdunelongueurdonnee :# sub_string "Le langage Caml" 3 7;;- : string = "langage"Enparticulier,sub_string s 1 (string_length s - 2)retournelachanespriveedesonpremieretdesonderniercaract`ere. Ceci setraduitparlafonctionrecursivesuivante :# let rec palindrome s =let longueur = string_length s inif longueur bool = # palindrome "serres";;- : bool = true# palindrome "toto";;- : bool = falseOperateursbooleensCettefonctionsecritpluselegamment`alaidedesoperateurs et et ou desbooleens. Par denition, si Pet Q sont des booleens, alors lexpression Pet Q est vraiequandPetQsontvraiessimultanement.Naturellement,lexpressionPou Qestvraied`esquePoubienQestvraieetafortiorisiPetQsontvraies.EnCamlle ou senote||etle et &&.Lesoperateurs||et&&remplacentcertainesformesdexpressionsconditionnelles.En eet, la construction if Pthentrueelse Q calcule la meme valeur booleenne queP|| Q et de meme if Pthen Q elsefalse calcule la meme valeur que P&& Q. Biens ur,if Pthentrueelsefalse se simplie en P. On clarie souvent les programmesenappliquantcesequivalences.Voicidoncuneversionplussimpledepalindromequiutiliselesoperateursbooleens :# let rec palindrome s =let longueur = string_length s in(longueur bool = Lesconstructions||et&&ontlesmemesprioritesrelativesque+et*, cest-` a-direlememeparenthesageimplicite. Ainsi, delamemefa conquea + b * cselitenfaita + (b * c), lexpressiona || b && cestluea || (b && c)parlamachine. Onlitalorslecodedepalindrometr`esnaturellement :unechaneestunpalindromesisalongueur est inferieure `a 1, ou si ses caract`eres de debut et de n sont les memes et quelasous-chanequilsdelimitentestunpalindrome.Denitionsparcas :leltrage 27Fonctionrecursive`aplusieursargumentsPour plus decacite, nous reecrivons lafonctionpalindromeencomparant di-rectement les caract`eres de la chane argument deux `a deux, sans creer de sous-chanes.Onprenddoncdeuxindicesdanslachanearguments.Lindiceidemarreaupremiercaract`ere ; lindicej demarreauderniercaract`ere(audepartdelaboucleonadoncnecessairementij, saufsi lachaneestvide).`Achaqueetape, oncomparelescar-act`eres dindice i etj.Sils sont egaux, oncontinue ;sinon,la chane nest evidemmentpasunpalindrome.Larecursionsarretequandlindiceiatteintoudepassej.Danslecasi = j,onestsurlecaract`erecentraldelachaneetilnyarien`afaire(ilnerestequuncaract`ere`aexaminerforcementegal`alui-meme) :sestunpalindrome.Danslecasi>j, il nyarien`afairenonplus : onadepasselemilieudelachaneenayantcomparedeux`adeuxtouslescaract`eres,doncsestunpalindrome.Cetexemplenousfournitnotrepremi`erefonctionrecursive`aplusieursarguments.# let rec palin s i j =(i >= j) || (s.[i] = s.[j]) && (palin s (i + 1) (j - 1));;palin : string -> int -> int -> bool = # let palindrome s = palin s 0 (string_length s - 1);;palindrome : string -> bool = # palindrome "eluparcettecrapule";;- : bool = trueOn simplie encore un peu ce programme en ecrivant la palin `a linterieur depalindrome, ce qui lui ote largument s, qui est lie par lafonctionpalindromeetqui est doncvisiblepar lafonctionlocale palin. Cest laversionlaplus jolie. Parcoquetterie,nousavonsaussisupprimelesparenth`esesautourdestests,carellessontimplicites.# let palindrome s =let rec palin i j =i >= j || s.[i] = s.[j] && palin (i + 1) (j - 1) inpalin 0 (string_length s - 1);;palindrome : string -> bool = # palindrome "tulaStroP ecras eC esarc ePortSalut";;- : bool = true2.2 Denitionsparcas : leltrageNousavonsdonneladenitionrecursivesuivantedelafonctionfactorielle :let rec factorielle n = if n = 0 then 1 else n * factorielle (n - 1);;Camldisposedunemani`ereencoreplusconcisededenircettefonction:lanalysedecas. Il yaici deuxcaspossiblespourlargumentdefactorielle, oubiencest0oubienilestdierentde0.Onlecritainsi :# let rec factorielle = function| 0 -> 1| n -> n * factorielle (n - 1);;factorielle : int -> int = 28 RecursiviteLanalysedecas| 0 -> 1 | n -> n * factorielle (n - 1)signiesimplement :si largumentdelafonctionest0alorsrenvoyer1, sinonnommernlargumentdelafonction et retourner n*factorielle(n-1). La barre verticale | introduit donclescasetcorrespond`aun oubien, tandisquela`eche -> indiquecequil fautcalculerdanslecascorrespondant.Lanalyse de cas porte le nom technique de ltrage que nous emploierons desormais.Le ltrage est un trait extremement puissant de Caml. Il est integre dans de nombreusesconstructionsdulangageettr`esfrequemmentemployedanslesprogrammes.Il arrive dans certains cas quon nutilise pas largument de la fonction pour calculerleresultat :# let egal_un = function | 1 -> true | x -> false;;egal_un : int -> bool = Pourbienmontrerquelenomxnesert`arien, puisquesavaleurnestpasnecessairepourretournerfalse, onsesertdunsymbolespecial _ (lesouligne), qui signie danstouslesautrescas :# let est_un = function | 1 -> true | _ -> false;;est_un : int -> bool = # est_un 1;;- : bool = true# est_un 0;;- : bool = falseNous abordons maintenant un probl`eme apparemment tr`es dicile, quune fonctionrecursiveresoutsansdiculteetavecunegrande elegance.2.3 LestoursdeHanoiLalegendeLe jeu des tours de Hanoi consiste en une plaquette de bois sur laquelle sont planteestroistiges. Surcestigessontenlesdesdisquesdeboisdontlesdiam`etressonttousdierents. Laseuler`egledujeuestdenejamaisposerundisquesurundisquepluspetitquelui, etdenedeplacerquunseul disque`alafois. Audebutdujeutouslesdisques sont poses sur la tige de gauche. Le but du jeu est de deplacer les disques dunetigesurlautre,sansjamaisviolerlar`egle,pournalementlesamenertoussurlatigededroite.Lejeuoriginal etaitaccompagnedunenoticeracontantlalegendedemoinesduntempledeHanoiquipassaientleurtemps`aresoudrecejeupouratteindrelenirvana.En eet, les moines croyaient que la n du monde arriverait lorsque le jeu serait acheve.Leurjeugrandeurnatureoccupaitlacourduntemple.Ilsecomposaitde64disquesdoretdetroistigesdivoiredunm`etredehaut. Cettelegendeaeteinventeeparlemathematicienfran caisEdouardLucasen1883.Notre but est decrire unprogramme qui indique les mouvements `a faire pourresoudre lejeu. Si vous netes pas tropimpatient, cherchezquelques instants vous-meme lasolution. Cest dicile, nest-ce pas ?Et pourtant, ce jeuest etrangementfacile`aresoudreavecuneprocedurerecursive.LestoursdeHanoi 29AplusgrosdisqueautresdisquesB CDeplacerlesautresdisquessurBenrespectantlar`egle :hanoi A C B (n - 1)A B CplusgrosdisqueautresdisquesDeplacerledisquerestantversC :mouvement A CA B CplusgrosdisqueautresdisquesDeplacerlesautresdisquesdeBversCenrespectantlar`egle :hanoi B A C (n - 1)A B CplusgrosdisqueautresdisquesFigure2.1:Commentresoudreleprobl`emedestoursdeHanoi.30 RecursiviteLeprogrammeSupposonsquelestigessappellentA,BetC,quensoitlenombrededisques,tousposesaudepartsurlatigeA, etquenousdevionslesmettresurlatigeC. Lastuceconsiste`aserendrecomptequesi noussavionscommentresoudreleprobl`emepourn 1disquesalorsnoussaurionslefairepourn, sansviolerlar`egle. Eneet, si lonsupposelesn 1disquesdej` aposessurlatigeB,ledernierdisqueencoreposesurlatigeAestleplusgrosdisque. Il noussutdoncdeleposersurlatigeCqui estvide(pasdeviolationpossibledelar`egle),puisdedeplacerlesn 1disquesdelatigeB`ala tigeC. Cest possible puisque nous supposons savoir comment deplacer n1 disquesdunetige`auneautreetpuisquecestleplusgrosdisquequiestmaintenantposesurC, il ny a pas de violation de la r`egle en posant les n1 disques deB sur la tigeC (voirla gure 2.1). Mais nous savons aussi resoudre le probl`eme pour 0 disques : il ny a rien`afaire.Noussavonsdoncresoudreleprobl`emedestoursdeHanoipourtoutn.Cestencore plus facile `a dire en Caml : nous denissons dabord une fonction auxiliaire pourimprimerlesmouvements,puislaprocedureprincipale.# let mouvement de vers =print_string("D eplace un disque de la tige " ^ de ^ " ` a la tige " ^ vers);print_newline ();;mouvement : string -> string -> unit = # let rec hanoi d epart milieu arriv ee = function| 0 -> ()| n -> hanoi d epart arriv ee milieu (n - 1);mouvement d epart arriv ee;hanoi milieu d epart arriv ee (n - 1);;hanoi : string -> string -> string -> int -> unit = Les noms des arguments d epart, milieu et arriv ee sont echanges lorsque nous voulonsdeplacer les disques dune tige `a lautre : par exemple, pour deplacer un disque de la tigedenomd epartverslatigeargumentmilieu, nousecrivonshanoi d epart arriv eemilieu 1.# hanoi "A" "B" "C" 3;;D eplace un disque de la tige A ` a la tige CD eplace un disque de la tige A ` a la tige BD eplace un disque de la tige C ` a la tige BD eplace un disque de la tige A ` a la tige CD eplace un disque de la tige B ` a la tige AD eplace un disque de la tige B ` a la tige CD eplace un disque de la tige A ` a la tige C- : unit = ()Veriez : le casse-tete est resolu. Cest magique ! Onna pas vraiment limpressiondavoir ecritunprogrammesisavant. . .Nevousinquietezpassi vouseprouvezdesdicultes`acomprendrelaprocedurehanoi.Cestnormal,carcestlepremierexempledeprogrammeCamlquinousoblige`a changer franchement notre fa condapprehender les programmes. Eneet, il estextremement diciledecomprendrecomment laproceduremarche. Aucontraire, ilNotionsdecomplexite 31fautsedemanderpourquoiellemarche.Lepourquoiestsimple :ilestenti`erementcon-tenu dans la gure 2.1. Si vous etes persuade du bien-fonde de la methode de resolutionquelaguresugg`ereetquevousetesconvaincuquelaprocedurehanoiimplementecorrectementcettemethode, alorsnecherchezpasplusloin: vousaveztoutcompris.Si enrevanchevousessayezdesuivrelederoulementdesappelsrecursifsetlesper-mutations darguments qui sederoulent `alexecutiondelaprocedure(par exempleenutilisant latracedeCaml), vous serezviteperdu. Enfait, memesi vous suiviezprecautionneusementcederoulementvousnenapprendriezpasplus, si cenestque camarche, puisquevousconstateriezquelesbonsargumentssemettentenplaceaubonmomentpourproduirelesbonsresultats, commeparmiracle. Il fautsedecider`apenserquecesuivi pas`apasduderoulementdesprogrammesestduressortdelamachineexclusivement. Notrecomprehensionestdebienplushautniveau: ellecon-sisteessentiellement `aprouver queleprogrammenepeut quemarcher ; comment leprogrammeparvienteectivementaubonresultatnenousregardepas.Ilestheureuxque cette noble activite de reexion sur le bien-fonde dune methode de resolution dunprobl`emenousappartienneenpropre,alorsquenousdeleguonsauxmachineslamiseenuvreeective.Un equilibre secreeainsi :silareexionsurlamethodeesthorsdeporteedelamachine, lagestionsanserreursdespassagesdeparam`etresetlareprisedes appels recursifs ensuspens est unjeudenfant pour lamachine, alors quenousserionsincapablesdunetellerigueur. Rendonsdoncauxmachinescequi appartientauxmachines.Pourceuxquecelainteresse,lasectionsuivanteesquisselesfondementstheoriquesdelamethodequiexpliquepourquoilaprocedurehanoimarcheeectivement.Acces-soirement, il permetaussi decalculerladate`alaquellelesmoinesaurontacheveleurtravail etdonnedoncunebonneideedeladatedelandumonde ! Si toutcelanevouspreoccupepas,passezdirectementauchapitresuivant.2.4 NotionsdecomplexiteLacomplexiteestletudedunombredoperationsnecessaires`alach`evementduncalcul. Uneanalysedecomplexitepermetdoncdesefaireuneideedutempsdecal-cul necessaire`alach`evementdunprogramme, enfonctiondelargumentqui lui estsoumis. Engeneral,oncomptelenombredoperationselementaires(additions, multi-plications, soustractionsetdivisions, comparaisonsdevaleurs, aectationsdelementsde tableau) et/ou le nombre dappels de fonctions. Par exemple, la fonction successeurdemandeuneseuleoperation,quelquesoitsonargument.Enrevanche,lacomplexitedelafonctionfactorielledependdesonargument :elledemandenoperationspourlargument n. Plus precisement, il faut n multiplications, n+1 appels recursifs `a la fonc-tionfactorielleetnsoustractions.Silonconsid`erequecestroistypesdoperationsont des co uts voisins, alors la complexite de factorielle est de lordre de 2n+(n+1),cest-` a-diredelordrede3n. Onconsidereradoncquelafonctionfactorielleaunecomplexitequi augmenteaumemerythmequesonargument, cequonnoteO(n)etquonprononce grand-oden. Plusprecisement, O(n)signie uncertainnombredefois n, plusdestermesnegligeablesdevantnquandndevientgrand, commeparexemple une constante. On ne sinteresse en eet qu`a un ordre de grandeur de la com-32 Recursiviteplexite : cettecomplexiteaugmente-t-ellecommelargument(algorithmelineaire), oucomme le carre de largument (algorithme quadratique), ou comme une exponentielle delargument(algorithmeexponentiel ) ?Danslecasdefactorielle,onresumeletudeennotantunecomplexitelineaireO(n),puisquelacomplexitereelleest3n + 1.PrincipederecurrenceLesetudesdecomplexiteetlesdenitionsrecursivesdefonctionsreposentsurunraisonnementsimplesurlesproprietesquiconcernentlesnombresentiers :leprincipederecurrence.Nousallonslexpliquer,puislutiliserpourdemontrerdesproprietesdelafonctionhanoi.Leprincipederecurrencesenonceinformellementainsi : si unecertaineproprietesurlesnombresentiersestvraiepour0etsi laproprieteestvraiepourlesuccesseurdunnombred`esquelleestvraiepourcenombre,alorscetteproprieteestvraiepourtouslesnombres.Formellement :soitP(n)uneproprietequidependdunentiern.Silesphrasessuivantessontvraies :1. P(0)estvraie,2. siP(n)estvraiealorsP(n + 1)estvraie,alorsP(n)estvraiepourtoutn.Ceprincipeestenfaitevident : lesdeuxproprietesdemandeesparleprincipederecurrence permettent facilement de demontrer la propriete Ppour toute valeur enti`ere.Parexemple, supposonsquePverielesdeuxproprietesetquonveuilledemontrerque Pest vraie pour 2. Puisque Pest vraie pour 0 elle est vraie pour son successeur, 1.MaispuisquePestvraiepour1elleestvraiepoursonsuccesseur,doncelleestvraiepour2. Il estclairqueceraisonnementsepoursuitsansprobl`emepourtoutnombreentierxe`alavance.Cest ceprincipequenous avons utilisepour resoudreleprobl`emedes tours deHanoi :1. nousavonsmontrequenoussavionsleresoudrepour0disque ;2. nousavonsmontrequensachantleresoudrepourn 1disquesnoussavionsleresoudrepourndisques.Ces deux cas correspondent exactement aux deux clauses de la fonction hanoi (cas 0->et cas n->). Le principe de recurrence nous prouve donc que nous savons eectivementresoudre le probl`eme pourtout n, meme si cela ne nousapparaissaitpas clairement audepart.La diculte intuitive de ce genre de denitions recursives est doser utiliserlhypoth`esederecurrence :ilfautsupposerquonsaitdej` afairepourn 1disquesetecrireleprogrammequiresoutleprobl`emepourndisques.Danslaprocedurehanoi,onsuppose ainsi deuxfois que lafonctionsaurabienfaire toute seule pour n1disques et lonnesoccupequededeplacer legros disque, cequi sembleuntravailfacile. Finalement, onalimpressiondevoir tourner ducodequelonnapasecrit,tellementilsembleastucieux` alexecution.Lecrituredefonctionsrecursivessereduitainsitr`essouventauschema :Notionsdecomplexite 33let rec f = function| 0 -> solutionsimple | n -> ... f (n - 1) ... f (n - 1) ...;;Ondemontreenmathematiquesquilnestpasinterditdappelerfsurdautresargu-mentsquen - 1, pourvuquilssoientpluspetitsquen(parexemplen - 2), maisalors il faut prevoir dautres cas simples (par exemple1 ->). Un exemple de ce schemadeprogrammeestlafonctiondeFibonaccideniepar :# let rec fib = function| 0 -> 1| 1 -> 1| n -> fib (n - 1) + fib (n - 2);;fib : int -> int = # fib 10;;- : int = 89Remarquezquecettefonctionfaiteectivementdeuxappelsrecursifssurdeuxvaleursdierentes,maistouteslesdeuxpluspetitesquelargumentdonne.ComplexitedelaprocedurehanoiIl est facile decrire un programme qui compte le nombre de mouvements necessairespour resoudre le jeu pour n disques : il y a 0 mouvement `a faire pour 0 disque, lappel `ala procedure mouvement produit 1 mouvement et le nombre de mouvements necessairesauxappelsrecursifsestforcementcompteparlafonctionrecursivedecomptagequenoussommesentraindedenir.Eneet,onsupposeunefoisdeplusquepourn 1lafonction saitfaire etonsecontentedetrouverleresultatpourn.# let rec compte_hanoi d epart milieu arriv ee = function| 0 -> 0| n -> compte_hanoi d epart arriv ee milieu (n - 1) + 1 +compte_hanoi milieu d epart arriv ee (n - 1);;compte_hanoi : a -> a -> a -> int -> int = Lesargumentscontenantlesnomsdestigessontbiens urinutilesetilsutdecrire :# let rec compte_hanoi_na f = function| 0 -> 0| n -> compte_hanoi_na f (n - 1) + 1 + compte_hanoi_na f (n - 1);;compte_hanoi_na f : int -> int = quonsimplieencoreen# let rec compte_hanoi = function| 0 -> 0| n -> (2 * compte_hanoi (n - 1)) + 1;;compte_hanoi : int -> int = # compte_hanoi 3;;- : int = 7# compte_hanoi 10;;- : int = 1023# compte_hanoi 16;;- : int = 65535Ondevinelaproprietesuivante :pourtout n,compte_hanoi (n) = 2n1.Nousallonsla demontrer en utilisant le principe de recurrence. Nous denissons donc formellement34 RecursivitelaproprietePpar : P(n)estvraiesi etseulementsi compte_hanoi(n)=2n 1. LapropositionP(0)estvraiecarcompte_hanoi (0) = 0et201 = 1 1 = 0.SupposonsP(n) vraieet montrons qualorsP(n + 1) est vraie. Pour montrer P(n + 1), il fautdemontrercompte_hanoi (n + 1) = 2n+11.Or,dapr`esladenitiondelafonctioncompte_hanoi,ona :compte_hanoi (n + 1) = 2 compte_hanoi ((n + 1) 1) + 1,soit compte_hanoi (n+1) =2compte_hanoi (n)+1. Mais, par hypoth`ese derecurrence, P(n) est vraie, donc compte_hanoi (n) = 2n1. En reportant dans legaliteprecedente,onobtient :compte_hanoi (n + 1) = 2 (2n1) + 1.Mais2 (2n1) + 1 = 2n+12 + 1 = 2n+11,donccompte_hanoi (n + 1) = 2n+11etP(n + 1)estvraie.Ilsensuit,dapr`esleprincipederecurrence,queP(n)estvraiepourtoutn.Aveccenouveauresultat,noussommesautorises`aredenircompte_hanoicommelafonctionqui `anassocie2n 1. Pour avoir uneideedunombredemouvementsnecessaires pour resoudre le probl`eme avec 64 disques, nous sommes obliges de faire lescalculs en virgule ottante car le resultat exc`ede de beaucoup la limite superieure desentiersrepresentablesenCaml.Nousreviendronsplustardsurlesnombresenvirguleottante,aussiappelesnombresottants(chapitre8).Pourlinstantilsutdesavoirquun nombre ottant est caracterise par le point qui prec`ede sa partie decimale et queles operations associees aux ottants sont suxees egalement par un point (+.,-.,*.,etc.).Nousimplementonsdoncnotrefonctionenutilisantlafonction puissance desnombresottants(power).# let compte_hanoi_rapide n = power 2.0 n -. 1.0;;compte_hanoi_rapide : float -> float = # compte_hanoi_rapide 64.0;;- : float = 1.84467440737e+19UnalgorithmecorrectmaisinutilisableGr ace`anotredemonstrationmathematique,nousavons etabliuneformuledecal-culdirectdunombredemouvementsnecessaires`alaresolutiondujeupour ndisques.Nousavonsainsitr`esfortementaccelerelafonctioncompte_hanoi.Cetaitindispens-able car notre premi`ere version, la fonctioncompte_hanoi_naf, quoique parfaitementcorrecte dun point de vue mathematique, naurait pas pu nous fournir le resultat pour64. Eneetcetteversioncalculesonresultatenutilisantuniquementladdition. Plusprecisement, ellenajoutetoujoursquedes1 : il lui auraitdoncfallufaire264 1ad-ditions. Memeensupposantquonfasse1milliarddadditionsparseconde,cequiest`alalimitedelatechnologieactuelle,ilauraitfallu,avecleprogrammedelapremi`ereversiondecompte_hanoi,Notionsdecomplexite 35# let nombre_de_secondes_par_an = 3600.0 *. 24.0 *. 365.25;;nombre_de_secondes_par_an : float = 31557600.0# let nombre_dadditions_par_an = nombre_de_secondes_par_an *. 1E9;;nombre_dadditions_par_an : float = 3.15576e+16# compte_hanoi_rapide 64.0 /. nombre_dadditions_par_an;;- : float = 584.542046091cest-` a-dire plus de 584 annees pour achever le calcul ! Nous sommes donc ici en presencedune fonctionqui donne eectivement le bonresultat ausens des mathematiques,maisquilecalculetellementlentementquelledevientinutilisable.`Aladierencedesmathematiques, il ne sut donc pas en informatique decrire des programmes corrects,il faut encore que leur complexite ne soit pas trop elevee pour quils calculent le resultatcorrectenuntempsraisonnable.La fonction compte_hanoi_nave necessite 2n1 additions pour largument n. Sontempsdecalcul estdoncproportionnel `aunepuissance(2n)dontlexposantestsonargument n: lalgorithme est exponentiel. La seconde version utilisant la multiplicationnecessitenmultiplications, lalgorithmeestdonclineaire. Unalgorithmelineairede-mandeuntempsdecalculquiaugmentecommelavaleurdesonargument(O(n)),cequiestraisonnable.Eneet,cetteversionnousauraitpermisdobtenirnotreresultat,puisquepourn=64il auraitfallu64multiplicationsseulement. Laderni`ereversion,quant `a elle, est entempsconstant. Elle ne necessite que deux operations ottantes quelquesoitsonargument : cestlalgorithmeideal. Onretiendraquunalgorithmeexpo-nentielestvitesusceptibledexigeruntempsdecalculprohibitifquandsonargumentaugmente.DatedelandumondeCalculonslenombredanneesnecessairesauxmoinespour achever leur jeu`a64disques. Supposonsquilspuissenteectuersansarret, jouretnuit, dixmouvementspar secondes, ce qui est vraiment le maximum quon puisse exiger de ces pauvres moines.Illeurfaudraitalors :# let nombre_de_mouvements_par_an =nombre_de_secondes_par_an *. 10.0;;nombre_de_mouvements_par_an : float = 315576000.0# compte_hanoi_rapide 64.0 /. nombre_de_mouvements_par_an;;- : float = 58454204609.1soitplusde58milliardsdannees.CestbeaucoupplusqueladureedevieestimeeduSoleil.Ilsembledoncquelheuredelandumondeaurasonnetr`eslongtempsavantlandujeu!Calcul delacomplexitedelasecondeversionDans la section precedente, nous avons arme que la seconde version decompte_hanoi :# let rec compte_hanoi = function| 0 -> 0| n -> 2 * compte_hanoi (n - 1) + 1;;compte_hanoi : int -> int = 36 Recursivitenecessitait n multiplications. La demonstration en est tr`es simple. Nous noteronsOp(compte_hanoi (n)) le nombre doperations necessaires pour eectuer le calculde compte_hanoi(n) `alaide de cette versionde compte_hanoi. Nous demontronspar recurrence la propriete P(n) denie par : P(n) est vraie si et seulement siOp(compte_hanoi (n)) = n. La propriete P(0) est vraie car Op(compte_hanoi (0)) = 0.Supposons P(n) vraie et montrons qualors P(n+1) est vraie. Pour montrer P(n+1),il faut demontrer Op(compte_hanoi (n+1)) =(n+1). Or, dapr`es le code dela fonction compte_hanoi, quand on a le resultat de compte_hanoi (n - 1), ilfaut faire une multiplicationde plus pour obtenir compte_hanoi (n). Onadonc :Op(compte_hanoi (n+1)) = 1 +Op(compte_hanoi (n)) ;mais,dapr`es lhypoth`esederecurrence,Op(compte_hanoi (n)) = n,etdoncOp(compte_hanoi (n +1)) = n +1.IlsensuitqueP(n)estvraiepourtoutn.Remarquonspournirquenousavonscalculelacomplexitedehanoienutilisantlafonctioncompte_hanoi, dontnousavonsd u`anouveauetudierlacomplexite,pourloptimiser(souspeinedenepasobtenireectivementlacomplexitedehanoi).Ilfautdecidementreechirsurlesprogrammesquon ecrit. . .3ProgrammationimperativeO` ulonapprendque2x + 2xfont4x.ousmettonsenplace dans ce chapitre quelques outils indispensables `a la pro-grammationimperative. Enparticulier, nousintroduisonslanotiondetableau,etlutilisonspourcalculerdesidentitesremarquables.Nousseronsparexempleenmesuredetablirparprogrammelaformule(x + 1)2=x2+ 2x + 1. Entermessa-vants nous ferons du calcul formel sur des polyn omes `a une indeterminee. Si vous savezdej` aquil yaautrechosedanslaviequelaprogrammationfonctionnelleetquevousconnaissezlesboucles for et while ,vouspouvezsautercechapitre.3.1 LaprogrammationimperativeJusqu`apresent, nousavonsecritdepetitsprogrammesdansunsous-ensembledeCaml : lapartiedeclarative, laplusprochedesmathematiques. Nousavonstoujoursdeni desfonctionsqui retournaientleresultatquenousvoulionscalculer. Cesfonc-tionscalculentleresultatsouhaiteausensdescalculsmathematiques,cest-` a-direparsimplications successives dune expression. Ce style de programmation`alaide defonctionssappellelaprogrammationfonctionnelle.Une autre fa conde calculer consiste `aconsiderer quuncalcul est unprocessusevolutif,o` uletempsasonimportance.Ilsagitdemodierunetat :lordinateurcom-mencelexecutionduprogrammedansuncertainetatinitial, quelexecutiondupro-gramme modie jusqu`aparvenir `aunetat nal qui contient le resultat voulu. Onchangeletat courant par modicationducontenudelamemoiredelordinateur (`alaidedaectations),ouencoreparinteractionaveclemondeexterieur :interrogationdelutilisateur,achagederesultats,lectureou ecrituredechiers,breftoutcequonnomme les entrees-sorties. Toutes ces operations qui modient physiquement le contenudesadressesmemoiresontappeleeseets(ouencoreeetsdebord) :Un eet est une modication dune case de la memoire (tableau oureference), ouencoreuneinteractionaveclemondeexterieur(impressionoulecture).38 ProgrammationimperativeCestyledeprogrammationpareetssappellelaprogrammationimperative. Cenomprovient evidemment de lasignicationdumode imperatif dans laconjugaisondesverbes. Eneet, les programmes imperatifs decrivent explicitement `alamachinelasuitedesoperations`aeectuer(faisci,fais ca).Aucontraire,enprogrammationfonc-tionnelle, on laisse la machine calculer le resultat comme elle le peut `a partir dune for-mule,sansluiprecisercompl`etementlordredanslequelelledoitoperer.Parexemple,pourcalculerlecarredunnombrex,onecritx * xenprogrammationfonctionnelle.Aucontraire,unemethodeimperativeseraitdereserverunecasememoirecommeac-cumulateur, delinitialiseravecx, puisderemplacerlecontenudecetaccumulateurpar soncontenumultipliepar lui-meme. Leresultat chercheserait maintenant danslaccumulateur.Dansuncassisimple,cesdescriptionssont evidemmentcaricaturales,maislideeestlabonne.Le style imperatif implique la modication de letat de la memoire, donc lutilisationdestructuresdedonneesmodiables(parexemplelestableauxdontles elementspeu-vent etre changes dynamiquement) et lemploi de commandes. Les commandes sont desexpressions qui ne retournent pas de valeurs interessantes ; leur resultat est simplementune modication de letat courant, cest-` a-dire un eet. Lorsquune fonction se contentedexecuteruneseriedecommandes,onlappellesouventprocedure.UneprocedureenCaml est donc simplement une fonction qui se contente de faire des eets, sans produirederesultatausensmathematique.Nous aurions pu nous cantonner au sous-ensemble fonctionnel de Caml et cependantecrire de tr`es jolis programmes. Mais ce ut ete donner une fausse image de Caml : ce quifaitlapuissancedulangagecestjustementquilneselimitepas`alaprogrammationfonctionnelle, mais int`egre harmonieusement programmation fonctionnelle et program-mation imperative. De plus, nous cherchons avant tout `a vous montrer les programmeslesplussimplesetlesplusclairspossibles : nousavonsdoncbesoindetouslesoutilsqueCamlmet`anotredisposition.Desurcrot, laprogrammationimperativenestpasseulementindispensablepourtraiterlesprobl`emesdinteractionaveclemondeexterieur(entrees-sorties).Danscer-tainscasunalgorithme, cest-` a-direunemethodederesolutiondunprobl`eme, exigemoins de calculs lorsquil est ecrit en style imperatif que lorsquil est ecrit en style fonc-tionnel. Enn, certains algorithmes sexpriment naturellement entermes devolutiondun etat ;laprogrammationimperativesimposealors.Nousavonspourlinstantillustreleseetsdentrees-sorties, plusprecisementlesimpressions. Nousallonsmaintenantfairedeseetssurlamemoire, cequonappelleaussi des modications physiques ou modications en place de donnees. Pour cela il nousfautdisposerdecasesmemoiremodiablesparleprogramme.Camlproposepourcelales notions dereferences et detableaux. Nous commen cons par etudier les tableaux, quisontplussimples.Puisque la notion de temps intervient en programmation imperative, il nous faut unmoyen de specier au langage fait ceci dabord et fait cela ensuite : cest la notiondesequencequenousavonsdej` avueauchapitre1. Nousavonsegalementbesoinderepeterdessuitesdeets :cestlanotiondeboucles.Nousdecrivonscesconstruction,puisappliquonscesoutilsaucalculsurlespolyn omes.Boucles 393.2 BouclesCaml fournitdeuxsortesdebouclespourrepeterdeseets : laboucle pour etlaboucle tant que . Laboucle pour rep`eteuncalcul unnombredefois xe`alavance ;laboucle tantque rep`eteuncalcultantquuneconditionrestevraie.Boucle tantque Boucle tantque ::= whileexpression (while :tantque)doexpressiondone (do :faire,done :fait)Lasignicationdewhileconditiondoactionsdoneestsimplementdefairelesactionstant que lacondition est vraie. La condition est testee au debut de chaque iteration. Sielleestinitialementfausse, lesactionsnesontjamaisexecutees. Danscertainscas, laboucle tant que sert `a repeter indeniment les memes actions jusqu`a un evenementexceptionnel.Danscecas,laconditiondeboucleesttoutsimplementlebooleentrue,commedanswhile true doactionsdone.Boucle pour Boucle pour ::= forident=expression (for :pour)(to|downto)expression (to :jusqu`a,down:enbas)doexpressiondone (do :faire,done :fait)Lasemantique, cest-` a-direlasignication, delexpressionfor i =debut tondoactionsdoneestdefairelesactionsaveci = debut,puisaveci = debut +1etainsidesuite, jusqu`a i = n. En particulier, si debut > n, on nevalue jamais actions. Pour laversiondownto,ondecrementelindicedebouclei(onluisoustrait1)`achaquetour,au lieu de lincrementer (lui ajouter 1). Lindice de boucle est forcement du type entier.Lenomassocie`alindicedeboucleestintroduitparlaboucle(commeparuneliaisonlet) ; saliaisonnestvalidequependantlecorpsdelaboucle. Prenonsunexemplesimple : nous imprimons les dix chires `a laide dune boucle de 0 `a 9. Nous denissonsuneprocedureimprime_chiffredontlargumentest rien , etnousladeclenchonsenlappliquant`a rien.# let imprime_chiffres () =for i = 0 to 9 doprint_int idone;print_newline ();;imprime_chiffres : unit -> unit = # imprime_chiffres ();;0123456789- : unit = ()40 Programmationimperative3.3 ManipulationdepolynomesNous continuons lapprentissagedelaprogrammationimperativepar letudedestableaux.`Atitredillustration,nousecrivonsunjeudefonctionsquiimplemententlesoperationsdebasesurlespolyn omes. Avantdenouslancerdanslaprogrammation,nousrappelonsbri`evementcequesontlespolyn omes.Lespolynomes`auneindetermineeDes classes elementaires, onretient souvent quunpolyn ome est une somme depuissances de x. Par exemple, p = x2+2x+3 est un polyn ome. La variable x est appeleelindeterminee du polyn ome. Un polyn ome est une somme de termes elementaires quonnomme mon omes (par exemple x2et 2x).Etant donnee une variablex, onappellemon omedecoecientaietdedegreilexpressionai xi. Ledegredunpolyn omeestcelui de son mon ome de plus haut degre. On rappelle que x1= x et x0= 1. Le mon omededegre0est doncreduit ` auneconstante(cest 3pour p) et celui dedegre1auproduit dun nombre par lindeterminee (cest 2x pour p). Dautre part, nous utiliseronslapropriete :pourtoutnetmentierspositifs, xnxm= xn+m.Nousmodelisonslespolyn omes`alaidedetableauxdentiers :letableaudescoef-cientsdeleursmon omes. Lesdegresserontdoncimplicites, simplementdeterminesparlindiceducoecientdansletableauquirepresentelepolyn ome.Parexemple,lepolyn omep = x2+ 2x + 3serarepresenteparletableaucontenantlesnombres3,2,1dans cet ordre, puisque 3 est le coecient de degre 0 de p, 2 est le coecient de degre 1et1lecoecientdedegre2.Nousetudionsdoncmaintenantbri`evementlestableauxdeCaml.TableauxLes tableaux, aussi appeles vecteurs , sont des suites nies et modiables devaleurs dunmemetype. Leur typeest noteavect (o` uasignie nimportequeltype ). Puisque les elements des tableauxsont tous de lameme nature (dumemetype), on qualie les tableaux de suiteshomog`enes de valeurs. Les valeurs dun tableausontenregistreesdansdescellules dememoire consecutives.Lespositionsdes elementsdansuntableaudebutent`alaposition0.ConstructiondetableauxUntableausedenitdedeuxfa cons : soitendressantdirectementlalistedeseselements, soit encreant letableauet enremplissant ses cases ulterieurement. Si untableauestdeniparlalistedeses elements,cettelisteestentoureesdessymboles[|et |], tandis que les elements sont separes par des ; . Notre polyn ome p = x2+2x+3sedenitdoncparlaphrase :# let p = [| 3; 2; 1 |];;p : int vect = [|3; 2; 1|]Graphiquement, onrepresente naturellement les tableauxpar une successiondecases.Parexemple,pserarepresenteainsi :Manipulationdepolyn omes 41p 3 2 10 1 2. .indicesPourconstruiredestableauxdontonrempliralescasesplustard,ondisposedelafonctionpredeniemake_vect.Aveccettefonction,oncreeuntableauendonnantsatailleetunelementquiseramisaudepartdanstouteslescasesdutableau:lavaleurdinitialisation du tableau. Denissons par exemple un tableau de taille 4 contenant des2etuntableaudetaille3contenantlachane"Bonjour" :# let q = make_vect 4 2;;q : int vect = [|2; 2; 2; 2|]# let r = make_vect 3 "Bonjour";;r : string vect = [|"Bonjour"; "Bonjour"; "Bonjour"|]Latailleduntableausobtientenappelantlaprimitivevect_length.# vect_length q;;- : int = 4Unefoisletableaucree,onpeutconsulteretmodierlecontenudesescases.Sitestuntableauetnunentier,t.(n)designelelementdindicendutableaut.tt.(0) t.(n) t.(vect_length(t)1)# let a0 = p.(0);;a0 : int = 3Onaectelavaleurv`alacasendutableautparlaconstructiont.(n) bool = 3.6 LesreferencesLesreferencessontdesstructuresdedonneespredeniesqui modelisentlescasesmemoiredelamachine.Laproprietecaracteristiquedescasesmemoireestquonpeutles lire et les ecrire : la lecture renvoie leur contenu courant, lecriture change ce contenu.Les cases memoire sont utilisees pour representer des compteurs ou des accumulateurs,dontlecontenu evolueaucoursducalcul.LireetecrirelescasesmemoirePoursuivant lanalogie avec les cases memoire, vous pouvez vous gurer une referencecomme une bote (la case memoire) qui contient une valeur : vous pouvez placer quelquechose dans la bote (ecriture), ou demander `a ouvrir la bote pour examiner son contenu(lecture). Lesreferencessontcreees`alaidedelaconstructionref(val), o` uval estlavaleurinitialementcontenuedanslareference. Denissonsparexempleuncompteurquivautinitialement0 :# let compteur = ref 0;;compteur : int ref = ref 0Lavaleurdecompteurestdoncunebotecontenant0,quonpeutrepresenterainsi :compteur 0Lecontenucourant dune referenceest renvoyepar loperateur de dereferencement,cest-` a-dire de lecture dune reference, note ! . (Il ne faut pas confondre cette notationavecloperateurfactorielledesmathematiques,quenousavonsvuauchapitre2,etquiseplaceapr`essonargument ;le!Camlseplaceavantsonargument.)48 Programmationimperative# !compteur;;- : int = 0compteur 0!Onchangelecontenudunereference(ecriture)enutilisantlesymboletraditionneldelaectation := .# compteur := 2;;- : unit = ()Laectationest,graphiquement,loperationinversede ! :compteur 2:=Apr`eslaectation,lecontenudelabotecompteurestdonc2.# !compteur;;- : int = 2Pourincrementercompteur,nousdevonsajouter1aucontenucourantdelabote :# compteur := 1 + !compteur;;- : unit = ()# !compteur;;- : int = 3Une r`egle generale en Caml est que tous les objets du langage sont manipulables commedesvaleurssansrestrictionsparticuli`eres :onlespasseenparam`etreetonlesrendenresultat,aumemetitrequelesvaleursdebase.Lesreferencesnederogentpas`acetter`egle.Nouspouvonsdoncpasserdesreferencesenparam`etreetdeniruneprocedurequi incremente le contenude nimporte quelle reference contenant unentier (cetteprocedure est predenie sous le nomincr dans le syst`eme Caml, mais nous en ecrivonslecode`atitredexemple). Lafonctionprendunereferencecenargumentetmodiesoncontenu(c :=. . . )pourymettrelavaleurcourantedelareferenceplusun(1 +!c) :# let incremente c = c := 1 + !c;;incremente : int ref -> unit = # incremente compteur; !compteur;;- : int = 4LesvariablesimperativesUnidenticateurlie`aunereferencesecomportecommelesvariablesdeslangagesimperatifs (C, Pascal, Ada), puisquon peut modier `a volonte le contenu de la reference.LaseuledierenceestquenCaml il fautexplicitementdereferencerlidenticateur`alaidedeloperateur!,pourenobtenirlavaleurcourante ;ladistinctionentrelobjetvariableetlavaleurcourantedecetobjetestdoncplusnette.Commenouslavionsannonce`alasection1.3, ladenitiondunnomparlacon-struction let est dierente de laectation dune variable. Nous sommes maintenant enmesure de comprendre cette dierence, en comparant laredenition dun identicateurparunnouveauletetlaectationdunidenticateurlie`aunereference :Unprogrammeutilisantdesreferences 49# let x = 1;;x : int = 1# let f y = x + y;;f : int -> int = # let x = 2;;x : int = 2# f 0;;- : int = 1# let x = ref 1;;x : int ref = ref 1# let f y = !x + y;;f : int -> int = # x := 2;;- : unit = ()# f 0;;- : int = 2Dans la colonne de gauche, la redenition dex ne modie en rien la valeur dex dans lecorps de la fonction f. En revanche `a droite, lidenticateur x est lie `a une reference. Lavaleur de !x dans le corps de f change donc evidemment apr`es laectation (cependant xest toujours lie `a la meme valeur : la meme reference). On constate ainsi que les fonctionsqui utilisent des references non locales sont susceptibles de changer dynamiquement decomportement,augredesaectationsdesreferencesquellesemploient.3.7 UnprogrammeutilisantdesreferencesUnexemple realiste dutilisationdes references nous est fourni par la fonction factorielle , qui retourneleproduit des nombres entiers inferieurs ouegaux`asonargument.Nousenavionsdonneladenitionrecursivesuivanteauchapitre2 :n! =_1 si n = 0n (n 1)! sinon.Voici une autre denition, dont nous admettrons quelle est mathematiquementequivalente :0! = 1n! = 1 2 (n 1) nCettedenitionavectroispetitspoints . . . estallusiveetsetraduitgeneralementparuneimplementationsurmachine`abasedebouclesetdaccumulateurs. Ainsi, ondeniraunereferencepouraccumulerlesmultiplicationsparlesnombrespluspetitsquen, durantlexecutionduneboucleallantde1`an.`Achaquetouronmultiplielecontenu actuel de laccumulateur par lindice de boucle courant (accu := i * !accu),si bienqu`alandelabouclelaccumulateurcontientleresultatvoulu; onrenvoiedoncsoncontenu(!accu).# let fact n =if n = 0 then 1 elsebeginlet accu = ref 1 infor i = 1 to n do accu := i * !accu done;!accuend;;fact : int -> int = 50 Programmationimperative# fact 10;;- : int = 3628800Unepetiteremarque :onpeutsupprimerletestif n = 0sanschangerlasemantiquedelafonction.Eneet,lorsquenestnul,labouclesarreteinstantanement,alorsquelareferenceaccuvaut 1, cequi est leresultat correct dans cecas. Onobtient plussimplement :# let fact n =let accu = ref 1 infor i = 1 to n do accu := i * !accu done;!accu;;fact : int -> int = Cet exemple nous permet de comparer `a meilleur escient les styles imperatif et fonc-tionnel. En eet, nous avons d u indiquer `a la machine la suite des operations `a eectuer,engeran