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ładyEdytuj

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 lispoweEdytuj

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 RefleksjaEdytuj

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 metodyEdytuj

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.

Zobacz teżEdytuj