Dynamiczne modyfikowanie prologowej bazy wiedzy

Proces tworzenia prostych prologowych aplikacji przebiega zazwyczaj podobnie. Zaczynamy od stworzenia bazy wiedzy, definiujemy niezbędne predykaty (niekiedy również w tym samym pliku), a następnie odpalamy interpreter i pytamy o fakty oraz łączące je relacje. Czy oznacza to, że zdefiniowana na samym początku baza jest czymś niemodyfikowalnym z punktu widzenia programu? Czy możemy zmieniać fakty przy pomocy predykatów?

Predykaty dynamiczne

Okazuje się, że jak najbardziej jest to możliwe, choć jest to funkcjonalność, z której należy korzystać z dużą rozwagą. Zacznijmy od tego, że nie wszystkie predykaty mogą być dynamicznie modyfikowane. Aby było to wykonalne trzeba zastosować wobec nich dyrektywę dynamic/1. Spójrzmy na przykładową bazę wiedzy, zawierającą fakty dotyczące modelów samochodów:

Gdybyśmy po wczytaniu tak wyglądającego pliku chcieli zmodyfikować fakty car_model, niechybnie zostałby wyrzucony wyjątek.W GNU Prologu wyglądałby on tak:

Musimy zatem umieścić klauzulę dynamic/1 w którejś z linii poprzedzających definicję pierwszego predykatu car_model. Na przykład w taki sposób:

Oznacza to, że uczyniliśmy nasz predykat dynamicznym (domyślnie każdy zdefiniowany przez użytkownika predykat jest statyczny) i możemy przystąpić do dynamicznych operacji na nim.

Definicje

Predykaty, jakich użyjemy to:

  • asserta/1 – dodaje klauzulę (fakt lub regułę) do bazy, wstawiając ją na początku
  • assertz/1 – wstawia klauzulę na końcu bazy
  • retract/1 – usuwa z bazy zunifikowany term
  • retractall/1 – usuwa z bazy wszystkie fakty klauzule, których głowa unifikuje się z przekazanym argumentem

Oprócz tego użyjemy pomocniczo następujących predykatów:

  • listing/0 – wyświetla aktualny stan bazy (listuje wszystkie zdefiniowane predykaty)
  • tell/1 – otwiera plik, aby zapisywać w nim dane
  • told/0 – zamyka otwarty wcześniej plik

Przykłady

Powyżej opisanych predykatów możemy używać zarówno z poziomu interaktywnego interpretera, jak również w ciele tworzonych reguł. Możliwe będzie zatem stworzenie na przykład takich predykatów:

Ich wywołanie (np. add_car_model(renault, espace) albo remove_car_model(ford, fusion)) zmodyfikuje wczytaną bazę wiedzy, ale po zakończeniu sesji Prologa, zmiany nie będą nigdzie widoczne. Plik z naszymi faktami nie jest bowiem automatycznie synchronizowany z wczytaną do pamięci bazą.

Oznacza to, że chcąc zapisać zmodyfikowaną bazę do pliku (tego samego lub innego), musimy skorzystać z trzech dodatkowych predykatów, wywołanych dokładnie w tej kolejności, jak w poniższym fragmencie:

Kiedy używać

Dynamiczne modyfikowanie bazy wiedzy to z pewnością bardzo ciekawa funkcjonalność. Wynika ona z faktu, że Prolog jest językiem homoikonicznym, to znaczy takim, w którym „reprezentacja programu jest jednocześnie podstawową strukturą danych wykorzystywaną w języku”.

Na początku wpisu wspomniałem jednak o tym, że funkcjonalności tej należy używać z pewną ostrożnością. Zastanówmy się w jakich przypadkach jest to uzasadnione, a w jakich lepiej skorzystać z innych rozwiązań.

Przede wszystkim błędem jest posługiwanie się dynamicznym modyfikowaniem bazy wiedzy w celu symulowania zmiennych globalnych lub przechowywania stanu. Właściwe podejście do tych zagadnień wygląda w Prologu nieco inaczej i powinno być realizowane przy pomocy argumentów predykatów.

Jeśli zaś motywem skłaniającym nas do używania assertów i retractów jest optymalizacja wydajności, na przykład w postaci memoizacji (cache’owania obliczonych już wartości, aby nie liczyć ich później ponownie, lecz sięgać po nie do pamięci), to wtedy naruszenie prologowej deklaratywności wydaje się być uzasadnione.

 

Linki

.