Różnica między kodem interpretowanym a skompilowanym jest prawdopodobnie fikcją, jak podkreśla komentarz Raphaela :
the claim seems to be trivially wrong without further assumptions: if there is
an interpreter, I can always bundle interpreter and code in one executable ...
Faktem jest, że kod jest zawsze interpretowany przez oprogramowanie, sprzęt lub kombinację obu tych elementów, a proces kompilacji nie jest w stanie stwierdzić, który z nich będzie.
To, co postrzegasz jako kompilację, to proces tłumaczenia z jednego języka (dla źródła) na inny język (dla celu). I interpreter dla jest zazwyczaj różni się od tłumacza T .T SS.T.S.T.
Skompilowany program jest tłumaczony z jednej formy syntaktycznej na inną formę syntaktyczną P T , tak, że biorąc pod uwagę zamierzoną semantykę języków S i T , P S i P T mają takie samo zachowanie obliczeniowe, do kilku rzeczy, które zazwyczaj próbują zmienić, ewentualnie zoptymalizować, na przykład złożoność lub prostą wydajność (czas, przestrzeń, powierzchnia, zużycie energii). Staram się nie mówić o równoważności funkcjonalnej, ponieważ wymagałoby to precyzyjnych definicji.P.S.P.T.S.T.P.S.P.T.
Niektóre kompilatory zostały w rzeczywistości wykorzystane po prostu do zmniejszenia rozmiaru kodu, a nie do „poprawienia” wykonania. Tak było w przypadku języka używanego w systemie Plato (choć nie nazywali go kompilacją).
Można rozważyć kod pełni skompilowane, jeśli po procesie kompilacji, których już nie potrzebujemy tłumacza . Przynajmniej jest to jedyny sposób, w jaki mogę odczytać twoje pytanie, jako pytanie inżynieryjne, a nie teoretyczne (ponieważ teoretycznie zawsze mogę przebudować tłumacza).S.
Jedną z rzeczy, które mogą powodować problemy, jest meta-okólnik . Wtedy program będzie manipulował strukturami składniowymi we własnym języku źródłowym , tworząc fragment programu, który jest następnie interpretowany tak, jakby był częścią oryginalnego programu. Ponieważ możesz tworzyć dowolne fragmenty programu w języku S w wyniku dowolnego obliczenia manipulującego bezsensownymi fragmentami składniowymi, zgaduję, że możesz prawie uniemożliwić (z technicznego punktu widzenia) skompilowanie programu do języka , tak aby teraz generuje fragmenty . Stąd potrzebny będzie interpreter dla lub przynajmniej kompilator od doS.S.T S S T ST.T.S.S.T. do kompilacji w locie wygenerowanych fragmentów w (patrz także ten dokument ).S.
Ale nie jestem pewien, jak można to właściwie sformalizować (i nie mam na to teraz czasu). I niemożliwe jest wielkie słowo dla kwestii, które nie są sformalizowane.
Dalsze uwagi
Dodano po 36 godzinach. Możesz pominąć tę bardzo długą kontynuację.
Wiele komentarzy do tego pytania pokazuje dwa poglądy na problem: pogląd teoretyczny, który postrzega go jako pozbawiony znaczenia, oraz pogląd inżynieryjny, który niestety nie jest tak łatwo sformalizowany.
Istnieje wiele sposobów spojrzenia na interpretację i kompilację, a ja postaram się naszkicować kilka. Spróbuję być tak nieformalny, jak tylko potrafię
Schemat nagrobków
Jedną z wczesnych formalizacji (od początku lat 60. do końca 1990) są diagramy T lub
Tombstone . Te diagramy przedstawiają w możliwych do skomponowania elementach graficznych język implementacji interpretera lub kompilatora, język źródłowy jest interpretowany lub kompilowany, a język docelowy w przypadku kompilatorów. Bardziej rozbudowane wersje mogą dodawać atrybuty. Te reprezentacje graficzne można postrzegać jako aksjomaty, reguły wnioskowania, przydatne do mechanicznego uzyskiwania generacji procesora z dowodu ich istnienia z aksjomatów à la Curry-Howard (choć nie jestem pewien, czy zrobiono to w latach sześćdziesiątych :).
Częściowa ocena
Innym interesującym poglądem jest paradygmat oceny częściowej . Przyjmuję prosty pogląd na programy jako rodzaj implementacji funkcji, która oblicza odpowiedź na podstawie niektórych danych wejściowych. Następnie tłumacza
dla języka S to program, który ma program p S
napisane w S i danych d dla tego programu, i oblicza wynik Według semantyki S . Częściowa ocena jest techniką specjalizujący program dwoma argumentami 1 i 2 , gdy tylko jeden argument, powiedzmy do 1jaS.S.pSSdSa1a2a1, jest znany. Celem jest szybsza ocena, gdy w końcu otrzymasz drugi argument . Jest to szczególnie przydatne, jeśli 2 zmienia się częściej niż o 1 jako koszt częściowej oceny z pomocą 1 mogą być amortyzowane wszystkich obliczeń, gdzie tylko 2 zmienia się.a2a2a1a1a2
Jest to częsta sytuacja w projektowaniu algorytmów (często temat pierwszego komentarza na temat SE-CS), kiedy pewna bardziej statyczna część danych jest wstępnie przetwarzana, tak że koszt wstępnego przetwarzania może być amortyzowany we wszystkich aplikacjach algorytmu z bardziej zmiennymi częściami danych wejściowych.
Taka jest również sytuacja tłumaczy, ponieważ pierwszym argumentem jest program do wykonania, który zwykle jest wykonywany wiele razy z różnymi danymi (lub ma wiele części wykonywanych z różnymi danymi). Dlatego naturalną ideą stało się specjalizowanie tłumacza do szybszej oceny danego programu poprzez częściową ocenę go w tym programie jako pierwszy argument. Może to być postrzegane jako sposób kompilacji programu, i przeprowadzono znaczące prace badawcze dotyczące kompilacji poprzez częściową ocenę interpretera na jego pierwszym argumencie (programowym).
Twierdzenie Smn
Zaletą częściowego podejścia do oceny jest to, że zakorzenia się ona w teorii (choć teoria może być kłamcą), zwłaszcza w
twierdzeniu Kleene'a Smn . Próbuję tu przedstawić intuicyjnie, mając nadzieję, że nie zdenerwuje to czystych teoretyków.
Biorąc pod uwagę numerację Gödela funkcji rekurencyjnych, możesz postrzegać φ jako swój sprzęt, tak że biorąc pod uwagę numer Gödela p
(odczyt kodu obiektowego ) programu φ p jest funkcją zdefiniowaną przez p (tj. Obliczoną przez kod obiektu na twoim sprzęcie ).φφpφpp
W najprostszej formie twierdzenie to znajduje się w Wikipedii w następujący sposób (do niewielkiej zmiany notacji):
Biorąc pod uwagę numerację Gödela funkcji rekurencyjnych, istnieje pierwotna funkcja rekurencyjna σ dwóch argumentów o następującej właściwości: dla każdej liczby Gödela q częściowej funkcji obliczalnej f z dwoma argumentami wyrażenia φ σ ( q , x ) ( y ) i f ( x , y ) określa się dla tych samych kombinacji liczb naturalnych, x i Y , a ich wartości są takie same dla każdej takiej kombinacji. Innymi słowy, następująca ekstensywna równość funkcji obowiązuje dla każdegoφσqfφσ(q,x)(y)f(x,y)xy :
xφσ( q, x )≃ λ y. φq( x , y) .
Teraz, biorąc za interpreter I S , x jako kod źródłowy programu p S , ay jako dane d dla tego programu, możemy napisać:
qjaS.xpS.yreφσ( JaS., pS.)≃ λ d. φjaS.( pS., d) .
może być postrzegana jako wykonanie tłumacza I S
na sprzęcie, czyli jako czarnej skrzynki gotowy do interpretowania programów napisanych w języku S .φjaS.jaS.S.
Funkcja może być postrzegana jako funkcja, która specjalizuje interpreter I S dla programu P S , tak jak w częściowej ocenie. Zatem liczba Gödel σ ( I S , P S ) można zobaczyć, że zawiera kod wynikowy jest zestawiane wersja programu s S .σjaS.P.S.σ( JaS., pS.)pS.
Więc funkcja może być postrzegane jako funkcja, która przyjmuje jako argument kod źródłowy programu q S
napisany w języku S i zwraca wersję kodu obiektowego dla tego programu. Więc C S jest zwykle nazywane kompilator.doS.= λ qS.. σ( ( IS., qS.)qS.S.doS.
Kilka wniosków
Jednak, jak powiedziałem: „teoria może być kłamcą”, a nawet może wydawać się taką. Problem polega na tym, że nic nie wiemy o funkcji . W rzeczywistości istnieje wiele takich funkcji i sądzę, że dowód twierdzenia może posłużyć się bardzo prostą definicją, która z technicznego punktu widzenia może nie być lepsza niż rozwiązanie zaproponowane przez Raphaela: po prostu połączyć kod źródłowy q S do interpretera I S . Zawsze można to zrobić, abyśmy mogli powiedzieć: kompilacja jest zawsze możliwa.σqS.jaS.
Sformalizowanie bardziej restrykcyjnego pojęcia, czym jest kompilator, wymagałoby bardziej subtelnego podejścia teoretycznego. Nie wiem, co można było zrobić w tym kierunku. Bardzo realna praca nad częściową oceną jest bardziej realistyczna z inżynieryjnego punktu widzenia. Istnieją oczywiście inne techniki pisania kompilatorów, w tym ekstrakcja programów z dowodu ich specyfikacji, opracowana w kontekście teorii typów, w oparciu o izomorfizm Curry-Howarda (ale wychodzę poza zakres moich kompetencji) .
Moim celem tutaj było wykazanie, że uwaga Rafaela nie jest „szalona”, ale rozsądne przypomnienie, że rzeczy nie są oczywiste, a nawet proste. Mówienie, że coś jest niemożliwe, jest mocnym stwierdzeniem, które wymaga precyzyjnych definicji i dowodu, choćby po to, aby dokładnie zrozumieć, w jaki sposób i dlaczego jest to niemożliwe . Ale zbudowanie odpowiedniej formalizacji, aby wyrazić taki dowód, może być dość trudne.
To powiedziawszy, nawet jeśli konkretnej funkcji nie da się skompilować, w sensie zrozumiałym dla inżynierów, standardowe techniki kompilacji można zawsze zastosować do części programów, które nie używają takiej funkcji, jak zauważa odpowiedź Gillesa.
Aby podążać za kluczowymi uwagami Gillesa, że w zależności od języka pewne rzeczy mogą być zrobione w czasie kompilacji, podczas gdy inne muszą być wykonane w czasie wykonywania, a zatem wymagają określonego kodu, widzimy, że koncepcja kompilacji jest w rzeczywistości źle zdefiniowany i prawdopodobnie nie da się go zdefiniować w zadowalający sposób. Kompilacja jest jedynie procesem optymalizacji, co starałem się pokazać w częściowej części oceny , gdy porównałem ją ze statycznym przetwarzaniem danych w niektórych algorytmach.
Jako złożony proces optymalizacji koncepcja kompilacji należy do kontinuum. W zależności od charakterystyki języka lub programu niektóre informacje mogą być dostępne statycznie i pozwalają na lepszą optymalizację. Inne rzeczy należy odłożyć na czas wykonywania. Kiedy wszystko idzie naprawdę źle, wszystko musi być zrobione w czasie wykonywania przynajmniej dla niektórych części programu, a dołączenie kodu źródłowego do interpretera to wszystko, co możesz zrobić. Więc to wiązanie jest tylko dolną częścią tego kontinuum kompilacji. Wiele badań nad kompilatorami dotyczy znalezienia sposobów na statyczne wykonanie tego, co kiedyś robiono dynamicznie. Dobrym przykładem wydaje się zbieranie śmieci w czasie kompilacji.
Zauważ, że powiedzenie, że proces kompilacji powinien generować kod maszynowy, nie jest pomocne. Właśnie to może zrobić pakiet, ponieważ interpreter jest kodem maszynowym (cóż, kompilacja krzyżowa może stać się nieco bardziej złożona).