Jeśli coś można wygenerować, to chodzi o dane, a nie kod.
Biorąc to pod uwagę, czy ten cały pomysł generowania kodu źródłowego nie jest nieporozumieniem? To znaczy, jeśli istnieje jakiś generator kodu, to dlaczego nie uczynić tego czymś właściwym, które może otrzymać wymagane parametry i wykonać właściwe działanie, które wykonałby kod „wygenerowany”?
Jeśli robi się to ze względu na wydajność, brzmi to jak wada kompilatora.
Jeśli robi się to w celu połączenia dwóch języków, brzmi to jak brak biblioteki interfejsów.
Czy coś mi umyka?
Wiem, że kod to także dane. Nie rozumiem tylko, po co generować kod źródłowy ? Dlaczego nie przekształcić go w funkcję, która akceptuje parametry i działa na nie?
flex
lub parser wygenerowany przezbison
prawie na pewno będzie bardziej przewidywalny, bardziej poprawny i często szybszy do wykonania niż odpowiedniki napisane ręcznie w C; i zbudowany z dużo mniejszej ilości kodu (a więc również mniej pracy do utrzymania).Odpowiedzi:
Technicznie, jeśli generujemy kod, nie jest on źródłem, nawet jeśli jest to tekst, który jest czytelny dla ludzi. Kod źródłowy to oryginalny kod, generowany przez człowieka lub inną prawdziwą inteligencję, nie tłumaczony mechanicznie i nie dający się natychmiast odtworzyć z (prawdziwego) źródła (bezpośrednio lub pośrednio).
Powiedziałbym, że i tak wszystko jest danymi . Nawet kod źródłowy. Zwłaszcza kod źródłowy! Kod źródłowy to tylko dane w języku zaprojektowanym do realizacji zadań programistycznych. Dane te należy tłumaczyć, interpretować, kompilować, generować w razie potrzeby na inne formy - danych - z których niektóre mogą być wykonywane.
Procesor wykonuje instrukcje z pamięci. Ta sama pamięć, która jest używana dla danych. Zanim procesor wykona instrukcje, program jest ładowany do pamięci jako dane .
Tak więc wszystko to dane , nawet kod .
Kompilacja wielu kroków jest w porządku, z których jednym może być pośrednie generowanie kodu jako tekstu.
To jeden sposób, ale są też inne.
Nie wszystkie formularze tekstowe są przeznaczone do spożycia przez ludzi. W szczególności wygenerowany kod (jako tekst) jest zwykle przeznaczony do konsumpcji przez kompilator, a nie do konsumpcji przez ludzi.
Kod źródłowy jest uważany za oryginalny: master - to, co edytujemy i rozwijamy; co archiwizujemy za pomocą kontroli kodu źródłowego. Wygenerowany kod, nawet jeśli tekst czytelny dla człowieka, jest zazwyczaj generowany z oryginalnego kodu źródłowego . Generowany kod, ogólnie mówiąc, nie musi być pod kontrolą źródła, ponieważ jest generowany podczas kompilacji.
źródło
Praktyczne rozumowanie
Z tej edycji zakładam, że pytasz na raczej praktycznym poziomie, a nie teoretycznej informatyki.
Klasycznym powodem generowania kodu źródłowego w językach statycznych, takich jak Java, było to, że takie języki po prostu tak naprawdę nie miały łatwych w użyciu narzędzi w języku do robienia bardzo dynamicznych rzeczy. Na przykład w czasach tworzenia Javy po prostu nie było możliwe łatwe utworzenie klasy o nazwie dynamicznej (dopasowanie nazwy tabeli z bazy danych) i metod dynamicznych (dopasowanie atrybutów z tej tabeli) z dynamicznymi typami danych (dopasowanie rodzaje wspomnianych atrybutów). Zwłaszcza, że Java przywiązuje dużą wagę, nie, gwarantuje, że jest w stanie wychwycić błędy typu w czasie kompilacji.
Zatem w takim ustawieniu programista może jedynie tworzyć kod Java i ręcznie pisać wiele wierszy kodu. Często programista przekona się, że za każdym razem, gdy zmienia się tabela, musi wrócić i zmienić kod, aby dopasować; a jeśli zapomni o tym, zdarzają się złe rzeczy. Dlatego programista przejdzie do momentu, w którym napisze kilka narzędzi, które to dla niego zrobią. I tak droga zaczyna się coraz bardziej inteligentnie generować kod.
(Tak, można wygenerować kod bajtowy w locie, ale programowanie takiej rzeczy w Javie nie byłoby czymś, co przypadkowy programista zrobiłby tylko pomiędzy napisaniem kilku linii kodu domeny.)
Porównaj to z bardzo dynamicznymi językami, na przykład Ruby, który pod wieloma względami uważałbym za antytezę dla Javy (zauważ, że mówię to nie doceniając żadnego z tych podejść; są po prostu różne). Tutaj jest w 100% normalne i standardowe, aby dynamicznie generować klasy, metody itp. W czasie wykonywania, a co najważniejsze, programista może to zrobić trywialnie bezpośrednio w kodzie, bez przechodzenia na poziom „meta”. Tak, rzeczy takie jak Ruby on Rails pochodzą z generowania kodu, ale stwierdziliśmy w naszej pracy, że zasadniczo używamy tego jako pewnego rodzaju zaawansowanego „trybu samouczka” dla nowych programistów, ale po pewnym czasie staje się zbędny (ponieważ jest tak mało kodu pisać w tym ekosystemie, że kiedy wiesz, co robisz, ręczne pisanie staje się szybsze niż czyszczenie wygenerowanego kodu).
To tylko dwa praktyczne przykłady z „prawdziwego świata”. Następnie masz języki takie jak LISP, gdzie kod to dane, dosłownie. Z drugiej strony, w skompilowanych językach (bez silnika wykonawczego, takiego jak Java czy Ruby), nie ma (lub było, nie nadążałem za nowoczesnymi funkcjami C ++ ...) po prostu nie ma pojęcia definiowania nazw klas lub metod w czasie wykonywania, tak więc generowanie kodu proces kompilacji jest narzędziem z wyboru dla większości rzeczy (innymi bardziej specyficznymi przykładami dla C / C ++ są takie jak flex, yacc itp.).
źródło
Ponieważ programowanie kartami dziurkowanymi (lub kodami alt w notatniku ) jest uciążliwe.
Prawdziwe. Nie dbam o wydajność, chyba że jestem do tego zmuszony.
Hmm, nie mam pojęcia o czym mówisz.
Wygląda to tak: Wygenerowany i zachowany kod źródłowy jest zawsze i na zawsze uciążliwy. Istnieje tylko z jednego powodu. Ktoś chce pracować w jednym języku, podczas gdy ktoś inny nalega na pracę w innym, a nikomu nie przeszkadza, aby wymyślić sposób współpracy między nimi, więc jeden z nich zastanawia się, jak zamienić swój ulubiony język na język narzucony, aby mogli zrobić to, co chcą.
Co jest w porządku, dopóki nie będę musiał go utrzymać. W którym momencie wszyscy możecie umrzeć.
Czy to jest anty-wzór? Westchnienie, nie. Wiele języków nawet by nie istniało, gdybyśmy nie chcieli pożegnać się z wadami poprzednich języków, a generowanie kodu starszych języków jest początkiem liczby nowych języków.
To podstawa kodu, która pozostała w pół przekształconym patchworku potwora Frankensteina, którego nie znoszę. Wygenerowany kod jest nietykalnym kodem. Nienawidzę patrzeć na nietykalny kod. Jednak ludzie ciągle to sprawdzają. DLACZEGO? Równie dobrze możesz sprawdzać plik wykonywalny.
Cóż, teraz mam na myśli. Chodzi mi o to, że wszyscy „generujemy kod”. To kiedy traktujesz wygenerowany kod jak kod źródłowy, doprowadzasz mnie do szału. Tylko dlatego, że wygląda na to, że kod źródłowy nie czyni go kodem źródłowym.
źródło
/etc/
pliki wNajczęstszym przypadkiem użycia generatorów kodu, z którymi musiałem pracować w swojej karierze, były generatory, które
wziął jako dane wejściowe jakiś meta-opis wysokiego poziomu dla jakiegoś modelu danych lub schematu bazy danych (może to być schemat relacyjny lub jakiś schemat XML)
i wygenerował kod CRUD płyty kotłowej dla klas dostępu do danych jako dane wyjściowe, a może dodatkowe rzeczy, takie jak odpowiednie zapytania SQL lub dokumentacja.
Korzyścią jest to, że z jednego wiersza krótkiej specyfikacji wejściowej otrzymujesz od 5 do 10 wierszy debugowalnego, bezpiecznego typu, wolnego od błędów (zakładając, że dane wyjściowe generatorów kodu są dojrzałe) kodu, który w innym przypadku musiałbyś zaimplementować i utrzymywać ręcznie. Możesz sobie wyobrazić, jak bardzo zmniejsza to nakłady na utrzymanie i rozwój.
Pozwól, że odpowiem również na twoje pierwsze pytanie
Nie, nie generowanie kodu źródłowego per se, ale rzeczywiście istnieją pewne pułapki. Jak stwierdzono w The Pragmatic Programmer , należy unikać używania generatora kodu, gdy wytwarza on kod trudny do zrozumienia . W przeciwnym razie zwiększone wysiłki związane z użyciem lub debugowaniem tego kodu mogą łatwo przewyższyć wysiłek zaoszczędzony przez ręczne pisanie kodu.
Chciałbym również dodać, że zazwyczaj dobrym pomysłem jest oddzielenie generowanych części kodu od kodu napisanego ręcznie, w taki sposób, aby ponowne generowanie nie zastępowało żadnych ręcznych zmian. Jednak kilkakrotnie poradziłem sobie z sytuacją, w której zadaniem było migrowanie kodu napisanego w starym języku X do innego, bardziej nowoczesnego języka Y, z zamiarem późniejszego utrzymania go w języku Y. Jest to prawidłowe użycie przypadek generowania kodu jednorazowego.
źródło
Napotkałem dwa przypadki użycia wygenerowanego (w czasie kompilacji i nigdy nie zalogowanego) kodu:
źródło
Sussmann miał wiele ciekawych rzeczy do powiedzenia na temat takich rzeczy w swoim klasycznym „Strukturze i interpretacji programów komputerowych”, głównie o dualności kod-dane.
Dla mnie głównym zastosowaniem generowania kodu adhoc jest wykorzystanie dostępnego kompilatora do konwersji jakiegoś małego języka specyficznego dla domeny na coś, co mogę połączyć z moimi programami. Pomyśl o BNF, pomyśl o ASN1 (Właściwie nie, to jest brzydkie), pomyśl o arkuszach kalkulacyjnych słownika danych.
Ciekawe języki specyficzne dla domeny mogą znacznie zaoszczędzić czas, a tworzenie czegoś, co można skompilować za pomocą standardowych narzędzi językowych, jest właściwą drogą przy tworzeniu takich rzeczy, które wolisz edytować, niebanalny parser zhakowany ręcznie w dowolnym języku ojczystym, jakim jesteś. pisanie czy BNF dla automatycznie wygenerowanego?
Wysyłając tekst, który jest następnie podawany do jakiegoś kompilatora systemowego, uzyskuję optymalizację tych kompilatorów i konfigurację specyficzną dla systemu bez konieczności myślenia o tym.
Skutecznie używam języka wejściowego kompilatora jako kolejnej pośredniej reprezentacji, na czym polega problem? Pliki tekstowe nie są z natury kodami źródłowymi, mogą być IR dla kompilatora , a jeśli wyglądają jak C, C ++, Java lub cokolwiek innego, kogo to obchodzi?
Teraz, jeśli nie myślisz , że możesz edytować WYJŚCIE analizatora składni języka zabawek, co wyraźnie rozczaruje, gdy następnym razem ktoś edytuje pliki języka wejściowego i przebudowuje, odpowiedzią jest, aby nie zatwierdzać automatycznie wygenerowanej podczerwieni do repozytorium, generowane przez Twój zestaw narzędzi (I unikaj posiadania takich osób w grupie deweloperów, zwykle są bardziej zadowoleni z pracy w marketingu).
W naszych językach nie jest to tak bardzo porażką ekspresyjności, jak wyrazem faktu, że czasami można uzyskać (lub masować) części specyfikacji w formie, która może zostać automatycznie przekonwertowana na kod, i która zwykle spłodzi znacznie mniej błędów i być o wiele łatwiejszym w utrzymaniu. Jeśli mogę dać naszym testerom i konfiguratorom arkusz kalkulacyjny, który mogą ulepszyć, a następnie uruchomić narzędzie, które pobiera te dane i wyrzuca pełny plik heksadecymalny dla Flasha na moim ECU, to ogromna oszczędność czasu na ręcznym tłumaczeniu najnowsza konfiguracja w zestawie stałych w języku dnia (w komplecie literówki).
To samo dotyczy budowania modeli w Simulink, a następnie generowania C za pomocą RTW, a następnie kompilowania do celu za pomocą dowolnego narzędzia sensownego, pośrednie C jest nieczytelne, więc co? Wysoki poziom Matlab RTW musi znać tylko podzbiór języka C, a kompilator C zajmuje się szczegółami platformy. Jedyny raz człowiek musi przeszukiwać wygenerowane C, gdy skrypty RTW mają błąd, a tego rodzaju rzeczy jest o wiele łatwiejsze do debugowania za pomocą nominalnie czytelnej dla człowieka podczerwieni niż tylko binarnego drzewa parsowania.
Możesz oczywiście napisać takie rzeczy do kodu bajtowego lub nawet kodu wykonywalnego, ale dlaczego miałbyś to zrobić? Mamy narzędzia do konwersji IR na te rzeczy.
źródło
Pragmatyczna odpowiedź: czy generowanie kodu jest konieczne i przydatne? Czy zapewnia coś, co jest naprawdę bardzo przydatne i potrzebne dla zastrzeżonej bazy kodów, czy też wydaje się, że po prostu tworzy inny sposób robienia rzeczy w sposób, który przyczynia się do większego intelektualnego obciążenia dla uzyskania nieoptymalnych wyników?
Jeśli musisz zadać to pytanie i nie ma jednoznacznej odpowiedzi, prawdopodobnie generowanie kodu jest zbędne i jedynie przyczynia się do egzotyki oraz znacznego intelektualnego obciążenia bazy kodowej.
Tymczasem jeśli weźmiesz coś takiego jak OpenShadingLanguage: https://github.com/imageworks/OpenShadingLanguage
... to nie trzeba stawiać takich pytań, ponieważ natychmiastowe odpowiedzi dają imponujące wyniki.
W takim przypadku nie trzeba kwestionować istnienia generatora kodu. Jeśli pracujesz w tego typu domenach VFX, twoja natychmiastowa reakcja zwykle brzmi: „zamknij się i weź moje pieniądze!” lub „wow, musimy też zrobić coś takiego”.
źródło
Nie, generowanie kodu pośredniego nie jest anty-wzorcem. Odpowiedź na drugą część twojego pytania „Dlaczego to robisz?” Jest bardzo szerokim (i osobnym) pytaniem, chociaż i tak podam kilka powodów.
Historyczne konsekwencje braku pośredniego kodu czytelnego dla człowieka
Weźmy jako przykłady C i C ++, ponieważ należą one do najbardziej znanych języków.
Należy zauważyć, że logiczny proces kompilacji kodu C generuje nie kod maszynowy, ale raczej kod zestawu czytelny dla człowieka. Podobnie, stare kompilatory C ++ służą do fizycznej kompilacji kodu C ++ do kodu C. W tym łańcuchu zdarzeń można skompilować kod od czytelnego dla człowieka kodu 1 do czytelnego dla człowieka kodu 2 do czytelnego dla człowieka kodu 3 do kodu maszynowego. "Dlaczego?" Dlaczego nie?
Jeśli pośredni, czytelny dla człowieka kod nigdy nie zostałby wygenerowany, moglibyśmy w ogóle nie mieć C lub C ++. Z pewnością jest to możliwa; ludzie podążają ścieżką najmniejszego oporu wobec swoich celów, a jeśli jakiś inny język zyskałby na popularności z powodu stagnacji rozwoju C, C mógł umrzeć, gdy był jeszcze młody. Oczywiście można by się spierać: „Ale może użylibyśmy innego języka, a może byłoby lepiej”. Może, a może byłoby gorzej. A może wszyscy nadal piszemy na zgromadzeniu.
Dlaczego warto korzystać z pośredniego kodu czytelnego dla człowieka?
Przykład
Wcześniej pracowałem nad projektami, w których należy generować kod na podstawie danych lub informacji zawartych w innym dokumencie. Na przykład jeden projekt miał wszystkie swoje wiadomości sieciowe i stałe dane zdefiniowane w arkuszu kalkulacyjnym oraz narzędzie, które przechodzi przez arkusz kalkulacyjny i generuje dużo kodu C ++ i Java, które pozwalają nam pracować z tymi wiadomościami.
Nie twierdzę, że był to najlepszy sposób na skonfigurowanie tego projektu (nie byłem częścią jego uruchomienia), ale to właśnie mieliśmy i były to setki (może nawet tysiące, nie jestem pewien) struktur i obiektów oraz stałych które były generowane; w tym momencie prawdopodobnie jest już za późno, aby spróbować przerobić go na coś w rodzaju Rhapsody. Ale nawet jeśli zostałyby przerobione w coś takiego jak Rhapsody, to i tak mamy kod wygenerowany z Rhapsody .
Ponadto posiadanie wszystkich tych danych w arkuszu kalkulacyjnym było dobre pod jednym względem: pozwoliło nam reprezentować dane w sposób, którego nie moglibyśmy uzyskać, gdyby były to tylko pliki kodu źródłowego.
Przykład 2
Kiedy pracowałem przy budowie kompilatora, użyłem narzędzia Antlr do wykonania leksykalizacji i parsowania. Podałem gramatykę języka, następnie użyłem narzędzia do wyplucia ton kodu w C ++ lub Javie, a następnie użyłem wygenerowanego kodu obok mojego własnego kodu i umieściłem go w kompilacji.
Jak inaczej należy to zrobić? Być może mógłbyś wymyślić inny sposób; prawdopodobnie są inne sposoby. Ale w przypadku tej pracy inne sposoby nie byłyby lepsze niż wygenerowany kod leksykalny / parsujący, który miałem.
źródło
To, czego brakuje, to ponowne użycie .
Mamy niesamowite narzędzie do przekształcania kodu źródłowego w binarny, zwane kompilatorem. Jego dane wejściowe są dobrze zdefiniowane (zwykle!), A wiele pracy wymagało dopracowania optymalizacji. Jeśli chcesz używać kompilatora do wykonywania niektórych operacji, chcesz użyć istniejącego kompilatora, a nie pisać własnego.
Wiele osób wymyśla nowe języki programowania i pisze własne kompilatory. Prawie bez wyjątku wszyscy to robią, ponieważ lubią wyzwania, a nie dlatego, że potrzebują funkcji zapewnianych przez ten język. Wszystko, co robią, można zrobić w innym języku; po prostu tworzą nowy język, ponieważ lubią te funkcje. Tym, co ich nie dostanie, jest dobrze dostrojony, szybki, wydajny, optymalizujący kompilator. To da im coś, co może zamienić tekst na binarny, jasne, ale nie będzie tak dobre, jak wszystkie istniejące kompilatory .
Tekst to nie tylko coś, co ludzie czytają i piszą. Komputery są również w domu z tekstem. W rzeczywistości formaty takie jak XML (i inne powiązane formaty) są skuteczne, ponieważ używają zwykłego tekstu. Formaty plików binarnych są często niejasne i źle udokumentowane, a czytelnik nie może łatwo dowiedzieć się, jak działają. XML jest stosunkowo samo-dokumentujący, co ułatwia ludziom pisanie kodu korzystającego z plików w formacie XML. Wszystkie języki programowania są skonfigurowane do odczytu i zapisu plików tekstowych.
Załóżmy, że chcesz dodać nowy obiekt, aby ułatwić Ci życie. Być może jest to narzędzie do układania GUI. Być może jest to interfejs sygnałów i gniazd, który zapewnia Qt . Być może jest to sposób, w jaki TI Code Composer Studio pozwala skonfigurować urządzenie, z którym pracujesz i pobrać odpowiednie biblioteki do kompilacji. Być może zajmuje to słownik danych i automatyczne generowanie typów i definicji zmiennych globalnych (tak, nadal jest to bardzo ważne w oprogramowaniu wbudowanym). Cokolwiek to jest, najskuteczniejszym sposobem na wykorzystanie istniejącego kompilatora jest stworzenie narzędzia, które weźmie twoją konfigurację, czymkolwiek jest i automatycznie wygeneruje kod w wybranym języku.
Jest łatwy do opracowania i przetestowania, ponieważ wiesz, co się dzieje i możesz odczytać wyrzucony przez niego kod źródłowy. Nie musisz tracić lat na budowanie kompilatora, który może konkurować z GCC. Nie musisz uczyć się zupełnie nowego języka ani wymagać od innych osób. Wszystko, co musisz zrobić, to zautomatyzować ten jeden mały obszar, a wszystko inne pozostanie takie samo. Zadanie wykonane.
źródło
Trochę bardziej pragmatyczna odpowiedź, koncentrująca się na tym, dlaczego, a nie na tym, co jest i nie jest kodem źródłowym. Zauważ, że generowanie kodu źródłowego jest częścią procesu kompilacji we wszystkich tych przypadkach - więc wygenerowane pliki nie powinny znaleźć się w kontroli źródła.
Interoperacyjność / prostota
Weźmy na przykład Bufory protokołów Google, doskonały przykład: piszesz pojedynczy opis protokołu wysokiego poziomu, który można następnie wykorzystać do wygenerowania implementacji w wielu językach - często różne części systemu są napisane w różnych językach.
Realizacja / powody techniczne
Weź TypeScript - przeglądarki nie mogą go interpretować, więc proces kompilacji używa transpilatora ( translatora kodu na kod) do generowania JavaScript. W rzeczywistości wiele nowych lub ezoterycznych skompilowanych języków zaczyna się od transpozycji do C, zanim otrzyma odpowiedni kompilator.
Łatwość użycia
W przypadku projektów osadzonych (myśl IoT) napisanych w C i wykorzystujących tylko jeden plik binarny (RTOS lub brak systemu operacyjnego) dość łatwo jest wygenerować tablicę C z danymi do kompilacji, jak w przypadku normalnego kodu źródłowego, zamiast łączenia ich bezpośrednio jako zasoby.
Edytować
Rozszerzanie protokołu protobuf: generowanie kodu pozwala generować obiekty jako klasy pierwszej klasy w dowolnym języku. W języku skompilowanym ogólny parser z konieczności zwróciłby strukturę klucz-wartość - co oznacza, że nie masz dużo kodu generatora, brakuje niektórych kontroli czasu kompilacji (w szczególności kluczy i typów wartości), uzyskujesz gorszą wydajność i bez uzupełniania kodu. Wyobraź sobie wszystkie te
void*
w C lub tak dużestd::variant
w C ++ (jeśli masz C ++ 17), niektóre języki mogą w ogóle nie mieć takiej funkcji.źródło
Jest to obejście dla niewystarczająco ekspresyjnego języka programowania. Nie ma potrzeby generowania kodu w języku zawierającym odpowiednie wbudowane metaprogramowanie.
źródło
Generowanie kodu źródłowego nie zawsze jest anty-wzorcem. Na przykład piszę obecnie środowisko, które na podstawie podanej specyfikacji generuje kod w dwóch różnych językach (JavaScript i Java). Środowisko wykorzystuje wygenerowany Javascript do rejestrowania działań przeglądarki użytkownika, a kod Java w Selenium faktycznie wykonuje akcję, gdy środowisko jest w trybie odtwarzania. Gdybym nie używał generowania kodu, musiałbym ręcznie upewnić się, że oba są zawsze zsynchronizowane, co jest uciążliwe, a także logiczne.
Jeśli jednak używa się generowania kodu źródłowego do zastępowania funkcji takich jak generyczne, to jest to anty-wzorzec.
źródło
Może dobry przykład, w którym kod pośredni okazał się przyczyną sukcesu? Mogę zaoferować Ci HTML.
Uważam, że HTML był prosty i statyczny - ułatwiał tworzenie przeglądarek, umożliwiał wczesne uruchamianie przeglądarek mobilnych itp. Jak pokazały dalsze eksperymenty (aplety Java, Flash) - bardziej złożone i wydajne języki prowadzą do większej liczby problemów . Okazuje się, że użytkownicy są w rzeczywistości zagrożeni przez aplety Java, a odwiedzanie takich stron było równie bezpieczne, jak wypróbowanie pęknięć gier pobranych przez DC ++. Z drugiej strony zwykły HTML jest na tyle nieszkodliwy, że pozwala nam sprawdzić każdą witrynę z uzasadnioną wiarą w bezpieczeństwo naszego urządzenia.
Jednak HTML nie byłby w pobliżu, gdzie jest teraz, gdyby nie był generowany komputerowo. Moja odpowiedź nie pojawiłaby się nawet na tej stronie, dopóki ktoś ręcznie nie przepisał jej z bazy danych do pliku HTML. Na szczęście możesz zrobić użyteczny HTML w prawie każdym języku programowania :)
Czy możesz sobie wyobrazić lepszy sposób wyświetlania pytania oraz wszystkich odpowiedzi i komentarzy dla użytkownika niż użycie HTML jako generowanego kodu pośredniego?
źródło
Ponieważ jest to szybsze i łatwiejsze (i mniej podatne na błędy) niż ręczne pisanie kodu, szczególnie w przypadku żmudnych, powtarzalnych zadań. Możesz także użyć narzędzia wysokiego poziomu, aby zweryfikować i zweryfikować swój projekt przed napisaniem pojedynczego wiersza kodu.
Typowe przypadki użycia:
Jeśli chodzi o „dlaczego nie uczynić go funkcją i bezpośrednio przekazywać parametry”, należy zauważyć, że żadne z powyższych nie jest środowiskiem wykonawczym samym w sobie. Nie ma możliwości powiązania kodu z nimi.
źródło
Czasami twój język programowania po prostu nie ma potrzebnych udogodnień, co uniemożliwia pisanie funkcji lub makr, aby robić to, co chcesz. A może mógłby zrobić to, co chcesz, ale kod napisać byłoby brzydkie. Prosty skrypt Pythona (lub podobny) może następnie wygenerować wymagany kod w ramach procesu kompilacji, który następnie zostanie
#include
umieszczony w rzeczywistym pliku źródłowym.Skąd to wiem? Ponieważ jest to rozwiązanie, do którego wielokrotnie sięgałem podczas pracy z różnymi różnymi systemami, ostatnio SourcePawn. Prosty skrypt Pythona, który analizuje prosty wiersz kodu źródłowego i generuje dwa lub trzy wiersze wygenerowanego kodu, jest znacznie lepszy niż ręczne tworzenie wygenerowanego kodu, gdy skończysz z dwoma tuzinami takich wierszy (tworząc wszystkie moje cvary).
Przykładowy / przykładowy kod źródłowy dostępny, jeśli ludzie tego chcą.
źródło
Formularz tekstowy jest wymagany do łatwego spożycia przez ludzi. Komputery dość łatwo przetwarzają kod w formie tekstowej. Dlatego generowany kod powinien być generowany w formie, która jest najłatwiejsza do wygenerowania i najłatwiejsza do wykorzystania przez komputery i która jest bardzo często czytelnym tekstem.
A kiedy generujesz kod, sam proces generowania kodu często wymaga debugowania - przez ludzi. Jest to bardzo, bardzo przydatne, jeśli wygenerowany kod jest czytelny dla człowieka, dzięki czemu ludzie mogą wykryć problemy w procesie generowania kodu. W końcu ktoś musi napisać kod, aby wygenerować kod. Nie dzieje się to z powietrza.
źródło
Generowanie kodu, tylko raz
Nie każde generowanie kodu źródłowego polega na wygenerowaniu jakiegoś kodu, a następnie nigdy go nie dotykaniu; a następnie generowanie go z oryginalnego źródła, gdy wymaga aktualizacji.
Czasami generujesz kod tylko raz, a następnie odrzucasz oryginalne źródło i przechodząc dalej utrzymuj nowe źródło.
Zdarza się to czasami podczas przenoszenia kodu z jednego języka na inny. W szczególności, jeśli nie oczekuje się późniejszego przeniesienia nowych zmian w oryginale (np. Stary kod języka nie zostanie zachowany lub jest faktycznie kompletny (np. W przypadku niektórych funkcji matematycznych)).
Jednym z powszechnych przypadków jest to, że napisanie generatora kodu, aby to zrobić, może właściwie tylko poprawnie przetłumaczyć 90% kodu. a następnie te ostatnie 10% należy naprawić ręcznie. Co jest znacznie szybsze niż ręczne tłumaczenie 100%.
Takie generatory kodu często bardzo różnią się od generatorów kodu, które
f2c
produkują tłumacze w pełnym języku (jak Cython lub ). Ponieważ celem jest jednokrotne utrzymanie kodu. Często są one wykonywane jako 1 off, aby robić dokładnie to, co muszą. Pod wieloma względami jest to wersja następnego poziomu użycia regex / find-replace do kodu portu. „Portowanie wspomagane przez narzędzie” można powiedzieć.Generowanie kodu, tylko raz, na przykład ze strony internetowej.
Ściśle powiązane jest to, jeśli generujesz kod z jakiegoś źródła, do którego nie chcesz ponownie uzyskiwać dostępu. Np. Jeśli działania potrzebne do wygenerowania kodu nie są powtarzalne, spójne lub ich wykonanie jest kosztowne. Obecnie pracuję nad parą projektów: DataDeps.jl i DataDepsGenerators.jl .
DataDeps.jl pomaga użytkownikom pobierać dane (takie jak standardowe zestawy danych ML). Aby to zrobić, potrzebuje czegoś, co nazywamy RegistrationBlock. Jest to kod określający niektóre metadane, na przykład skąd pobierać pliki, sumę kontrolną oraz komunikat wyjaśniający użytkownikowi wszelkie warunki / kodeksy / status licencji na dane.
Pisanie tych bloków może być denerwujące. Informacje te są często dostępne w (ustrukturyzowanych lub nieustrukturyzowanych) na stronach internetowych, na których przechowywane są dane. Tak więc DataDepsGenerators.jl używa skrobaka do generowania kodu RegistrationBlockCode dla niektórych witryn, które przechowują wiele danych.
Może to nie wygenerować ich poprawnie. Tak więc deweloper korzystający z wygenerowanego kodu może i powinien to sprawdzić i poprawić. Są szanse, że chcą się upewnić, że nie zepsuły na przykład informacji licencyjnych.
Co ważne, użytkownicy / deweloperzy pracujący z DataDeps.jl nie muszą instalować ani używać skryptu do korzystania z wygenerowanego kodu RegistrationBlock. (Nie trzeba pobierać i instalować skrobaka internetowego, który pozwala zaoszczędzić sporo czasu. Szczególnie w przypadku CI)
Jednorazowe wygenerowanie kodu źródłowego nie jest antypatternem. i zwykle nie można go zastąpić metaprogramowaniem.
źródło
f2c
+cc
), ale wynikowy kod nie był tak naprawdę dobrym punktem wyjścia dla wersji C programu, AFAIK.f2c
dnia)sed
przechodzi długą drogę, ale czasami potrzeba nieco bardziej wyrazistej mocy. Granica między logiką programu a danymi jest często dobra. Czasami rozróżnienie nie jest przydatne. JSON to (/ was) kod konstruktora obiektów javascript. W moim przykładzie generuję również kod konstruktora obiektów (czy to dane? Może (może nie, ponieważ czasami ma wywołania funkcji). Czy lepiej jest traktować go jako kod? Tak.)Generowanie kodu „źródłowego” wskazuje na niedociągnięcie generowanego języka. Czy używanie narzędzi w celu przezwyciężenia tego problemu jest anty-wzorem? Absolutnie nie - wyjaśnię.
Zazwyczaj stosuje się generowanie kodu, ponieważ istnieje definicja wyższego poziomu, która może opisać wynikowy kod znacznie mniej szczegółowy niż język niższego poziomu. Tak więc generowanie kodu ułatwia wydajność i zwięzłość.
Kiedy piszę c ++, robię to, ponieważ pozwala mi to pisać kod wydajniej niż przy użyciu asemblera lub kodu maszynowego. Nadal kod maszynowy jest generowany przez kompilator. Na początku c ++ był po prostu preprocesorem, który generował kod C. Języki ogólnego przeznaczenia doskonale nadają się do generowania zachowań ogólnego przeznaczenia.
W ten sam sposób, używając DSL (języka specyficznego dla domeny), można pisać zwięzłe, ale być może kod ograniczony do konkretnego zadania. To sprawi, że wygenerowanie poprawnego działania kodu będzie mniej skomplikowane. Pamiętaj, że kod oznacza koniec i koniec . Deweloper szuka efektywnego sposobu generowania zachowań.
Idealnie generator może tworzyć szybki kod na podstawie danych wejściowych, które są łatwiejsze w obsłudze i zrozumieniu. Jeśli zostanie to spełnione, nie użycie generatora jest anty-wzorcem . Ten anty-wzór zazwyczaj wynika z faktu, że „czysty” kod jest „czystszy”, podobnie jak pracownik drewna lub inny rzemieślnik może patrzeć na użycie elektronarzędzi lub użycie CNC do „generowania” detali (myśl złota młot ).
Z drugiej strony, jeśli źródło wygenerowanego kodu jest trudniejsze do utrzymania lub wygenerowania kodu, który nie jest wystarczająco wydajny, użytkownik wpada w pułapkę używania niewłaściwych narzędzi (czasami z powodu tego samego złotego młota ).
źródło
Generowanie kodu źródłowego absolutnie oznacza, że generowany kod to dane. Ale są to dane pierwszej klasy, dane, którymi reszta programu może manipulować.
Dwa najczęstsze typy danych, o których wiem, że są zintegrowane z kodem źródłowym, to graficzne informacje o oknach (liczba i rozmieszczenie różnych kontrolek) oraz ORM. W obu przypadkach integracja poprzez generowanie kodu ułatwia manipulowanie danymi, ponieważ nie trzeba wykonywać dodatkowych „specjalnych” kroków, aby z nich skorzystać.
Podczas pracy z oryginalnymi komputerami Mac (1984) definicje okien i okien zostały utworzone za pomocą edytora zasobów, który utrzymywał dane w formacie binarnym. Korzystanie z tych zasobów w aplikacji było trudniejsze niż byłoby, gdyby „format binarny” to Pascal.
Tak więc nie, generowanie kodu źródłowego nie jest anty-wzorcem, pozwala na włączenie danych do aplikacji, co ułatwia korzystanie z nich.
źródło
Generowanie kodu jest anty-wzorcem, gdy kosztuje więcej niż osiąga. Taka sytuacja ma miejsce, gdy generowanie odbywa się z A do B, gdzie A jest prawie tym samym językiem co B, ale z pewnymi drobnymi rozszerzeniami, które można wykonać po prostu kodując w A przy mniejszym wysiłku niż wszystkie niestandardowe narzędzia i tworzenie scen dla A do B .
Kompromis jest bardziej zapobiegający generowaniu kodu w językach, które nie mają funkcji metaprogramowania (makra strukturalne) ze względu na komplikacje i niedociągnięcia związane z uzyskaniem metaprogramowania poprzez etapowanie zewnętrznego przetwarzania tekstu.
Słaby kompromis może mieć również związek z ilością zastosowań. Język A może znacznie różnić się od B, ale cały projekt z niestandardowym generatorem kodu wykorzystuje A tylko w jednym lub dwóch małych miejscach, dzięki czemu całkowita złożoność (małe bity A oraz generator kodu A -> B, plus etapowanie kompilacji otaczającej) przekracza złożoność rozwiązania wykonanego właśnie w B.
Zasadniczo, jeśli zaangażujemy się w generowanie kodu, powinniśmy prawdopodobnie „pójść na całość lub wrócić do domu”: sprawić, by miał znaczną semantykę i często go używać, albo nie zawracaj sobie tym głowy.
źródło
Nie widziałem tego wyraźnie (zauważyłem, że dotknęło go jedno lub dwie odpowiedzi, ale nie wydawało się to zbyt jasne)
Generowanie kodu (jak powiedziałeś, jakby to były dane) nie stanowi problemu - jest to sposób na ponowne użycie kompilatora w celu dodatkowym.
Edytowanie wygenerowanego kodu jest jednym z najbardziej podstępnych, złych, przerażających anty-wzorów, jakie kiedykolwiek spotkałeś. Nie rób tego.
W najlepszym wypadku edycja wygenerowanego kodu powoduje wciągnięcie do twojego projektu szeregu słabych kodów (CAŁY zestaw kodów jest teraz naprawdę KODEM ŹRÓDŁA - nie ma już danych). W najgorszym wypadku kod ściągnięty do twojego programu jest wysoce redundantny, źle nazwany śmieci, który jest prawie całkowicie nie do utrzymania.
Podejrzewam, że trzecią kategorią jest kod, którego używasz raz (generator GUI?), A następnie edytuj, aby pomóc Ci zacząć / uczyć się. To jest po trochu każdy - może to być dobry sposób na rozpoczęcie, ale twój generator GUI będzie ukierunkowany na użycie kodu „Generowalnego”, który nie będzie świetnym początkiem dla Ciebie jako programisty - Ponadto możesz być kusiło mnie, aby użyć go ponownie dla drugiego GUI, co oznacza ściągnięcie nadmiarowego kodu SOURCE do twojego systemu.
Jeśli Twoje narzędzie jest wystarczająco inteligentne, aby uniemożliwić jakiekolwiek edycje generowanego kodu, idź do niego. Jeśli nie, nazwałbym to jednym z najgorszych anty-wzorów.
źródło
Kod i dane to: Informacje.
Dane to informacje dokładnie w takiej formie, w jakiej potrzebujesz (i wartości). Kod jest również informacją, ale w formie pośredniej lub pośredniej. Zasadniczo kod jest również formą danych.
Mówiąc dokładniej, kod jest informacją dla maszyn, która zwalnia ludzi z samodzielnego przetwarzania informacji.
Najważniejszym motywem jest odciążenie ludzi od przetwarzania informacji. Kroki pośrednie są dopuszczalne, o ile ułatwiają życie. Dlatego istnieją pośrednie narzędzia do mapowania informacji. Jak generatory kodów, kompilatory, transpilatory itp.
Powiedzmy, że ktoś oferuje taką funkcję mapowania, której implementacja jest dla Ciebie niejasna. Jeśli funkcja działa zgodnie z obietnicą, czy obchodzi Cię, czy wewnętrznie generuje kod źródłowy, czy nie?
źródło
Ponieważ później określasz, że kodem są dane, twoja propozycja ogranicza się do „Jeśli coś można wygenerować, to nie jest to kod”. Czy powiedziałbyś zatem, że kod asemblera wygenerowany przez kompilator C nie jest kodem? Co się stanie, jeśli zdarzy się, że dokładnie pokrywa się z kodem asemblera, który piszę ręcznie? Możesz tam pójść, jeśli chcesz, ale nie przyjdę z tobą.
Zamiast tego zacznijmy od definicji „kodu”. Nie wymagając zbyt technicznej wiedzy, całkiem dobrą definicją do celów tej dyskusji byłyby „instrukcje uruchamiane maszynowo do wykonywania obliczeń”.
Cóż, twoja początkowa propozycja jest taka, że kodu nie można wygenerować, ale odrzucam tę propozycję. Jeśli zaakceptujesz moją definicję „kodu”, nie powinno być problemu koncepcyjnego z generowaniem kodu w ogóle.
Cóż, to zupełnie inne pytanie o powód zastosowania generowania kodu, a nie o jego naturze. Proponujesz alternatywę, w której zamiast pisać lub używać generatora kodu, pisze się funkcję, która oblicza wynik bezpośrednio. Ale w jakim języku? Dawno minęły czasy, kiedy ktoś pisał bezpośrednio w kodzie maszynowym, a jeśli piszesz swój kod w innym języku, polegasz na generatorze kodu w postaci kompilatora i / lub asemblera, aby stworzyć program, który faktycznie działa.
Dlaczego więc wolisz pisać w Javie, C, Lisp lub czymkolwiek innym? Nawet asembler? Twierdzę, że przynajmniej częściowo dlatego, że te języki zapewniają abstrakcje danych i operacji, które ułatwiają wyrażenie szczegółów obliczeń, które chcesz wykonać.
To samo dotyczy większości generatorów kodu wyższego poziomu. Prototypowe przypadki to prawdopodobnie generatory skanerów i analizatorów składni, takie jak
lex
iyacc
. Tak, możesz napisać skaner i analizator składni bezpośrednio w C lub w innym wybranym przez siebie języku programowania (nawet w surowym kodzie maszynowym), a czasem tak jest. Jednak w przypadku problemu o znacznej złożoności użycie języka specjalnego, wyższego poziomu, takiego jak lex lub yacc, ułatwia pisanie, czytanie i utrzymywanie odręcznego kodu. Zwykle też znacznie mniejszy.Powinieneś także rozważyć, co dokładnie rozumiesz przez „generator kodu”. Rozważałbym wstępne przetwarzanie C i tworzenie szablonów C ++ jako ćwiczenia w generowaniu kodu; sprzeciwiasz się tym? Jeśli nie, to myślę, że musisz wykonać gimnastykę umysłową, aby zracjonalizować akceptację tych, ale odrzucając inne smaki generowania kodu.
Dlaczego? Zasadniczo zakładasz, że należy mieć uniwersalny program, do którego użytkownik podaje dane, niektóre sklasyfikowane jako „instrukcje”, a inne jako „dane wejściowe”, i które kontynuują obliczenia i emitują więcej danych, które nazywamy „danymi wyjściowymi”. (Z pewnego punktu widzenia taki uniwersalny program można nazwać „systemem operacyjnym”.) Ale dlaczego przypuszczasz, że kompilator powinien być tak samo skuteczny w optymalizacji takiego programu ogólnego, jak w optymalizacji bardziej wyspecjalizowanego programu? program? Oba programy mają różne cechy i różne możliwości.
Mówisz, że tak jakby posiadanie uniwersalnej do pewnego stopnia biblioteki interfejsu byłoby z pewnością dobrą rzeczą. Być może tak, ale w wielu przypadkach taka biblioteka byłaby duża i trudna do napisania i utrzymania, a może nawet powolna. A jeśli taka bestia w rzeczywistości nie istnieje, aby obsłużyć konkretny problem, to kim jesteś, aby nalegać, aby została stworzona, gdy podejście do generowania kodu może rozwiązać problem znacznie szybciej i łatwiej?
Myślę, że kilka rzeczy.
Generatory kodu przekształcają kod napisany w jednym języku na kod w innym, zwykle niższym poziomie. Pytasz zatem, dlaczego ludzie chcieliby pisać programy w wielu językach, a zwłaszcza dlaczego mogliby mieszać języki subiektywnie różnych poziomów.
Ale już tego dotknąłem. Wybiera się język dla konkretnego zadania, częściowo po to, by był on klarowny i wyrazisty dla tego zadania. Ponieważ mniejszy kod ma średnio mniej błędów i jest łatwiejszy w utrzymaniu, istnieje również tendencja do języków wyższego poziomu, przynajmniej w przypadku pracy na dużą skalę. Ale złożony program obejmuje wiele zadań i często niektóre z nich można skuteczniej rozwiązać w jednym języku, podczas gdy inne można skuteczniej lub bardziej zwięźle rozwiązać w innym języku. Użycie odpowiedniego narzędzia do pracy czasami oznacza zastosowanie generowania kodu.
źródło
Odpowiedź na pytanie w kontekście komentarza:
Kompilator nigdy nie będzie zoptymalizowany do twojego zadania. Powód tego jest prosty: jest zoptymalizowany do wykonywania wielu zadań. To narzędzie ogólnego zastosowania używane przez wiele osób do wielu różnych zadań. Kiedy już wiesz, jakie jest Twoje zadanie, możesz podejść do kodu w sposób specyficzny dla domeny, dokonując kompromisów, których kompilatory nie mogłyby.
Jako przykład pracowałem nad oprogramowaniem, w którym analityk może potrzebować napisać kod. Mogliby napisać swój algorytm w C ++ i dodać wszystkie sprawdzone granice i sztuczki zapamiętywania, od których zależą, ale to wymaga dużej wiedzy na temat wewnętrznego działania kodu. Wolą napisać coś prostego i pozwolić mi rzucić na to algorytm, aby wygenerować końcowy kod C ++. Następnie mogę wykonywać egzotyczne sztuczki, aby zmaksymalizować wydajność, na przykład analizę statyczną, której nigdy nie spodziewałbym się, że moi analitycy wytrzymają. Generowanie kodu pozwala im pisać w sposób specyficzny dla domeny, co pozwala im wydostać się z produktu łatwiej niż jakiekolwiek narzędzie ogólnego zastosowania.
Zrobiłem też dokładnie odwrotnie. Mam kolejną pracę, którą wykonałem, która miała mandat „bez generowania kodu”. Nadal chcieliśmy ułatwić życie osobom korzystającym z oprogramowania, dlatego wykorzystaliśmy ogromne ilości metaprogramowania szablonów, aby kompilator generował kod w locie. Tak więc potrzebowałem tylko języka C ++ ogólnego przeznaczenia, aby wykonać swoją pracę.
Jest jednak pewien haczyk. To było niezwykle trudne, aby zagwarantować, że błędy były czytelne. Jeśli kiedykolwiek używałeś wcześniej metaprogramowanego kodu szablonu, wiesz, że pojedynczy niewinny błąd może wygenerować błąd, który wymaga 100 wierszy niezrozumiałych nazw klas i argumentów szablonu, aby zrozumieć, co poszło nie tak. Ten efekt był tak wyraźny, że zalecanym procesem debugowania dla błędów składniowych było: „Przewiń dziennik błędów, aż zobaczysz błąd, w którym po raz pierwszy wystąpił błąd. Idź do tego wiersza i po prostu zezuj, aż zdasz sobie sprawę źle zrobiłem ”.
Gdybyśmy użyli generowania kodu, moglibyśmy mieć znacznie potężniejsze możliwości obsługi błędów, z błędami czytelnymi dla człowieka. C'est la vie.
źródło
Istnieje kilka różnych sposobów korzystania z generowania kodu. Można je podzielić na trzy główne grupy:
Sądzę, że mówisz o trzecim rodzaju generowanego kodu, ponieważ jest to najbardziej kontrowersyjna forma. W pierwszych dwóch formach wygenerowany kod jest etapem pośrednim, który jest bardzo czysto oddzielony od kodu źródłowego. Ale w trzeciej formie nie ma formalnej separacji między kodem źródłowym a wygenerowanym kodem, z tym wyjątkiem, że wygenerowany kod prawdopodobnie zawiera komentarz, który mówi „nie edytuj tego kodu”. To nadal stwarza ryzyko edytowania wygenerowanego kodu przez programistów, co byłoby naprawdę brzydkie. Z punktu widzenia kompilatora generowany kod to kod źródłowy.
Niemniej jednak takie formy generowanego kodu mogą być naprawdę przydatne w języku o typie statycznym. Na przykład przy integracji z jednostkami ORM bardzo przydatne jest stosowanie silnie typowanych opakowań dla tabel bazy danych. Pewnie, że mógłby obsłużyć integrację dynamicznie w czasie wykonywania, ale można stracić typu bezpieczeństwa i wsparcie narzędzi (zakończenie Code). Główną zaletą statycznego języka typów jest obsługa systemu typów w typie pisania, a nie tylko w czasie wykonywania. (I odwrotnie, ten typ generowania kodu nie jest bardzo rozpowszechniony w językach dynamicznie typowanych, ponieważ w takim języku nie zapewnia żadnych korzyści w porównaniu z konwersjami środowiska wykonawczego.)
Ponieważ bezpieczeństwo typu i uzupełnianie kodu są funkcjami, które chcesz w czasie kompilacji (i podczas pisania kodu w IDE), ale zwykłe funkcje są wykonywane tylko w czasie wykonywania.
Może być jednak środek: F # obsługuje koncepcję dostawców typów, która jest zasadniczo silnie typowanymi interfejsami generowanymi programowo w czasie kompilacji. Ta koncepcja prawdopodobnie zastąpiłaby wiele zastosowań generowania kodu i zapewniła czystsze rozdzielenie problemów.
źródło
Zestawy instrukcji procesora są zasadniczo niezbędne , ale języki programowania mogą być deklaratywne . Uruchomienie programu napisanego w języku deklaratywnym nieuchronnie wymaga pewnego rodzaju generowania kodu. Jak wspomniano w tej odpowiedzi i innych, głównym powodem generowania kodu źródłowego w języku czytelnym dla człowieka jest wykorzystanie wyrafinowanych optymalizacji przeprowadzanych przez kompilatory.
źródło
Źle to zrozumiałeś. Powinien przeczytać
Jeśli coś można wprowadzić do generatora interpretowalnych elementów , to chodzi tu o kod, a nie dane.
Jest to format źródłowy dla tego etapu kompilacji, a format ujścia jest nadal kodem.
źródło
gcc -fverbose-asm -O -S
nie jest kodem źródłowym (i to nie tylko lub głównie dane), nawet jeśli jest to jakaś forma tekstowa zawsze podawana do GNUas
i czasami czytana przez ludzi.