Programowanie w kodzie binarnym bezwzględnym
Pierwsi programiści pisali w kodzie binarnym bezwzględnym: każda instrukcja & każdy adres w surowych cyfrach binarnych. Pojedyncza instrukcja mogła wyglądać jak 01100101 00001010 — kod instrukcji & adres w pamięci w kodzie binarnym.
Problem kodu spaghetti
Gdy błąd wymagał wstawienia nowej instrukcji, programiści stanęli przed dylematem. Wstawienie na miejscu oznaczało, że każdy następny adres instrukcji przesunął się o jeden — wymagając od programisty aktualizacji każdego odwołania do adresu w całym programie. Katastrofalne.
Rozwiązanie: zastąpić instrukcję tuż przed punktem wstawienia skokiem do pustej pamięci. W tej pustej lokalizacji: napisać przesłonięta instrukcję, dodać nowe instrukcje, a następnie wrócić. Gdy błędy pojawiły się w poprawkach, zastosować tę samą sztuczkę ponownie, używając innej pustej pamięci.
Wynik: ścieżka wykonania przez program przeskoczyła na pozornie losowe lokalizacje. Hamming nazwał to 'puszką spaghetti'. Ścieżka przepływu kontroli, narysowana na papierze, wyglądała dokładnie jak splątane spaghetti.
Drogi wyjścia
Dwie bezpośrednie ulepszenia: notacja ósemkowa (grupowanie cyfr binarnych w zestawy po 3) i szesnastkowa (grupy po 4, używając A–F dla wartości powyżej 9). Te zmniejszyły błędy pisania, ale nie rozwiązały fundamentalnego problemu adresowania.
Asembler symboliczny (np. SAP firmy IBM — Symbolic Assembly Program — i SOAP — Symbolic Optimizing Assembly Program na IBM 650) umożliwił programistom pisanie nazw instrukcji (ADD, MOVE) i symbolicznych etykiet adresowych zamiast kodu binarnego. Asembler przetłumaczył na kod binarny w momencie wejścia, automatycznie zarządzając przypisywaniem adresów.
SOAP wykonał dodatkową optymalizację: układał instrukcje na obracającym się bębnie, aby następna instrukcja dotarła do głowicy odczytującej dokładnie wtedy, gdy poprzednia się zakończyła — kodowanie o minimalnym opóźnieniu. SOAP nawet kompilował się sam: program A przetwarzany jako dane wytwarzał B, a B uruchamiany na A mierzył, jak wiele samokopiacja go poprawiła.
Biblioteki & Kod relokacyjny
Hamming zauważył, że idea oprogramowania do ponownego użytku (bibliotek matematycznych) pojawiła się bardzo wcześnie — Babbage to sobie wyobraził. Problem: biblioteka o adresach bezwzględnych wymagała, aby każda procedura zajmowała te same lokalizacje pamięci za każdym razem, gdy była używana. Gdy łączna biblioteka rosła zbyt duża, programy konkurowały o te same adresy.
Rozwiązanie: kod relokacyjny. Asembler generuje instrukcje, które odnoszą się do pamięci względnie — przesunięcia od adresu bazowego — zamiast adresów bezwzględnych. Konsolidator rozwiązuje ostateczne adresy w momencie załadowania.
Niepublikowane raporty Von Neumann'a (szeroko rozpowszechniane) opisywały niezbędne sztuczki programistyczne. Pierwsza opublikowana książka programistyczna (Wilkes, Wheeler & Gill, EDSAC, 1951) skodyfikowała te techniki.
Rozwidlenie w projektowaniu języka
FORTRAN (1957, IBM) i ALGOL (1958, międzynarodowy komitet) reprezentują dwie filozofie projektowania, które wyprodukowały radykalnie różne wyniki.
FORTRAN
John Backus kierował projektem FORTRAN (FORmula TRANslation) w IBM. Cel projektowania: ułatwić korzystanie z języka naukowcom & inżynierom. FORTRAN akceptował notację matematyczną, która wydawała się naturalna dla swoich użytkowników: A = B + C * D zamiast ADD B, C; STORE T; MULTIPLY T, D; STORE A.
FORTRAN przetrwał ponad 60 lat. Pozostaje w aktywnym użytkowaniu w obliczeniach naukowych, dynamice płynów, modelowaniu klimatu & fizyce obliczeniowej. Hamming zauważył tę trwałość jako dowód udanego projektowania.
ALGOL
ALGOL (ALGOrithmic Language) został zaprojektowany przez komitet logików & informatyków zmierzających do matematycznej rygorystyczności: logicznie czysty, formalnie definiowalny język. Notacja Backusa-Naura (BNF) do opisywania gramatyk została wynaleziona do specyfikacji ALGOL.
ALGOL zawiódł w praktyce. Pomimo jego logicznej elegancji & jego ogromnego wpływu na późniejsze projektowanie języka (Pascal, C, & prawie każdy współczesny język pochodzi z koncepcji gramatyki ALGOL), sam ALGOL nigdy nie został szeroko wdrożony. Werdykt Hamminga: logicznie zaprojektowany, humanistycznie niemożliwy w użyciu.
Hierarchia języków
Hamming opisał naturalną hierarchię od kodu maszynowego przez asembler, języki wyższego poziomu, & ostatecznie 'język zorientowany na problem' bliski temu, jak praktycy myślą o swojej domenie problemu. Każdy poziom dodaje czytelność dla człowieka kosztem efektywności maszyny.
Cztery kryteria projektowania języka Hamminga
Hamming zdystylizował lekcję z FORTRAN vs ALGOL do czterech kryteriów dla udanego języka programowania:
1. Łatwy do nauki — nowicjusz może szybko stać się produktywny
2. Łatwy w użyciu — rutynowe zadania wymagają minimalnej ceremonii
3. Łatwy do debugowania — błędy wytwarzają znaczące, lokalizowalne komunikaty
4. Łatwy w użyciu podprogramów — ponowne użycie & abstrakcja nie wymagają heroicznych wysiłków
Dodał obserwację strukturalną: język ludzki nosi około 60% redundancji; język pisany około 40%. Języki o niskiej redundancji (takie jak APL) wytwarzają eleganckie jednowiersze, które eksperci uważają za piękne & początkujących za niezrozumiałe — & które zawierają niewidoczne błędy, gdy pojedynczy znak zmienia znaczenie.
Implikacja: język zaprojektowany dla logicznej elegancji optymalizuje dla złego czytelnika. Programista jest człowiekiem; ludzie potrzebują redundancji, aby wychwycić błędy & wyjaśnić intencję.
Psychologiczne vs logiczne projektowanie języka
Hamming powrócił do kontrastu FORTRAN/ALGOL jako lekcji z dynamiki instytucjonalnej & ludzkiej, a nie tylko projektowania języka.
FORTRAN był zaprojektowany psychologicznie — dla ludzi, którzy by go używali, konkretnie naukowców, którzy myśleli w notacji matematycznej. ALGOL był zaprojektowany logicznie — dla poprawności formalnej & eleganckiej teoretycznie.
Paradoks, który zidentyfikował Hamming: język logicznie poprawny, któremu ludzie się opierają, zawodzi; język pragmatycznie zaprojektowany, który ludzie przyjmują, odnosi sukces, nawet jeśli jest logicznie bardziej chaotyczny.
Przytoczył APL jako skrajny przypadek: logicznie elegancki, wyrażalny w jednowierszach, z własnym zestawem specjalnych znaków. Eksperci go kochali. Zwykli programiści uważali go za nieczytelny. Pojedyncza zmiana znaku mogła cicho przekształcić znaczenie programu. APL ma małą oddaną społeczność & prawie zero użytku w głównym nurcie.
Argument redundancji ludzkiej: mowa ~60% redundantna (powtórzone kontekst, wyjaśniające słowa, przewidywalna struktura). Język pisany ~40% redundantny. Ta redundancja służy wykrywaniu błędów — ludzie są zawodnicy, więc język ewoluował, aby nosić wystarczającą ilość powtórzonych informacji, aby wychwycić & poprawić błędy. Język o niskiej redundancji usuwa tę sieć bezpieczeństwa.
Hierarchia kompilatora
Hamming opisał warstwy kompilator/interpreter: program może czytać w języku wyższego poziomu & tłumaczyć go na język niższego poziomu. Stosy te warstwy — każda tłumaczy jeden poziom niżej. Na szczycie: język specjalizowany dla domeny, w której eksperci w dziedzinie (biologia, finanse, fizyka) piszą naturalnie. Na dnie: kod maszynowy. Każde przejście to kompilator lub interpreter.
Przewidywanie przetrwania języka
Do 1993 roku Hamming obserwował, że wiele języków odniosło sukces & zawiodło. FORTRAN (1957) przetrwał. ALGOL (1958) zawiódł. COBOL (1959) przetrwał dziesięciolecia w obliczeniach biznesowych. LISP (1958) przetrwał w badaniach AI. PL/I (1964) próbował ujednolicić wszystko & zawiódł.
Powtarzający się wzór
Rozdział historii oprogramowania Hamminga zawiera powtarzającą się strukturę:
1. Istnieje bolesne ograniczenie (adresy bezwzględne, notacja binarna, niemająca do utrzymania kod)
2. Ktoś wynalazł warstwę abstrakcji, która ukrywa ograniczenie
3. Abstrakcja umożliwia nową skalę, która tworzy nowe bolesne ograniczenia
4. Powtórz
Binarny → ósemkowy/szesnastkowy → asembler symboliczny → FORTRAN → programowanie strukturalne → języki obiektowe → języki specjalizowane dla domeny. Każda warstwa rozwiązuje najbardziej ostry ból poprzednika, zamiast wprowadzać nową klasę problemu.
Problem kodu spaghetti (adresy bezwzględne) prowadził do asemblera symbolicznego. Duże programy asemblerowe doprowadziły do FORTRAN. Duże programy FORTRAN doprowadziły do programowania strukturalnego & później do orientacji obiektowej. Wykład Hamminga skończył się przed tymi późniejszymi przejściami, ale wzór się kontynuuje.
Jego lekcja dla inżynierów: zawsze rozwiązujesz ból ujawniony przez poprzednią abstrakcję. Zrozumienie warstwy, na której się znajdujesz, wymaga wiedzy o tym, dlaczego warstwa poniżej istnieje.