Un limbaj de programare concatenativ este un limbaj de programare bazat pe faptul că concatenarea a două bucăți de cod exprimă compoziția lor . Într-un astfel de limbaj, specificarea implicită a argumentelor funcției este utilizată pe scară largă (vezi programarea inutilă ), noile funcții sunt definite ca compoziție de funcții, iar concatenarea este folosită în locul aplicației [1] . Această abordare este opusă programării aplicative .
Multe limbi concatenative folosesc notația postfixă și o stivă pentru a stoca argumente și a returna valorile operațiilor, astfel încât limbile concatenative înseamnă de obicei limbaje stivă. Cu toate acestea, limbajele concatenative pot fi construite pe alte principii, astfel încât termenii limbaj stiva și limbaj concatenative nu sunt sinonimi.
Limbajele concatenative sunt simple, eficiente și ușor de implementat, astfel încât cele mai populare limbaje de acest tip sunt folosite în calculatoarele programabile și pentru încorporarea în sistemele cu microprocesoare mici. De exemplu, limbajul concatenativ RPL este utilizat în calculatoarele programabile Hewlett-Packard HP-28 și HP-48 . Limbajul de programare Forth a fost implementat pe multe procesoare cu capacități de calcul foarte limitate [2] , de exemplu, a fost folosit pe computerul Jupiter ACE cu o memorie RAM de bază de doar 1 KB. Cu toate acestea, datorită caracterului neobișnuit și dificultății lor de a citi codul sursă al programelor, limbajele de programare concatenative au rămas de nișă.
Cel mai comun limbaj concatenativ este limbajul de descriere a paginii PostScript , din care un subset limitat este utilizat în PDF . Interpretul său este încorporat în multe imprimante de înaltă performanță.
Un limbaj de programare se numește concatenativ dacă îndeplinește următoarele cerințe:
Într-un limbaj concatenativ, fiecare expresie este o funcție. Nu există o operațiune specială de aplicare, pentru a aplica funcția la argumente, este suficient să puneți numele funcției lângă argumente, adică să efectuați „lipirea” textului (concatenarea). Noile funcții sunt, de asemenea, definite prin concatenare, care este pur și simplu o secvență de alte nume de funcții.
Să fie date funcții fooa două argumente și bara unui argument. Pentru a se aplica fooargumentelor, în notație de prefix , este suficient să compuneți o expresie ca aceasta:
foo 4 5
Acum aplicați funcția barla rezultatul funcției foo:
bar foo 4 5
În cele din urmă, să definim o funcție bazca o concatenare a trei funcții:
define baz
bar foo 4
end-define
Expresia baz 8este echivalentă cu expresia bar foo 4 8. Adică, numele oricărei funcții poate fi înlocuit cu textul definiției acesteia și se poate obține expresia corectă. Acest principiu simplu definește specificul limbajelor concatenative, avantajele și dezavantajele acestora.
Pentru ca concatenarea fragmentelor de cod să-și exprime întotdeauna compoziția, limbajul trebuie să aibă funcții de un singur argument. [3] În acest caz, puteți refuza să specificați în mod explicit argumentul, deci folosind un prefix uniform sau o notație postfixă, puteți crea un limbaj de programare în care concatenarea fragmentelor de cod exprimă compoziția lor, adică un limbaj concatenativ.
O modalitate simplă și eficientă de a implementa această abordare este utilizarea unei stive . Funcțiile preiau argumente din stivă și împing rezultatul în stivă. Prin urmare, putem spune că în limbajele de programare a stivei concatenative, funcțiile iau un singur argument - starea stivei și returnează o nouă stare a stivei. [4] Aceste limbi folosesc de obicei notația postfix, deoarece stiva funcționează în LIFO .
Există și alte moduri. De exemplu, o funcție preia textul programului și îl returnează cu unele modificări care reflectă activitatea acestuia. Pe acest principiu se poate construi un limbaj homoiconic foarte simplu și flexibil . [5] Este posibil să construiți un limbaj în jurul principiului conductei UNIX : fiecare funcție ia un șir și returnează un șir nou după procesare. [6] Spre deosebire de principiul anterior, textul transmis funcției conține doar argumente, nu întregul program. Aceste metode pot funcționa atât cu prefix, cât și cu notație postfix.
În loc de o stivă, pot fi folosite alte structuri de date, cum ar fi o coadă sau un deque (deque) [7] .
Ideea unui limbaj concatenativ este următoarea: toate expresiile sunt funcții care preiau o parte din aceeași structură de date și returnează noua sa stare. Această structură de date (stivă, deque, coadă, șir de text etc.) joacă rolul de lipici pentru funcțiile de „lipire” într-un program, stochează starea programului. Această abordare definește avantajele și dezavantajele limbajelor concatenative.
Avantaje:
Defecte:
Primul limbaj concatenativ de nivel înalt a fost Forth , dezvoltat de Charles Moore la sfârșitul anilor 1960 și începutul anilor 1970. A folosit o stivă fără tip și a fost ușor de implementat și foarte eficient, ceea ce a făcut posibilă implementarea compilatoarelor chiar și cu resurse de calcul extrem de limitate. Forth a influențat semnificativ limbile concatenative ulterioare.
Lector și programator Manfred von Thun la Universitatea La Trobe , influențat de celebra prelegere a lui John Backus „Se poate elibera programarea din stilul lui von Neumann?” a dezvoltat limbajul de programare Joy stack și a pus bazele teoretice pentru programarea concatenative. A fost limbajul Joy care a fost numit pentru prima dată concatenativ.
Influențată de Forth și Joy, Slava Pestov a creat limbajul de programare Factor Stack în 2003 . Este poziționat ca un „limbaj practic de programare stivă”. Ulterior, au fost dezvoltate limbile concatenative stive Cat și Kitten , care se disting prin tastarea statică . Un alt limbaj concatenativ modern, min , are o sintaxă minimalistă și o implementare foarte compactă (aproximativ 1 megaoctet) și este folosit în generatorul de site HastySite .
Dintre limbajele de stivă specializate, cele mai cunoscute sunt PostScript , care este folosit pentru a descrie pagini și a le tipări, precum și RPL , limbajul de programare pentru calculatoarele HP-28 și HP-48 .
Majoritatea limbajelor de programare concatenative folosesc stiva pentru a transmite argumente. Acest lucru se datorează ușurinței implementării și proprietăților stivei, care este convenabil de utilizat cu notația postfix. Luați în considerare lucrul cu stiva folosind limbajul Forth ca exemplu.
În Forth, un program constă din cuvinte separate prin spații. Dacă cuvântul este un număr, atunci este împins în partea de sus a stivei. Dacă cuvântul este numele unei funcții, atunci acea funcție este numită (în terminologia Forth, funcțiile sunt numite cuvinte). Preia argumente din stivă și împinge rezultatul în stivă. Luați în considerare cel mai simplu program, care constă din patru cuvinte:
3 4 + .
Primele două cuvinte sunt numere, așa că sunt împinse pe stivă. Apoi funcția se numește +, care ia două numere din stivă, le adună și împinge rezultatul în stivă. Apoi funcția se numește ., care afișează numărul din stivă. Astfel, argumentele preced funcția, motiv pentru care această notație se numește postfix.
Limbile concatenative de uz general nu au câștigat o popularitate semnificativă. Acest lucru se datorează avantajelor și dezavantajelor lor specifice, care sunt o consecință a principiului de bază: toate funcțiile iau un singur argument și returnează o valoare. Când exact acest lucru este necesar, nu există probleme, iar limbajele concatenative vă permit să scrieți programe foarte simple, concise și clare. Să presupunem că o limbă concatenativă cu notație postfixă are următoarele funcții care acceptă și returnează șiruri de text:
input - returnează textul introdus de utilizator imprimare - afișează text pe ecran majuscule - Schimbă literele mici în majuscule într-un șir first_word - returnează primul cuvânt dintr-un șir (taie șirul la primul spațiu după primul cuvânt)Să le folosim pentru a scrie un program care afișează numele utilizatorului cu majuscule:
input first_word upcase print
Dificultăți apar atunci când trebuie să utilizați funcții cu un număr diferit de argumente. Într-un limbaj de stivă, trebuie să plasați argumentele într-o anumită ordine și, adesea, trebuie să le schimbați. De asemenea, dacă un argument este folosit de mai multe ori într-o funcție, acesta trebuie să fie duplicat. Acest lucru duce la expresii greu de înțeles. De exemplu, funcția
f x y z = y² + x² − |y|
în limbajul stivei se scrie după cum urmează:
f = drop dup dup × swap abs rot3 dup × swap − +
Variabilele sunt utilizate în mod explicit în limbile concatenative moderne, cum ar fi Kitten și min, pentru a depăși aceste dificultăți. În limbajul Kitten, variabilele sunt declarate astfel:
->x; // variabila x își va obține valoarea din stivă 5 -> y; // y = 5 1 2 3 -> xyz; // x = 1; y=2; z = 3Luați în considerare funcția de pătrare a unui număr. În mod tradițional, pentru limbile stive în Kitten este scris după cum urmează: [8]
define square (Int32 -> Int32):
dup (*)
Și astfel poate fi rescris folosind o variabilă:
define square (Int32 -> Int32):
-> x;
x * x
În acest exemplu cel mai simplu, acest lucru nu are niciun sens special. Cu toate acestea, dacă un argument sau argumente sunt folosite de multe ori într-o funcție, utilizarea variabilelor simplifică foarte mult scrierea programului și citirea codului sursă. Un fragment din codul programului care afișează melodia 99 de sticle de bere :
definiți sticle_de_bere (Int32 -> +IO): ->x; xverse dacă (x > 1): (x - 1) sticle_de_bereÎn limbajul de programare min, simbolurile sunt folosite în mod similar:
x define ; символ x получит значение из стека
:x ; сокращённая запись
8 :x ; x = 8
De exemplu, luați în considerare un program min care returnează adevărat dacă un fișier este mai mare de 1 megaoctet și a fost modificat recent:
dup dup
"\.zip$" match
swap fsize 1000000 > and
swap mtime now 3600 - >
Folosind simbolul, puteți evita duplicarea și rearanjarea elementelor stivei și puteți îmbunătăți semnificativ lizibilitatea codului:
:filepath
filepath "\.zip$" match
filepath fsize 1000000 >
filepath mtime now 3600 - >
and and
Utilizarea variabilelor aduce limbajele concatenative mai aproape de cele aplicative, dar există încă diferențe fundamentale între ele. În limbajele concatenative, programatorul are posibilitatea de a alege să folosească stiva (sau un mecanism similar) sau să declare variabile. În plus, mecanismul de lucru cu variabile este destul de transparent și ușor de gestionat. Acest lucru oferă flexibilitate și posibilitatea unei implementări eficiente și relativ simple.