Metaprogramowanie
Metaprogramowanie – technika umożliwiająca programom tworzenie lub modyfikację kodu innych programów (lub ich samych). Program będący w stanie modyfikować lub generować kod innego programu nazywa się metaprogramem.
Wykorzystanie zasad metaprogramowania pozwala na przykład na dynamiczną modyfikację programu podczas jego kompilacji.
Metaprogramy tworzy się w metajęzykach. Jeśli język jest jednocześnie swoim metajęzykiem, taką cechę nazywamy refleksyjnością (ang. reflexivity).
Metaprogramowanie może polegać nie tylko na generowaniu kodu, ale również na modyfikacjach w czasie wykonania programu. Takie możliwości dają języki Javascript, C#, Lisp, Perl, PHP, Prolog, Python, Ruby, Groovy, Smalltalk, R oraz Tcl.
Przykłady
[edytuj | edytuj kod]Przykładem prostego metaprogramu jest ten skrypt w bashu:
#!/bin/bash # metaprogram echo '#!/bin/bash' >program for ((I=1; I<=992; I++)) do echo "echo $I" >>program done chmod +x program
Program ten generuje 993 linie, wypisujące liczby od 1 do 992. Nie jest to zbyt efektywny sposób na wypisanie liczb 1-992, ale ilustruje jak w kilka minut można stworzyć program o długości 1000 linii.
Dość analogiczny program w języku python wersji 3.0:
from os import system clay = open( "adam.py", "w" ) clay.write( "print( \"Madam, i'm Adam.\" )" ) clay.close() system("python3.0 adam.py")
Jeśli umieścimy powyższy kod w pliku i uruchomimy go to program wygeneruje plik/program adam.py i uruchomi go, a ten wypisze "Madam, i'm Adam.". Python posiada kilka funkcji ułatwiających metaprogramowanie np. exec()
czy execFile()
.
Poniżej analogiczny kod w scheme:
(define adam "adam.scm") (define clay (open-output-file adam)) (display "(display " clay) (write "Madam, i'm Adam." clay) (display ")" clay) (close-output-port clay) (load adam)
Makra lispowe
[edytuj | edytuj kod]Dialekty języka Lisp, takie jak Scheme, Clojure czy Common Lisp, jako języki symboliczne, obsługują tzw. makra składniowe. Kod źródłowy zorganizowany jest w nich w postaci tzw. S-wyrażeń i może być po wczytaniu do pamięci zmieniany tak samo, jak inne dane, z użyciem funkcji operujących na jednokierunkowych listach, którymi reprezentowane są złożone S-wyrażenia.
Przykład makra when w Scheme:
(define-macro (when cond . body) `(if ,cond (begin ,@body)))
Przykład makra when w Clojure:
(defmacro when [test & body] (list 'if test (cons 'do body)))
Powyższe makro działa podobnie do konstrukcji sterującej if, ale ma tylko jedną "odnogę" i można do niej wstawiać wiele wyrażeń. Zapisanie tego kodu w postaci funkcji wywołałoby zarówno warunek, jak i ciało.
Warto zauważyć, że lispowe makra są specyficznymi funkcjami, jednak różnią się od zwykłych funkcji następującymi cechami:
- Ich podprogramy uruchamiane są zanim dojdzie do uruchomienia programu, a po wczytaniu S-wyrażeń kodu źródłowego, w fazie zwanej makroekspansją.
- Ich argumenty nie są zachłannie wartościowane przed przekazaniem, lecz ich wartości zawierają podstawiony w miejscach ich przekazania kod źródłowy w postaci struktur danych reprezentujących S-wyrażenia.
- Zwracane przez nie wartości będą potraktowane jak kod źródłowy, który wstawiony zostanie w miejscach ich wywołań, a następnie poddany procesowi wartościowania wraz z całym programem.
Introspekcja i Refleksja
[edytuj | edytuj kod]Są to cechy meta programowania umożliwiające sprawdzanie jak wyglądają obiekty w pamięci np. sprawdzenie listy pól i metod w obiekcie czy pobranie i wywołanie metody, na podstawie wygenerowanego ciągu znaków.
Magiczne metody
[edytuj | edytuj kod]Jest to mechanizm występujący mi. w JavaScript, Python czy PHP umożliwiający zastąpienie wbudowanego mechanizmu własną implementacją np. w php są funkcje takie jak __call czy __get, które wywołają się, gdy próbujemy wywołać metodę lub pobrać właściwość, która nie istnieje. Podobny mechanizm występuje w języku Python. W JavaScript mechanizm ten zaimplementowany jest za pomocą zdefiniowanych symboli oraz obiektów Proxy, które weszły do języka wraz z wersją ES6.