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
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
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
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
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.