Historia nawiasów klamrowych w programowaniu
Kiedy nawiasy klamrowe (które są nam przecież tak dobrze znane) stały się częścią programowania? A co ważniejsze, kiedy bloki kodu stały się częścią struktury programu? Postaram się w tym artykule przytoczyć jakże interesującą historię nawiasów klamrowych w programowaniu, odpowiadając tym samym na wyżej postawione pytania.
Źródło: codingwithchuck.com
Przykład języka bez bloków
Wszyscy programiści znający składnię programowania w stylu C, wiedzą też, co to bloki. Wszystkie główne konstrukcje w językach C, C++, Java, JavaScript i innych mają strukturę blokową — innymi słowy, główne części tych konstrukcji są umieszczane w blokach ograniczonych nawiasami klamrowymi. Jest to jednak kwestia dyskusyjna, ponieważ instrukcje if
oraz pętle mogą być tworzone bez bloków, jeśli ich ciała mają tylko jedną instrukcję.
Jednakże w ciągu ostatnich kilku lat eksperci od stylów programowania zalecali umieszczanie wszystkich ciał konstrukcyjnych w bloku (zobacz „JavaScript — mocne strony” [JavaScript: The Good Parts] Douglasa Cockroft'a, aby przekonać się, że najlepiej jest umieszczać wszystkie ciała instrukcji if
oraz pętli w blokach). Niemniej jednak pierwsze wysokopoziomowe języki programowanie nie miały bloków.
Zademonstruję to za pomocą języka FORTRAN z końca lat pięćdziesiątych. Donald Knuth w swoim doskonałym artykule na temat wczesnych wysokopoziomowych języków programowania o nazwie „The Early Development of Programming Languages” (który również pojawił się w jego książce „Selected Papers on Computer Languages”) porównuje cechy kilku wczesnych języków, pokazując, jak pewien program komputerowy jest implementowany w różnych językach. Warto również dodać, że program ten implementuje algorytm TPK. Oto jak to wszystko wygląda we współczesnym JavaScript:
function f(t) {
return Math.sqrt(Math.abs(t) + 5 * Math.pow(t, 3));
}
let arr = [];
let y;
for (let i = 0; i < 11; i++) {
putstr("Enter a number: ");
arr[i] = parseFloat(readline());
}
for (let i = 10; i >= 0; i--) {
y = f(arr[i])
if (y > 400) {
print(y,"is too large.");
}
else {
print(i,y)
}
}
To, co ten program robi, nie jest tutaj istotne. Ważne jest natomiast to, że wykorzystuje on wszystkie funkcje, które Knuth uznał za istotne w nowoczesnym języku programowania. Knuth porównał kilka języków programowania za pomocą tego algorytmu. Jeden z ostatnich, któremu się przyjrzał, jest obecnie często uważany za pierwszy prawdziwy wysokopoziomowy język programowania. Oto jak wygląda program TPK we Fortranie, według Knutha:
C THE TPK ALGORITHM, FORTRAN STYLE
FUNF(T) = SQRTF(ABSF(T))+5.0*T**3
DIMENSION A(11)
1 FORMAT(6F12.4)
READ 1, A
DO 10 J = 1, 11
I = 11 – J
Y = FUNF(A(I+1))
IF (400.0-Y) 4, 8, 8
4 PRINT 5, I
5 FORMAT(I10, 10H TOO LARGE)
GO TO 10
8 PRINT 9, I, Y
9 FORMAT(I10, F12.7)
10 CONTINUE
STOP 52525
Nie będę wchodził w szczegóły składni Fortrana, ale wyraźnie widać, że język ten nie zawiera bloków. Funkcja jest za to zdefiniowana w jednej linijce, a pętla DO
używa etykiety linii do sterowania pętlą. Wysokopoziomowe języki programowania nie miały w tamtym czasie pojęcia grupowania instrukcji złożonych w bloki i nadal polegały na instrukcjach skoku do kontrolowania przepływu programu.
Powyższy kod został napisany przez Knutha przy użyciu wersji Fortrana z 1957 roku. W latach 1957–1960 powstał język ALGOL, który naprawił wiele wad takich języków, jak Fortran.
Bloki w Algol
Język programowania Algol został po raz pierwszy opisany w 1958 r., ale jego najpopularniejszą wersją jest Algol 60. Jedną z cech Algola jest możliwość grupowania instrukcji w instrukcje złożone, zwane również blokami.
Każdy program Algolu był uważany za blok, ponieważ program zawiera zwykle jedną lub więcej instrukcji (stąd instrukcje złożone). Deweloperzy Algolu uznali, że wiele przypadków programowania, takich jak instrukcje warunkowe oraz pętle, wymagają, aby instrukcje były traktowane jako jednostka.
Algol zaznacza początek i koniec bloku słowami kluczowymi begin
oraz end
. Dany blok może być zagnieżdżony w innym, przy czym blok zewnętrzny uważany jest za ten dominujący, a blok wewnętrzny za podrzędny. Oto przykład programu Algol, który zagnieżdża bloki:
begin real a;
a := 1;
begin real a;
a := 2;
print(a)
end;
print(a)
end
Numery 2 i 1 (w takiej kolejności) są drukowane. Oto przykład użycia bloku w instrukcji if
w Algolu:
if x > -1 then
begin
if x ≠ 0 then
x := 1/x
end;
A oto przykład bloku pętli for
w Algolu:
begin
real a0, a1, a2, a3, z, p;
integer n, i;
read(a0, a1, a2, a3);
read(n)
for i := 1 step 1 until n do
begin
read(z);
p := ((a3 × z + a2) × z + a1) × z + a0
print(p)
end i
end
Spójrzmy na program TPK w Algolu, aby przekonać się, w jaki sposób pomaga mu struktura blokowa, w porównaniu do wersji Fortran:
TPK begin integer i, real y; real a[0:10];
real procedure f(t); real t; value t;
f := sqrt(abs(t)) + 5 × t ↑ 3;
for i := 0 step 1 until 10 do read(a[i]);
for i := 10 step -1 until 0 do
begin y := f(a[i]);
if y > 400 then write(I, 'TOO LARGE')
else write(i, y);
end
end TPK
Teraz widzisz, że struktura blokowa Algola sprawia, że kod jest bardziej współczesny.
Przejście do BCPL
Kolejna zmiana w składni języków o strukturze blokowej nastąpiła wraz z językiem BCPL opracowanym przez Martina Richardsa na Uniwersytecie Cambridge około 1967 roku. W początkowym okresie rozwoju Algolu, czyli między 1960, a 1967 twórcy kompilatorów i systemów szukali sposobów na tworzenie aplikacji systemowych (takich jak systemy operacyjne) przy użyciu języków innych niż języki maszynowe i assembly.
Posługuję się BCPL, ponieważ C został opracowany jako ulepszenie BCPL, poprzez język pośredni o nazwie B opracowany przez Kena Thompsona. Richards opracował BCPL jako język programowania systemów, który był równie wydajny, jak język asemblera. Miał on jednak składnię na wyższym poziomie, aby kodowanie było łatwiejsze. Oznaczało to, że wiele funkcji języków wysokiego poziomu, takich jak Algol, musiało być zawartych w takim języku jak BCPL, ale lepiej.
Można to było na przykład osiągnąć przez uproszczenie oznaczenia bloku kodu. Czyli, zamiast używać słów (begin
oraz end
) lepiej byłoby użyć symboli. W przypadku instrukcji złożonych i bloków Richards wybrał symbole $
dla otwarcia oraz $
dla zamknięcia — nazywane są one nawiasami sekcyjnymi. Jeśli $(
oraz )$
są używane w BCPL z konstrukcją taką jak instrukcja if
lub pętla, to ustawiają limity dla instrukcji złożonej. Jeśli $(
zawiera jakieś deklaracje, to ograniczają one blok.
Oto, w jaki sposób instrukcja if jest zapisywana w BCPL z instrukcjami złożonymi:
IF A < B
$( LET T = A
A := B; B := T
$)
Oto przykład bloku BCPL:
GET "LIBHDR"
LET START() BE
$( LET A, B, C, SUM = 1, 2, 3, 0
SUM := A + B + C
WRITES("Sum is ")
WRITEN(SUM)
$)
Nawias sekcji otwierającej otwiera blok, ponieważ deklaracja następuje bezpośrednio po nim.
$( oraz $) stają się { oraz } w C
Około 1968 lub 1969 roku Ken Thompson i Dennis Ritchie z Bell Labs zaczęli eksperymentować z opracowaniem systemu operacyjnego przy użyciu systemowego języka programowania. Thompson zaczął od użycia Fortrana, ale szybko to porzucił, gdyż zdał sobie sprawę, że nie jest to możliwe. Zdecydował, że musi zmodyfikować BCPL, aby systemowy język programowania bardziej mu odpowiadał, i dlatego rozwinął B.
B, choć bliższy językowi programowania, którego potrzebowali Thompson i Ritchie, wciąż nie zaspokajał całkowicie ich potrzeb. Ritchie postanowił więc opracować kolejny język, zwany NB, dla New B. NB nie żył jednak zbyt długo i ostatecznie został zastąpiony przez zupełnie nową technologię, która naturalnie nazywała się C. Jeśli interesuje Was, jak powstawał C, zachęcam do zapoznania się z Historią języka C Dennisa Ritchiego.
Jedną z wielu rzeczy, które Thompson naprawił w B, były skrócone operatory. Były one niezbędne, aby ulepszony język pasował do ograniczeń pamięci ówczesnych komputerów. Thompson stworzył operatory przypisania złożonego (na przykład + =
) oraz operatory inkrementacji (++
) i dekrementacji (—
) jako sposoby na zwiększenie wygody pisania. Takie ulepszenie doprowadziło do uproszczenia innych operatorów z BCPL, i tutaj przykład: {
i }
zastąpiło $(
oraz $)
.
Jak to wygląda dzisiaj
Nawias klamrowy został uznany za główny symbol bloków w wielu językach programowania, a zwłaszcza w tych, które najbardziej odpowiadają stylowi C, czyli C ++, Java, C# oraz JavaScript.
Co ciekawe, nowsze języki, takie jak Go, czy Rust, również korzystają z nawiasów klamrowych. Właściwie to Go wymaga nawiasów klamrowych dla każdej konstrukcji warunkowej lub pętli, podążając za przykładem ekspertów z dziedziny programowania, którzy twierdzą, że nawet jeśli dany język ich nie wymaga, to programiści powinni używać nawiasów klamrowych, jeśli to możliwe w danej konstrukcji.