Hovedtrekket til funksjonelle programmeringsspråk. Prosedyremessige programmeringsspråk

Funksjonell programmering kombinerer ulike tilnærminger til å definere beregningsprosesser basert på ganske strenge abstrakte konsepter og metoder for symbolsk databehandling.

Et trekk ved funksjonelle programmeringsspråk er at programtekster på funksjonelle programmeringsspråk beskriver "hvordan løse et problem", men ikke foreskriver en sekvens av handlinger for å løse det. Hovedegenskapene til funksjonelle programmeringsspråk: korthet, enkelhet, sterk skriving, modularitet, tilstedeværelsen av late (late) beregninger.

Funksjonelle programmeringsspråk inkluderer: Lisp, Miranda, Gofel, ML, Standard ML, Objective CAML, F#, Scala, Pythagoras, etc.

Prosedyremessige programmeringsspråk

Et prosedyrespråk lar programmereren definere hvert trinn i prosessen med å løse et problem. Det særegne med slike programmeringsspråk er at oppgaver er delt inn i trinn og løst trinn for trinn. Ved å bruke et prosedyrespråk definerer programmereren språkkonstruksjoner for å utføre en sekvens av algoritmiske trinn.

Prosedyreprogrammeringsspråk: Ada, Basic, C, COBOL, Pascal, PL/1, Rapier, etc.

Stable programmeringsspråk

Et stabel programmeringsspråk er et programmeringsspråk som bruker maskinmodell stable. Stabelbaserte programmeringsspråk: Forth, PostScript, Java, C# osv. Når du bruker stabelen som hovedkanal for å sende parametere mellom ord, danner språkelementer naturlig fraser (sekvensiell kjeding). Denne egenskapen bringer disse språkene nærmere naturlige språk.

Aspektorienterte programmeringsspråk 5) Deklarative programmeringsspråk 6) Dynamiske programmeringsspråk 7) Pedagogiske språk programmering 8) Grensesnittbeskrivelsesspråk 9) Prototype programmeringsspråk 10) Objektorienterte programmeringsspråk 11) Logiske programmeringsspråk 12) Skriptprogrammeringsspråk 13) Esoteriske programmeringsspråk


Standardisering av programmeringsspråk. Programmeringsparadigme

Konseptet med et programmeringsspråk er uløselig knyttet til implementeringen. For å sikre at kompilering av samme program av forskjellige kompilatorer alltid gir samme resultat, utvikles programmeringsspråkstandarder. Standardiseringsorganisasjoner: American National Standards Institute ANSI, Institute of Electrical and Electronics Engineers IEEE, Organization internasjonale standarder ISO.



Når et språk opprettes, utgis en privat standard, bestemt av språkutviklerne. Hvis et språk blir utbredt, vil det over tid dukke opp forskjellige versjoner av kompilatorer som ikke akkurat følger den spesielle standarden. I de fleste tilfeller er det en utvidelse av de opprinnelig faste funksjonene til språket. En konsensusstandard er under utvikling for å bringe de mest populære implementeringene av språket på linje med hverandre. Veldig viktig faktor Standardiseringen av et programmeringsspråk er aktualiteten til standardens utseende - før den brede distribusjonen av språket og opprettelsen av mange inkompatible implementeringer. I prosessen med språkutvikling kan det dukke opp nye standarder som gjenspeiler moderne innovasjoner.

Programmeringsparadigmer

Paradigme- et sett med teorier, standarder og metoder som til sammen representerer en måte å organisere vitenskapelig kunnskap på - med andre ord en måte å se verden på. I analogi er det generelt akseptert at et paradigme i programmering er en måte å konseptualisere på som definerer hvordan beregninger skal utføres og hvordan arbeidet som utføres av en datamaskin skal struktureres og organiseres.

Det finnes flere grunnleggende programmeringsparadigmer, hvorav de viktigste for øyeblikket er paradigmene for direktiv, objektorientert og funksjonell-logisk programmering. For å støtte programmering i samsvar med et bestemt paradigme, er det utviklet spesielle algoritmiske språk.

C og Pascal er eksempler på språk designet for preskriptiv programmering, der programutvikleren bruker en prosessorientert modell, det vil si prøver å lage kode som opererer på data på riktig måte. Det aktive prinsippet i denne tilnærmingen anses å være et program (kode) som må utføre alt som er nødvendig for å oppnå ønsket resultat handlinger på passive data.


Programmeringsteknologi som en prosess for å utvikle programvareprodukter skapt som en uløselig helhet i form av veltestede programmer og undervisningsmateriell, som beskriver deres formål og bruk.

Programmering- skapelsesprosessen dataprogrammer. I bredere forstand: spekteret av aktiviteter knyttet til opprettelse og vedlikehold av arbeid. tilstand av programmer - dataprogramvare.

Programmeringsteknologi- et sett med metoder og verktøy som brukes i programvareutviklingsprosessen.

Programmeringsteknologi er et sett med teknologiske instruksjoner, inkludert:

· indikasjon på rekkefølgen av teknologiske operasjoner;

· liste forholdene under hvilke denne eller den operasjonen utføres;

· beskrivelser av selve operasjonene, hvor for hver operasjon de første dataene, resultatene, samt instruksjoner, forskrifter, standarder, kriterier osv. er definert.

Moderne programmeringsteknologi - komponent tilnærming, som innebærer å bygge programvare fra individuelle komponenter - fysisk separate deler av programvare som samhandler med hverandre gjennom standardiserte binære grensesnitt. For tiden kvalitetskriterier et programvareprodukt anses å være:− funksjonalitet; − pålitelighet;− brukervennlighet;− effektivitet(forholdet mellom nivået av tjenester levert av programvaren til brukeren når gitte forhold, til mengden ressurser som er brukt);− vedlikeholdbarhet(egenskapene til et programvareprodukt som tillater å minimere innsatsen for å gjøre endringer for å eliminere feil i det og for å modifisere det i samsvar med endrede behov til brukere); mobilitet(evnen til et programvaresystem til å overføres fra et miljø til et annet, spesielt fra en datamaskin til en annen).

Et viktig skritt opprettelse av et programvareprodukt. testing og feilsøking.

Feilsøking− dette er en aktivitet rettet mot å oppdage og rette feil i programvareprodukt ved å bruke kjøringsprosessene til programmene.

Testing− dette er prosessen med å kjøre programmene på et bestemt sett med data, der resultatet av applikasjonen er kjent på forhånd eller reglene for oppførselen til disse programmene er kjent.

Følgende PS-testmetoder finnes:

1) Statisk testing - manuell testing av programmet ved bordet.

2) Deterministisk testing - med ulike kombinasjoner av kildedata.

3) Stokastisk – ref. Dataene velges tilfeldig, og resultatet bestemmes av kvaliteten på resultatene eller et omtrentlig estimat.


Programmeringsstiler.

En programmeringsstil er et sett med programmeringsteknikker eller metoder som programmerere bruker for å produsere programmer som er korrekte, effektive, enkle å bruke og enkle å lese.

Det er flere programmeringsstiler:

  1. Prosedyreprogrammering er programmering der et program er en sekvens av utsagn. Brukes på høynivåspråk Basic, Fortran, etc.
  2. Funksjonell programmering er programmering der et program er en sekvens av funksjonskall. Brukes på Lisp og andre språk.
  3. Logisk programmering – dette er programmering der programmet er et sett med bestemmelse av relasjoner mellom objekter. Brukes i Prolog og andre språk.

Objektorientert programmering– dette er programmering der grunnlaget for programmet er et objekt som er en samling av data og regler for deres transformasjon. Brukes i Turbo-Pascal, C++, etc.

Funksjonene er abstraksjoner, der implementeringsdetaljene til en handling er skjult bak et eget navn. Et velskrevet sett med funksjoner gjør at de kan brukes mange ganger. Standard bibliotek Python kommer med et vell av funksjoner som er klare til bruk, hvorav mange er allsidige nok til å håndtere et bredt utvalg av innganger. Selv om en bestemt del av koden ikke brukes flere ganger, men når det gjelder inngangs- og utdatadata er den ganske autonom, kan den trygt separeres i en egen funksjon.

Denne forelesningen er mer orientert mot praktiske hensyn fremfor funksjonell programmeringsteori. Der det er nødvendig, vil relevante begreper imidlertid bli brukt og forklart.

Deretter skal vi se nærmere på beskrivelsen og bruken av funksjoner i Python, rekursjon, bestått og returnerende funksjoner som parametere, sekvensbehandling og iteratorer, samt konseptet med en generator. Det vil bli demonstrert at i Python er funksjoner objekter (og derfor kan sendes som parametere og returneres som et resultat av å utføre funksjoner). I tillegg, vi vil snakke om hvordan du kan implementere noen funksjonelle programmeringsmekanismer som ikke har direkte syntaktisk støtte i Python, men som er utbredt i funksjonelle programmeringsspråk.

Hva er funksjonell programmering?

Funksjonell programmering er en programmeringsstil som kun bruker sammensetninger av funksjoner. Med andre ord, dette programmering i uttrykk snarere enn imperative kommandoer.

Som David Mertz påpeker i sin artikkel om funksjonell programmering i Python, "funksjonell programmering - programmering i funksjonelle språk (LISP, ML, OCAML, Haskell, ...)", hvor hovedattributtene er:

  • "Tilstedeværelsen av førsteklasses funksjoner" (funksjoner, som andre objekter, kan overføres inne i funksjoner).
  • Rekursjon er grunnleggende ledelsesstruktur i et program.
  • Behandler lister (sekvenser).
  • Forbud mot bivirkninger i funksjoner, som først og fremst betyr fravær av tildeling (på "rene" funksjonelle språk)
  • Operatører er forbudt og det legges vekt på uttrykk. I stedet for operatører er hele programmet ideelt sett ett uttrykk med tilhørende definisjoner.
  • Hovedspørsmål: Hva må beregnes, ikke Hvordan.
  • Bruke funksjoner av høyere orden (funksjoner over funksjoner over funksjoner).

Funksjonelt program

I matematikk, en funksjon viser objekter fra samme sett ( funksjonsdefinisjonssett) til en annen ( sett med funksjonsverdier). Matematiske funksjoner (de kalles ren) "mekanisk", beregner de entydig resultatet fra de gitte argumentene. Rene funksjoner skal ikke lagre data mellom to samtaler. Du kan tenke på dem som svarte bokser, som du bare vet hva de gjør om, men ikke i det hele tatt hvordan.

Funksjonsstil programmer er utformet som komposisjon funksjoner. I dette tilfellet forstås funksjoner på nesten samme måte som i matematikk: de kartlegger et objekt til et annet. I programmering er "rene" funksjoner et ideal som ikke alltid er oppnåelig i praksis. Praktiske nyttige funksjoner har vanligvis bivirkning: Lagre tilstand mellom samtaler eller endre tilstanden til andre objekter. For eksempel er det umulig å forestille seg I/O-funksjoner uten bivirkninger. Egentlig brukes slike funksjoner av hensyn til disse "effektene". I tillegg fungerer matematiske funksjoner enkelt med objekter som krever en uendelig mengde informasjon (for eksempel reelle tall). Generelt datamaskin

  • Oversettelse

- PLO vil ikke lenger kunne redde oss fra "Cloud Monsters."

Oversetterens notat: Det er to konsepter - parallellisme (utførelse samtidig, uavhengig) og samtidighet (utførelse i trinn, én etter én, men flere oppgaver samtidig), og som alltid måtte jeg ha hjernen min for å finne de rette begrepene.

Jeg vil duplisere noen ord eller termer i parentes i originalen for å søke med engelske termer Ytterligere informasjon, som vil være mange ganger større.

Du har kanskje allerede hørt et slikt uttrykk som: "Clojure", "Scala", "Erlang" eller til og med "Java har nå lambdas". Og du har i det minste en vag idé om "Funksjonell programmering". Hvis du er medlem av et programmeringsfellesskap, kan dette emnet allerede ha blitt diskutert av deg.

Hvis du Googler "Funksjonell programmering" vil du ikke se noe nytt. Det andre språket som ble opprettet tidligere dekker allerede dette emnet, det ble opprettet på 50-tallet og heter Lisp. Så hvorfor i helvete ble dette emnet populært først nå? Bare 60 år senere?

I begynnelsen var datamaskiner veldig trege

Tro det eller ei, datamaskiner var mye tregere enn DOM. Nei, egentlig. Og samtidig var det 2 hovedideer i avtalen om design og implementering av programmeringsspråk:

De to første har lignende læreplaner, vil introdusere deg til det grunnleggende innen funksjonell programmering og er svært egnet for nybegynnere. Den tredje lenken, dette kurset om dataprogrammeringsparadigmer, dekker mer enn funksjonell programmering. Det er viktig å merke seg at disse materialene er for entry-level.

Jeg har alltid hatt lyst til å skrive en serie artikler om funksjonell programmering for dette magasinet, og jeg er veldig glad for at jeg endelig fikk muligheten. Selv om serien min om dataanalyse fortsatt er langt fra ferdig :). Jeg vil ikke kunngjøre innholdet i hele serien, jeg vil bare si at i dag vil vi snakke om forskjellige programmeringsspråk som støtter funksjonsstilen og tilsvarende programmeringsteknikker.

Programmeringsspråk som ikke alle vet om

Jeg begynte å programmere som barn, og i en alder av tjuefem virket det for meg at jeg visste og forsto alt. Gjenstand orientert programmering ble en del av hjernen min, ble alle tenkelige bøker om industriell programmering lest. Men jeg hadde fortsatt følelsen av at jeg hadde gått glipp av noe, noe veldig subtilt og utrolig viktig. Faktum er at jeg, som mange på nittitallet, på skolen ble lært opp til å programmere i Pascal (å ja, herlighet Turbo Pascal 5,5! - Ca. red.), så var det C og C++. På universitetet, Fortran og deretter Java som hovedverktøy på jobben. Jeg kunne Python og flere andre språk, men det var helt feil. Men jeg hadde ikke en seriøs utdannelse innen datavitenskap. En dag under en flytur over Atlanterhavet klarte jeg ikke å sove, og jeg ville lese noe. På en eller annen måte, på magisk vis, hadde jeg en bok om programmeringsspråket Haskell for hånden. Det virker for meg som om det var da jeg forsto den sanne betydningen av uttrykket "skjønnhet krever ofre."

Nå, når folk spør meg hvordan jeg lærte Haskell, sier jeg dette: på et fly. Denne episoden endret holdningen min til programmering generelt. Selvfølgelig, etter det første bekjentskapet, virket mange ting ikke helt klart for meg. Jeg måtte anstrenge meg og studere problemstillingen mer nøye. Og du vet, ti år har gått, mange funksjonelle elementer har blitt en del av industrispråk, lambda funksjoner eksisterer allerede selv i Java, type slutning- i C++, mønstermatching- i Scala. Mange tror at dette er et slags gjennombrudd. Og i denne serien med artikler vil jeg fortelle deg om funksjonelle programmeringsteknikker som bruker forskjellige språk og deres funksjoner.

Internett-brukere setter ofte sammen alle slags lister og topper til underholdning for publikum. For eksempel "en liste over bøker du bør lese før du fyller tretti." Hvis jeg fikk i oppgave å lage en liste over bøker om programmering som du bør lese før du blir eldre, så ville førsteplassen absolutt gått til boken av Abelson og Sussman "Struktur og tolkning av dataprogrammer". Noen ganger virker det til og med for meg som kompilatoren eller tolken noen språk bør stoppe alle som ikke har lest denne boken.

Derfor, hvis det er et språk du trenger for å begynne å lære funksjonell programmering, er det Lisp. Generelt er dette en hel familie av språk, som inkluderer et nå ganske populært språk for JVM kalt Clojure. Men det er ikke spesielt egnet som første funksjonsspråk. For dette er det bedre å bruke språket Opplegg, som ble utviklet ved MIT og frem til midten av 2000-tallet fungerte som hovedspråk for undervisning i programmering. Selv om introduksjonskurset med samme tittel som nevnte bok nå er erstattet av et kurs om Python, har det fortsatt ikke mistet sin relevans.

Jeg vil prøve å kort snakke om Scheme-språket og generelt om ideen bak språkene til denne gruppen. Til tross for at Lisp er veldig gammel (av alle høynivåspråk er det bare Fortran som er eldre), var det i den at mange programmeringsmetoder som brukes i dag først ble tilgjengelige. I det følgende vil jeg bruke navnet Lisp, som betyr en spesifikk implementering - Scheme.

Syntaks på to minutter

Syntaksen i Lisp er, hmm, litt kontroversiell. Faktum er at ideen som ligger til grunn for syntaksen er ekstremt enkel og er bygget på grunnlag av den såkalte S-uttrykk. Dette er en prefiksnotasjon der det kjente uttrykket 2 + 3 er skrevet som (+ 2 3) . Dette kan virke rart, men i praksis gir det noen ekstra muligheter. Forresten, (+ 2 10 (* 3,14 2)) fungerer også :). Dermed er hele programmet en samling lister som bruker prefiksnotasjon. Når det gjelder Lisp-språket, er selve programmet og det abstrakte syntakstreet - "hvis du vet hva jeg mener" 😉 - i hovedsak ikke annerledes. En slik plate gjør parsing Lisp-programmer er veldig enkle.
Siden vi snakker om et programmeringsspråk, bør vi snakke om hvordan man definerer funksjoner på dette språket.

Her må vi gjøre en liten digresjon. Det er en subtilitet, hvis betydning er undervurdert i moderne litteratur. Det er fortsatt nødvendig å skille en funksjon i matematisk forstand og en funksjon slik vi forstår den i funksjonell programmering. Faktum er at i matematikk er funksjoner deklarative objekter, og i programmering brukes de til å organisere beregningsprosessen, det vil si at de på en måte snarere representerer imperativ kunnskap, kunnskap som svarer på spørsmålet "hvordan?" Det er derfor Abelson og Sussman i sin bok skiller dette veldig nøye og kaller funksjoner i programmeringsprosedyrer. Dette er ikke akseptert i moderne funksjonell programmeringslitteratur. Men jeg anbefaler likevel sterkt å skille disse to betydningene av ordet "funksjon" i det minste i hodet ditt.

Den enkleste måten å definere en funksjon på er å skrive følgende kode. La oss starte med noe uanstendig enkelt:

(definer (kvadratrøtter a b c) (la ((D (- (* b b) (* 4 a c)))) (hvis (< D 0) (list) (let ((sqrtD (sqrt D))) (let ((x1 (/ (- (- b) sqrtD) (* 2.0 a))) (x2 (/ (+ (- b) sqrtD) (* 2.0 a)))) (list x1 x2))))))

Ja, det er akkurat det du trodde - å løse en kvadratisk ligning i Scheme. Men dette er mer enn nok til å se alle funksjonene i syntaksen. Her er sq-roots navnet på funksjonen fra tre formelle parametere.

Ved første øyekast har let-konstruksjonen, som brukes til å definere lokale variabler, for mange parenteser. Men dette er ikke sant, vi definerer ganske enkelt først en liste med variabler, og deretter et uttrykk der disse variablene brukes. Her (listen) er tom liste, som vi returnerer når det ikke er røtter, og (liste x1 x2) er en liste med to verdier.

Nå om uttrykk. I vår sq-roots-funksjon brukte vi en if-konstruksjon. Det er her funksjonell programmering kommer inn.

Poenget er at, i motsetning til imperative språk som C, i funksjonelle språk er hvis et uttrykk, ikke et utsagn. I praksis betyr dette at den ikke kan ha en annen gren. Fordi et uttrykk alltid må ha en mening.

Du kan ikke snakke om syntaks uten å snakke om syntaktisk sukker. I programmeringsspråk refererer syntaktisk sukker til konstruksjoner som ikke er nødvendige, men som bare gjør koden lettere å lese og gjenbruke. Til å begynne med, la oss gi et klassisk eksempel fra C-språket Mange vet at arrays ikke er et nødvendig uttrykksmiddel, siden det er pekere. Ja, faktisk, matriser implementeres gjennom pekere, og a[i] for C-språket er det samme som *(a + i) . Dette eksemplet er generelt ganske uvanlig, med en morsom effekt knyttet til det: siden addisjonsoperasjonen forblir kommutativ når det gjelder pekere, er det siste uttrykket det samme som *(i + a) , og dette kan oppnås ved å fjerne det syntaktiske sukker fra uttrykk i[a] ! Operasjonen med å fjerne syntaktisk sukker i engelske språk kalt et spesielt ord avsukker.

Tilbake til Scheme-språket er det et viktig eksempel på syntaktisk sukker. For å definere variabler, som i tilfellet med funksjoner, brukes et nøkkelord (i Lisp og Scheme kalles dette spesiell form) definere . For eksempel, (definer pi 3.14159) definerer variabelen pi. Generelt sett kan du definere funksjoner på nøyaktig samme måte:

(definer kvadrat (lambda (x) (* x x)))

dette er det samme som

(definer (kvadrat x) (* x x))

Den siste linjen ser litt lettere ut sammenlignet med versjonen som bruker et lambda-uttrykk. Det er imidlertid klart at det er nok å ha det første alternativet, og det andre er valgfritt. Hvorfor er den første viktigere? Fordi en av de mest grunnleggende egenskapene til funksjonelle språk er at funksjoner i dem er førsteklasses objekter. Det siste betyr at funksjoner kan sendes som et argument og returneres som en verdi.

Hvis du ser på let fra synspunktet til et lambda-uttrykk, kan du lett legge merke til følgende korrespondanse:

(la ((x 5) (y 2)) (* x y)) (bruk (lambda (x y) (* x y)) (liste 5 2))

Funksjonell programmering

Det finnes funksjonelle språk ren Og urent. Rene funksjonelle språk er relativt sjeldne, de inkluderer først og fremst Haskell Og Ren. Rene språk har ingen bivirkninger. I praksis betyr dette ingen oppdrag eller I/O slik vi er vant til. Dette skaper en rekke vanskeligheter, selv om dette på de allerede nevnte språkene er løst ganske smart, og på disse språkene skriver de kode med mye input/output. Språk som Lisp, OCaml eller Scala tillater funksjoner med bivirkninger, og i denne forstand er disse språkene ofte mer praktiske.

Vår oppgave er å lære de grunnleggende teknikkene for funksjonell programmering i Scheme. Derfor vil vi skrive ren funksjonell kode, uten å bruke en generator tilfeldige tall, I/O og sett! , som lar deg endre verdiene til variabler. Alt dette kan du lese om i boka SICP. La oss nå fokusere på det viktigste for oss.

Det første som forvirrer en nybegynner med funksjonell programmering er mangelen på løkker. Men hva bør vi gjøre? Mange av oss blir lært at rekursjon er dårlig. Dette argumenteres av det faktum at rekursjon i konvensjonelle programmeringsspråk vanligvis implementeres ineffektivt. Poenget er at man i det generelle tilfellet bør skille mellom rekursjon som en teknisk teknikk, det vil si å kalle en funksjon fra seg selv, og rekursjon som en prosess. Funksjonelle språk støtter optimalisering av halerekursjon eller, som noen ganger kalles, akkumulatorrekursjon. Dette kan illustreres med et enkelt eksempel.

La oss si at vi har to funksjoner - succ og prev. Den første returnerer et tall 1 større enn argumentet, og det andre returnerer 1 mindre. La oss nå prøve å definere tilleggsoperasjonen på to måter:

(definer (legg til x y) (hvis (eq? y 0) x (add (succ x) (prev y)))) (definer (add-1 x y) (if (eq? y 0) x (succ (add-) 1 x (forrige y)))))

Hva er forskjellen mellom det første og andre tilfellet? Faktum er at hvis vi vurderer beregningsmetoden for det første tilfellet trinn for trinn, kan vi se følgende:

(legg til 3 4) => (legg til 4 3) => (legg til 5 2) => (legg til 6 1) => (legg til 7 0) => 7

I det andre tilfellet vil vi ha noe slikt:

(add-1 3 4) => (succ (add-1 3 3)) => (succ (add-1 3 2))) => (succ (succ (add-1 3 1)) )) => (succ (succ (succ (succ (succ (add-1 3 0)))))) => (succ (succ (succ (succ 3)))) => (succ (succ (succ 4)))) => (suk. 5)) => (suk. 6) => 7

Til tross for at resultatet i begge tilfeller er det samme, er beregningsprosessen radikalt forskjellig. I det første tilfellet endres ikke mengden minne som brukes, men i det andre øker den lineært. Den første prosessen er iterativ, og andre - tilbakevendende. Ja, for å skrive effektive programmer I funksjonelle språk må halerekursjon brukes for å unngå stabeloverløp.

Lister

En av essensielle elementer funksjonell programmering, sammen med rekursjon, - lister. De gir grunnlag for komplekse strukturer data. Som i andre funksjonelle språk, er lister ganske enkelt koblet på en hode-til-hale-måte. Cons-funksjonen brukes til å lage en liste, og bil- og cdr-funksjonene brukes til å få tilgang til henholdsvis hode og hale på listen. Så listen (liste 1 2 3) er ikke mer enn (cons 1 (cons 2 (cons 3 "()))). Her er "() en tom liste. Så en typisk listebehandlingsfunksjon ser slik ut:

(definer (sum lst) (hvis (null? lst) 0 (+ (bil lst) (sum (cdr lst)))))

Denne funksjonen summerer ganske enkelt elementene i en liste. Mange listebehandlingsfunksjoner ser slik ut i en av de følgende artiklene, jeg vil fortelle deg hvorfor. Foreløpig vil jeg bare legge merke til at hvis vi erstatter det første argumentet i tillegg med 1, får vi en funksjon som beregner lengden på listen.

Funksjoner av høyere orden

Siden funksjoner kan sendes som argumenter og returneres som en verdi, ville det vært fint å finne en bruk for dette. Tenk på følgende klassiske eksempel:

(definer (kart f lst) (if (null? lst) lst (cons (f (bil lst)) (kart f (cdr lst))))))

Kartfunksjonen bruker f-funksjonen på hvert element i listen. Så rart det enn kan virke, kan vi nå uttrykke funksjonen for å beregne lengden på en listelengde i form av sum og kart:

(definer (lengde lst) (sum (kart (lambda (x) 1) lst)))

Hvis du plutselig bestemte deg nå for at alt dette på en eller annen måte er for enkelt, så la oss tenke på dette: hvordan implementere lister ved å bruke funksjoner av høyere orden?

Det vil si at du må implementere funksjonene cons , car og cdr slik at de tilfredsstiller følgende relasjon: for enhver liste lst er det sant at verdien (cons (car lst) (cdr lst)) sammenfaller med lst . Dette kan gjøres som følger:

(definer (cons x xs) (lambda (pick) (if (eq? pick 1) x xs))) (definer (bil f) (f 1)) (definer (cdr f) (f 2))

Hvordan det fungerer? Her returnerer cons-funksjonen en annen funksjon som har én parameter, og avhengig av det returnerer enten det første eller andre argumentet. Det er enkelt å kontrollere at det nødvendige forholdet holder for disse funksjonene.

Bruke sitat og metaprogrammering

En fin egenskap ved Lisp-språket er at det er ekstremt praktisk for å skrive programmer som konverterer andre programmer. Poenget er at et program består av lister, og en liste er hoveddatastrukturen i språket. Det er en måte å ganske enkelt "sitere" teksten til et program slik at den oppfattes som en liste over atomer.

Atomer er ganske enkelt tegnuttrykk, for eksempel ("hei "verden) , som er det samme som "(hei verden) , eller i full form (sitat (hei verden)) . Selv om de fleste Lisp-dialekter har strenger, kan du noen ganger klare deg med sitat Enda viktigere, ved å bruke denne tilnærmingen kan du forenkle kodegenerering og programbehandling.

La oss først prøve å forstå symbolske beregninger. Dette forstås vanligvis som dataalgebrasystemer som er i stand til å håndtere symbolske objekter, formler, ligninger og andre komplekse matematiske objekter (det er mange slike systemer, hovedeksemplene er systemer lønnetre Og Mathematica).

Du kan prøve å implementere symbolsk differensiering. Jeg tror at alle som er i nærheten av å fullføre skolen forestiller seg reglene for differensiering (selv om alt i virkeligheten er litt mer komplisert - her vil vi beregne den partielle deriverte ved ganske enkelt å telle andre variabler er konstanter, men dette kompliserer ikke saken i det hele tatt).

Så jeg vil bare gi et eksempel på kode som viser essensen av saken, og overlater detaljene til leseren (som, håper jeg, nøye studerer boken "Struktur og tolkning av dataprogrammer").

(definer (avlede exp var) (kond ((tall? exp) 0) ((variabel? exp) (hvis (samme-variabel? exp var) 1 0)) ((sum? exp) (make-sum (avledet) addend exp) var) (deriv (augend exp) var))) ((produkt? exp) (make-sum (make-product (multiplikator exp) (deriv (multiplikant exp) var)) (make-produkt (deriv (multiplikator) exp) var) (multiplikand exp)))) (ellers (feil "ukjent uttrykkstype - DERIV" exp))))

Her er derivfunksjonen en implementering av differensieringsalgoritmen slik den læres på skolen. Denne funksjonen krever implementering av tallfunksjoner? , variabel? og så videre, som lar oss forstå hvilken natur dette eller det uttrykkselementet har. Du må også implementere tilleggsfunksjoner lage-produkt og lage-sum. Her bruker vi kondisjonskonstruksjonen, som fortsatt er ukjent for oss - dette er en analog av bryteroperatøren i programmeringsspråk som C og Java.

Før vi går videre til å implementere de manglende funksjonene, er det verdt å merke seg at funksjonell programmering ganske ofte bruker en top-down utviklingstilnærming. Dette er når de mest generelle funksjonene skrives først, og deretter små funksjoner som er ansvarlige for implementeringsdetaljer.

(definer (variabel? x) (symbol? x)) (definer (samme-variabel? v1 v2) (og (variabel? v1) (variabel? v2) (eq? v1 v2))) (definer (gjør-sum a1) a2) (liste "+ a1 a2)) (definer (fabrikat-produkt m1 m2) (liste "* m1 m2)) (definer (sum? x) (og (par? x) (eq? (bil x) "+ ))) (definer (legg til s) (cadr s)) (definer (augend s) (caddr s)) (definer (produkt? x) (og (par? x) (eq? (bil x) "*)) ) (definer (multiplikator p) (cadr p)) (definer (multiplikant p) (caddr p))

Implementeringen av disse funksjonene krever ingen spesielle kommentarer, med mulig unntak av cadr- og caddr-funksjonene. Dette er ikke annet enn funksjoner som returnerer henholdsvis det andre og det tredje elementet i en liste.

Hvis du bruker den interaktive Scheme-tolken, kan du enkelt verifisere at den resulterende koden fungerer riktig, men uten å forenkle uttrykkene:

(avledet "(+ x 3) "x) => (+ 1 0) (avledet "(* (* x y) (+ x 3)) "x) => (+ (* (* x y) (+ 1 0) )) (* (+ (* x 0) (* 1 y)) (+ x 3)))

For trivielle tilfeller (for eksempel multiplikasjon med 0), løses forenklingsproblemet ganske enkelt. Dette spørsmålet overlates til leseren. De fleste eksemplene i denne artikkelen er hentet fra SICP-boken, så hvis du har noen problemer, kan du ganske enkelt referere til kilden (boken er i det offentlige domene).

Som enhver dialekt har Lisp gode metaprogrammeringsmuligheter, hovedsakelig knyttet til bruk av makroer. Dessverre krever dette problemet analyse i en egen artikkel.

La oss skrive en funksjon som vil fjerne syntaktisk sukker fra funksjonsdefinisjonen som diskutert tidligere:

(define (desugar-define def) (let ((fn-args (cadr def)) (body (caddr def))) (la ((navn (bil fn-args)) (args (cdr fn-args)))) (liste "definer navn (liste "lambda args body)))))

Denne funksjonen fungerer utmerket med velutformede funksjonsdefinisjoner:

(desugar-define "(definer (succ x) (+ x 1))) => (definer succ (lambda (x) (+ x 1)))

Dette fungerer imidlertid ikke for vanlige definisjoner som (definer x 5) .
Hvis vi ønsker å fjerne syntaktisk sukker i et stort program som inneholder mange forskjellige definisjoner, så må vi implementere ekstra sjekk:

(definer (sukker? def) (og (eq? (bil def) "definere) (liste? (cadr def))))

En slik sjekk kan bygges direkte inn i desugar-define-funksjonen, og sørger for at hvis definisjonen ikke trenger å fjerne syntaktisk sukker, endres den rett og slett ikke (denne trivielle øvelsen overlates til leseren). Deretter kan du pakke inn hele programmet i en liste og bruke kart:

(map desugar-define prog)

Konklusjon

I denne artikkelen satte jeg meg ikke i oppgave å snakke om Scheme i noen detalj. Først og fremst ønsket jeg å vise flere interessante trekk ved språket og tiltrekke leseren til å studere funksjonell programmering. Dette fantastiske språket har, til tross for sin enkelhet, sin egen sjarm og funksjoner som gjør programmering i det veldig spennende. Når det gjelder verktøyet for å jobbe med Scheme, kan de viljesterke svinge seg på MIT-skjema, og resten - nyt et utmerket læringsmiljø Dr. Rekkert. I en av de følgende artiklene vil jeg definitivt fortelle deg hvordan du skriver din egen Scheme-tolk.

Late beregninger

I tradisjonelle programmeringsspråk (som C++) fører tilkalling av en funksjon til at alle argumenter blir evaluert. Denne metoden for å kalle en funksjon kalles call-by-value. Hvis et argument ikke ble brukt i funksjonen, går resultatet av beregningen tapt, derfor ble beregningen gjort forgjeves. På en måte er det motsatte av call-by-value call-by-necessity. I dette tilfellet evalueres argumentet bare hvis det er nødvendig for å beregne resultatet. Et eksempel på denne virkemåten er konjunksjonsoperatoren fra C++ (&&), som ikke evaluerer verdien av det andre argumentet hvis det første argumentet er usant.

Hvis et funksjonelt språk ikke støtter lat evaluering, kalles det strengt. Faktisk, på slike språk er rekkefølgen for evaluering strengt definert. Eksempler på strenge språk inkluderer Scheme, Standard ML og Caml.

Språk som bruker lat evaluering kalles ikke-strenge. Haskell er et løst språk, akkurat som for eksempel Gofer og Miranda. Slappe språk er ofte rene.

Svært ofte inkluderer strenge språk støtte for noen av de nyttige funksjonene som finnes på ikke-strenge språk, for eksempel uendelige lister. Standard ML leveres med en spesiell modul for å støtte utsatte beregninger. I tillegg støtter Objective Caml det ekstra reserverte ordet lat og en konstruksjon for lister med verdier beregnet etter behov.

Denne delen gir en kort beskrivelse av noen (svært få) funksjonelle programmeringsspråk.

§ Lisp(Listebehandler). Det regnes som det første funksjonelle programmeringsspråket. Ikke skrevet. Den inneholder mange imperative egenskaper, men generelt sett oppmuntrer den til den funksjonelle stilen til programmering. Bruker call-by-value for beregninger. Det er en objektorientert dialekt av språket - CLOS.

§ JEG SVØMMER(Hvis du ser hva jeg mener). Funksjonell språkprototype. Utviklet av Landin på 60-tallet av 1900-tallet for å demonstrere hva et funksjonelt programmeringsspråk kan være. Sammen med språket utviklet Landin også en spesiell virtuell maskin for å kjøre programmer på ISWIM. Denne virtuelle call-by-value-maskinen kalles en SECD-maskin. Syntaksen til mange funksjonelle språk er basert på syntaksen til ISWIM-språket. Syntaksen til ISWIM er lik den til ML, spesielt Caml.

§ Opplegg. En Lisp-dialekt beregnet på vitenskapelig forskning innen datavitenskap. Scheme ble designet med vekt på eleganse og enkelhet i språket. Dette gjør språket mye mindre enn Common Lisp.


§ M.L.(Metaspråk). En familie av strenge språk med et utviklet polymorf type system og parameteriserbare moduler. ML undervises på mange vestlige universiteter (noen til og med som det første programmeringsspråket).

§ Standard ML. Et av de første maskinskrevne funksjonelle programmeringsspråkene. Inneholder noen imperative egenskaper som lenker til foranderlige verdier og er derfor ikke ren. Bruker call-by-value for beregninger. En veldig interessant implementering av modularitet. Kraftig polymorf type system. Den siste språkstandarden er Standard ML-97, som det er formelle for matematiske definisjoner syntaks, samt statisk og dynamisk semantikk av språket.

§ Caml lys Og Målsetting Caml. I likhet med Standard ML tilhører den ML-familien. Mål Caml skiller seg fra Caml Light hovedsakelig i sin støtte for klassisk objektorientert programmering. Akkurat som Standard ML er streng, men har noe innebygd støtte for lat evaluering.

§ Miranda. Utviklet av David Turner som et standard funksjonelt språk som brukte lat evaluering. Den har et strengt polymorf type system. I likhet med ML undervises det på mange universiteter. Han hadde stor innflytelse på utviklerne av Haskell-språket.

§ Haskell. Et av de vanligste ikke-strenge språkene. Den har et veldig utviklet skrivesystem. Modulsystemet er noe mindre godt utviklet. Den siste språkstandarden er Haskell-98.

§ Gofer(Bra for ekvasjonelt resonnement). Forenklet Haskell-dialekt. Designet for undervisning i funksjonell programmering.

§ Ren. Spesielt designet for parallell og distribuert programmering. Syntaksen ligner på Haskell. Ren. Bruker utsatte beregninger. Kompilatoren kommer med et sett med biblioteker (I/O-biblioteker) som lar deg programmere et grafisk brukergrensesnitt under Win32 eller MacOS.

La oss minne deg på det den viktigste egenskapen Den funksjonelle tilnærmingen er det faktum at ethvert program utviklet i et funksjonelt programmeringsspråk kan betraktes som en funksjon, hvis argumenter også kan være funksjoner.

Den funksjonelle tilnærmingen ga opphav til en hel familie av språk, hvis stamfar, som allerede nevnt, var programmeringsspråket LISP. Senere, på 70-tallet, ble den originale versjonen av ML-språket utviklet, som senere utviklet seg spesielt til SML, så vel som en rekke andre språk. Av disse er kanskje den "yngste" Haskell-språket, opprettet ganske nylig, på 90-tallet.

En viktig fordel implementering av funksjonelle programmeringsspråk er automatisert dynamisk distribusjon datamaskinminne for lagring av data. I dette tilfellet blir programmereren kvitt behovet for å kontrollere dataene, og kan om nødvendig kjøre "søppelsamling" -funksjonen - tømme minne fra data som programmet ikke lenger trenger.

Komplekse programmer i den funksjonelle tilnærmingen bygges ved å aggregere funksjoner. I dette tilfellet er programteksten en funksjon, der noen av argumentene også kan betraktes som funksjoner. Dermed kommer gjenbruk av kode ned til å kalle en tidligere beskrevet funksjon, hvis struktur, i motsetning til en imperativ språkprosedyre, er matematisk transparent.

Fordi en funksjon er en naturlig formalisme for funksjonelle programmeringsspråk, er implementering av ulike aspekter ved funksjonsrelatert programmering sterkt forenklet. Å skrive rekursive funksjoner blir intuitivt transparent, d.v.s. funksjoner som kaller seg selv som et argument. Implementeringen av behandling av rekursive datastrukturer blir også naturlig.

På grunn av implementeringen av en mønstertilpasningsmekanisme, er funksjonelle programmeringsspråk som ML og Haskell gode for symbolsk behandling.

Naturligvis er funksjonelle programmeringsspråk ikke uten noen ulemper.

Disse inkluderer ofte den ikke-lineære strukturen til programmet og den relativt lave effektiviteten av implementeringen. Den første ulempen er imidlertid ganske subjektiv, og den andre har blitt overvunnet av moderne implementeringer, spesielt en rekke nyere SML-språkoversettere, inkludert en kompilator for Microsoft .NET-miljøet.

For å utvikle profesjonell programvare i funksjonelle programmeringsspråk, må du ha en dyp forståelse av funksjonens natur.

Merk at begrepet "funksjon" i matematisk formalisering og programvareimplementering refererer til forskjellige konsepter.

Dermed er en matematisk funksjon f med et domene av definisjon A og et verdiområde B settet med ordnede par

slik at hvis

(a,b 1) f og (a,b 2) f,

I sin tur er en funksjon i et programmeringsspråk en konstruksjon av dette språket som beskriver reglene for å konvertere et argument (den såkalte faktiske parameteren) til et resultat.

For å formalisere konseptet "funksjon", ble en matematisk teori kjent som lambda-regning konstruert. Mer presist bør denne beregningen kalles beregningen av lambda-konverteringer.

Konvertering refererer til transformasjon av kalkulusobjekter (og i programmering, funksjoner og data) fra en form til en annen. Den første oppgaven i matematikk var ønsket om å forenkle uttrykksformen. I programmering er ikke denne oppgaven så viktig, selv om, som vi skal se senere, bruk av lambda-kalkulus som den første formaliseringen kan bidra til å forenkle typen program, dvs. føre til programkodeoptimalisering.

I tillegg gir konverteringer en overgang til nyinnførte notasjoner og lar en dermed representere fagområdet i en mer kompakt eller mer detaljert form, eller i matematisk språk endre abstraksjonsnivået ift. fagområde. Denne funksjonen er også mye brukt av objektorienterte og strukturert-modulære programmeringsspråk i hierarkiet av objekter, programfragmenter og datastrukturer. Samspillet mellom applikasjonskomponenter i .NET er basert på samme prinsipp. Det er i denne forstand at overgangen til nye notasjoner er et av de viktigste elementene i programmering generelt, og det er lambda-regning (i motsetning til mange andre grener av matematikk) som representerer en adekvat måte å formalisere renotasjoner.

La oss systematisere utviklingen av teoriene som ligger til grunn for den moderne tilnærmingen til lambda-kalkulus.

La oss vurdere utviklingen av programmeringsspråk som utvikler seg innenfor rammen av den funksjonelle tilnærmingen.

Tidlige funksjonelle programmeringsspråk, som stammer fra det klassiske LISP-språket (LISt Processing), ble designet for å behandle lister, dvs. symbolsk informasjon. I dette tilfellet var hovedtypene et atomelement og en liste over atomelementer, og hovedvekten var på å analysere innholdet i listen.

Utviklingen av tidlige programmeringsspråk ble funksjonelle programmeringsspråk med sterk skriving, et typisk eksempel her er den klassiske ML, og dens direkte etterkommer SML. I sterkt skrevet språk må hver konstruksjon (eller uttrykk) ha en type.

I senere funksjonelle programmeringsspråk er det imidlertid ikke behov for eksplisitt typetilordning, og typene opprinnelig udefinerte uttrykk, som i SML, kan utledes (før programmet kjøres) basert på typene uttrykk som er knyttet til dem .

Det neste trinnet i utviklingen av funksjonelle programmeringsspråk var støtten til polymorfe funksjoner, dvs. funksjoner med parametriske argumenter (analoger matematisk funksjon med parametere). Spesielt støttes polymorfisme i SML, Miranda og Haskell.

På det nåværende utviklingsstadiet har "ny generasjons" funksjonelle programmeringsspråk dukket opp med følgende avanserte evner: mønstertilpasning (Scheme, SML, Miranda, Haskell), parametrisk polymorfisme (SML) og såkalt "lat" (som nødvendig) beregninger (Haskell, Miranda, S.M.L.).

Familien av funksjonelle programmeringsspråk er ganske stor. Dette bevises ikke så mye av den betydelige listen over språk, men av det faktum at mange språk har gitt opphav til hele trender innen programmering. La oss huske at LISP ga opphav til en hel familie av språk: Scheme, InterLisp, COMMON Lisp, etc.

SML-programmeringsspråket var intet unntak, som ble opprettet i form av ML-språket av R. Milner ved MIT (Massachusetts Institute of Technology) og var opprinnelig ment for logiske deduksjoner, spesielt teorembevis. Språket er strengt typifisert og mangler parametrisk polymorfisme.

Utviklingen av "klassisk" ML har blitt tre moderne språk med nesten identiske evner (parametrisk polymorfisme, mønstertilpasning, "late" beregninger). Dette er SML-språket, utviklet i Storbritannia og USA, CaML, skapt av en gruppe franske forskere ved INRIA Institute, SML/NJ - en dialekt av SML fra New Jersey, og også en russisk utvikling - mosml (den " Moskva" dialekt av ML).

Nærheten til matematisk formalisering og den innledende funksjonelle orienteringen forårsaket følgende fordeler med den funksjonelle tilnærmingen:

1. enkel testing og verifisering av programkode basert på muligheten for å konstruere et strengt matematisk bevis på riktigheten av programmer;

2. forening av presentasjonen av programmet og data (data kan innkapsles i programmet som funksjonsargumenter, betegnelsen eller beregningen av funksjonsverdien kan gjøres etter behov);

3. sikker skriving: ugyldige dataoperasjoner er ekskludert;

4. dynamisk skriving: skrivefeil kan oppdages under kjøring (manglen på denne egenskapen i tidlige funksjonelle programmeringsspråk kan føre til overløp tilfeldig tilgangsminne datamaskin);

5. uavhengighet av programvareimplementeringen fra maskinrepresentasjonen av data og systemarkitekturen til programmet (programmereren er fokusert på detaljene i implementeringen, og ikke på funksjonene til maskinrepresentasjonen av data).

Merk at det å realisere fordelene som funksjonelle programmeringsspråk gir avhenger betydelig av valget av programvare og maskinvareplattform.

Hvis .NET-teknologi velges som programvareplattform, nesten uavhengig av maskinvareimplementering, programmerer eller leder programvareprosjekt mottar i tillegg følgende fordeler:

1. integrasjon ulike språk funksjonell programmering (dette utnytter fordelene til hvert av språkene maksimalt, spesielt Scheme gir en mønstertilpasningsmekanisme, og SML gir muligheten til å beregne etter behov);

2. integrering av ulike tilnærminger til programmering basert på den flerspråklige Common Language Infrastructure, eller CLI (spesielt er det mulig å bruke C# for å gi fordelene med en objektorientert tilnærming og SML - funksjonell, som i dette kurset) ;

3. felles enhetlig skrivesystem Common Type System, CTS (uniform og sikker ledelse typer data i programmet);

4. flertrinns, fleksibelt system for å sikre sikkerheten til programkoden (spesielt basert på monteringsmekanismen).

Hovedtrekkene til funksjonelle programmeringsspråk som skiller dem fra både imperative språk og logiske programmeringsspråk er referansetransparens og determinisme. I funksjonelle språk er det betydelig variasjon i parametere som skriving og regneregler. På mange språk er rekkefølgen for evaluering strengt definert. Men noen ganger inneholder strenge språk midler for å støtte noen nyttige elementer, iboende i ikke-strenge språk, for eksempel uendelige lister (Standard ML har en spesiell modul for å støtte late beregninger). I kontrast tillater ikke-strenge språk energisk beregning i noen tilfeller.

Dermed har Miranda lat semantikk, men lar deg spesifisere strenge konstruktører ved å markere konstruktørargumentene på en bestemt måte.

Mange moderne funksjonelle programmeringsspråk er sterkt maskinskrevne språk (sterk skriving). Sterk skriving gir større sikkerhet. Mange feil kan rettes på kompileringsstadiet, slik at feilsøkingsstadiet og den totale programutviklingstiden reduseres. Sterk skriving lar kompilatoren generere mer effektiv kode og dermed fremskynde programkjøringen. Sammen med dette er det funksjonelle språk med dynamisk skriving. Datatypen på slike språk bestemmes under programkjøring (kapittel 3). Noen ganger kalles de "typeløse". Fordelene deres inkluderer det faktum at programmer skrevet på disse språkene har større generalitet. En ulempe kan betraktes som tilskrivelsen av mange feil til programutførelsesstadiet og det tilhørende behovet for å bruke typekontrollfunksjoner og en tilsvarende reduksjon i programmets generalitet. Innskrevne språk bidrar til generering av mer "pålitelig" kode, mens maskinskrevne språk produserer mer "generell" kode.

Det neste kriteriet som funksjonelle programmeringsspråk kan klassifiseres etter kan være tilstedeværelsen av imperative mekanismer. Samtidig er det vanlig å kalle funksjonelle programmeringsspråk som er blottet for imperative mekanismer "rene", og de som har dem kalles "urene". I oversikten over funksjonelle programmeringsspråk nedenfor vil programmeringsspråk bli referert til som "praktisk" og "akademisk". Med "praktiske" språk mener vi språk som har en kommersiell applikasjon (de ble brukt til å utvikle ekte applikasjoner eller det var kommersielle programmeringssystemer). Akademiske programmeringsspråk er populære i forskningskretser og i feltet datautdanning, men det er praktisk talt ingen kommersielle applikasjoner skrevet på slike språk. De forblir bare et verktøy for å utføre teoretisk forskning innen datavitenskap og er mye brukt i utdanningsprosessen.

En liste over de mest populære funksjonelle programmeringsspråkene er gitt nedenfor ved å bruke følgende kriterier: generell informasjon; skriving; type beregning; renhet.

Vanlig Lisp. Versjonen av Lisp, som siden 1970 kan betraktes som språkstandarden, takket være støtten fra laboratoriet for kunstig intelligens ved Massachusetts Institute of Technology, er typeløs, energisk, med et stort sett imperative inneslutninger, som tillater tildeling, ødeleggelse av strukturer . Praktisk. Det er nok å si at vektoren grafikk editor Autocad.

Opplegg. En Lisp-dialekt beregnet for vitenskapelig forskning innen informatikk og undervisning i funksjonell programmering. Takket være mangelen på imperative inneslutninger er språket mye mindre enn vanlig lisp. Dateres tilbake til et språk utviklet av J. McCarthy i 1962. Akademisk, typeløs, energisk, ren.

Refal. En familie av språk utviklet av V. F. Turchin. Det eldste medlemmet av denne familien ble først realisert i 1968 i Russland. Det er fortsatt mye brukt i akademiske kretser. Inneholder logiske programmeringselementer (mønstertilpasning). Derfor foreslås Refal-språket i denne lærebok som språk for selvstudium.

Miranda. Sterkt skrevet, støtter brukerdatatyper og polymorfisme. Utviklet av Turner basert på de tidligere SALS- og KRC-språkene. Har lat semantikk. Ingen tvingende inkluderinger.

Haskell. Utviklingen av språket skjedde på slutten av forrige århundre. Allment kjent i akademiske kretser. På noen vestlige universiteter brukes det som hovedspråk for studenter å studere. Et av de kraftigste funksjonelle språkene. Lat språk. Rent funksjonelt språk. Skrevet. Haskell er et flott verktøy for å lære og eksperimentere med komplekse funksjonelle datatyper. Programmer skrevet i Haskell har en betydelig objektkodestørrelse og lav utførelseshastighet.

Ren. En Haskell-dialekt skreddersydd for behovene til praktisk programmering. Som Haskell er det et lat, rent funksjonelt språk, som inneholder typeklasser. Men Clean inneholder også interessante funksjoner, som ikke har noen tilsvarende i Haskell. For eksempel er imperative funksjoner i Clean basert på unike typer, ideen om hvilke er lånt fra lineær logikk. Clean inneholder mekanismer som kan forbedre effektiviteten til programmer betydelig. Disse mekanismene undertrykker eksplisitt lat beregninger. Clean-implementeringen er et kommersielt produkt, men en gratisversjon er tilgjengelig for forsknings- og utdanningsformål.

ML (metaspråk). Utviklet av en gruppe programmerere ledet av Robert Milier på midten av 70-tallet. i Edinburgh (Edinburgh Logic for Computable Functions). Ideen med språket var å lage en mekanisme for å konstruere formelle bevis i et logikksystem for beregnbare funksjoner. I 1983 ble språket revidert for å inkludere begreper som moduler. Kom til å bli kalt standard ML. ML er sterk maskinskrevet språk med statisk typekontroll og applikativ programkjøring. Den har vunnet stor popularitet i forskningsmiljøer og innen datautdanning.