Programowanie symboliczne

Programowanie symboliczne to paradygmat programowania, w którym program komputerowy może dokonywać zmian we własnych podstawowych elementach, np. wyrażeniach kodu źródłowego, w taki sam sposób, w jaki dokonuje się operacji na dowolnych danych[1]. Jest to sposób programowania, w którym rozwiązując problemy obliczeniowe, nie tylko tworzymy nowe programy, lecz również dostosowujemy język do efektywnego wyrażania rozwiązań tych problemów. Takie podejście leży u podstaw techniki zwanej metaprogramowaniem.

W językach programowania obsługujących paradygmat symboliczny, takich jak Lisp czy Prolog, można decydować, które fragmenty kodu źródłowego zostaną poddane uruchamianiu w procesie obliczania wartości wyrażeń, a które zostaną potraktowane jak dane. W rezultacie możliwe jest modyfikowanie oryginalnego kodu źródłowego programu (a dokładniej jego pamięciowej reprezentacji) z użyciem odpowiednich konstrukcji (w Lispie będą to makra i formy specjalne) zanim wyrażenia będą przeliczane. Pozwala to na budowanie złożonych systemów obliczeniowych na podstawie przejrzystych, wyspecjalizowanych części realizujących poszczególne zadania. Mogą to być niewielkie udogodnienia rozszerzające możliwości języka (np. nowe rodzaje pętli bądź innych konstrukcji sterujących), ale także zorientowane pod kątem wyrażania konkretnych rozwiązań problemów języki dziedzinowe.

Symboliczny paradygmat programowania zawdzięcza powstanie językowi Lisp, w którym wyrażenia kodu źródłowego mogą być interpretowane jako formy przeznaczone do przeliczenia w toku uruchamiania programu bądź jako formy zwykłe, czyli dane, na których program operuje. Programista jest w stanie wybierać, w jakich warunkach dany fragment kodu będzie potraktowany jak dane, a w jakich realizowany. Wynika to z operacji leżących u podstaw interpretacyjnych tego języka: eval (wartościowanie przekazanych danych, traktując je jak kod źródłowy) i quote (wyłączenie wartościowania fragmentu kodu źródłowego i potraktowanie go jak dane).

Przykładem elementów symbolicznych w innych językach programowania mogą być znane z języka C makra preprocesora lub konstrukcje typu eval w skryptowych językach powłokowych bądź nowoczesnych językach wieloparadygmatowych, takich jak Python czy Ruby.

Etymologia

edytuj

Słowo "symboliczne" pochodzi od nazwy głównej konstrukcji składniowej języka programowania Lisp, czyli wyrażenia symbolicznego (skr. S-wyrażenia, ang. symbolic expression, skr. S-expression). Jest ono rodzajem notacji, która umożliwia organizowanie kodu źródłowego z użyciem potencjalnie zagnieżdżonych list wyrażeń. W przypadku dialektów języka Lisp wyrażenia symboliczne umożliwiają zapis kodu źródłowego w formie, która może być przetwarzana nie tylko przez interpreter bądź kompilator języka, ale również przez programy napisane w tym języku (również zapisane jako S-wyrażenia).

Wyrażenie symboliczne to:

  • symbolicznie (z użyciem ograniczających nawiasów) wyrażona lista innych wyrażeń symbolicznych (oddzielonych znakami spacji) bądź
  • pojedynczy element niebędący symbolicznie wyrażoną listą.

Przykłady (komentarze ze znakami strzałek zawierają rezultaty obliczeń):

;; atomowe S-wyrażenie (symbol powiązany z wartością, np. 5)
a
; => 5

;; listowe S-wyrażenie (wywołanie funkcji +)
(+ 1 2)
; => 3

;; listowe S-wyrażenie (wywołanie funkcji + i funkcji -)
(+ 1 2 a (- 2 1))
; => 9

;; listowe S-wyrażenie jako dane (lista symboli i wartości)
'(+ 1 2 a (- 2 1))
; => (+ 1 2 a (- 2 1))

;; atomowe S-wyrażenie jako dane (symbol)
'a
; => a

Wspomnianym pojedynczym elementem może być np. literał wyrażający stałą wartość, ale również tzw. symbol, czyli czytelna etykieta służąca do identyfikowania umieszczonych w pamięci obiektów (np. wartości czy funkcji), z którymi została wcześniej skojarzona. Przykłady symboli to: +, -, abc, jakaś-nazwa itp.

Zazwyczaj symboliczne wyrażenia będą wartościowane, aż do uzyskania form zwykłych, których nie można już skracać, ponieważ są danymi (wartościami przeliczanymi do samych siebie). Między innymi każdy napotkany symbol będzie poddany procesowi rozpoznawania wartości, na którą wskazuje, aby uzyskać identyfikowany nim obiekt. Programista może jednak selektywnie tym procesem sterować i sprawiać, aby pewne wyrażenia nie były (natychmiastowo) przeliczane. Dotyczy to zarówno listowych S-wyrażeń, jak i pojedynczych S-wyrażeń (zwanych atomowymi). Na przykład w przypadku symbolu zamiast wskazywanej nim wartości zwrócony zostanie jego własny obiekt reprezentujący czytelną etykietę, a w przypadku symbolicznie zapisanej listy rezultatem nie będzie efekt wywołania podprogramu (np. funkcji), lecz obiekt jednokierunkowej listy elementów. Programem symbolicznym nazwiemy więc taki program, który operuje na wyrażeniach symbolicznych.

Przypisy

edytuj
  1. David S. Touretzky: COMMON LISP: A Gentle Introduction to Symbolic Computation. Redwood City, California: The Benjamin/Cummings Publishing Company. ISBN 0-8053-0492-4.