10.1.2 State accumulation using engines
Applications that need to manage a state can do so by passing the state around in an additional argument, storing it in a global variable or update it in the dynamic database using assertz/1 and retract/1. Both using an additional argument and a global variable (see b_setval/2), make the state subject to backtracking. This may or may not be desirable. If having a state is that subject to backtracking is required, using an additional argument or backtrackable global variable is the right approach. Otherwise, non-backtrackable global variables (nb_setval/2) and dynamic database come into the picture, where global variables are always local to a thread and the dynamic database may or may not be shared between threads (see thread_local/1).
Engines bring an alternative that packages a state inside the engine where it is typically represented in a (threaded) Prolog variable. The state may be updated, while controlled backtracking to a previous state belongs to the possibilities. It can be accessed and updated by anyone with access to the engines' handle. Using an engine to represent state has the following advantages:
- The programming style needed inside the engine is much more `Prolog friendly', using engine_fetch/1 to read a request and engine_yield/1 to reply to it.
- The state is packaged and subject to (atom) garbage collection.
- The state may be accessed from multiple threads. Access to the state is synchronized without the need for explicit locks.
The example below implements a shared priority heap based on library
library(heaps)
. The predicate update_heap/1 shows the
typical update loop for maintaining state inside an engine: fetch a
command, update the state, yield with the reply and call the updater
recursively. The update step is guarded against failure. For robustness
one may also guard it against exceptions using catch/3.
Note that heap_get/2 passes the Priority and Key
it wishes to delete from the heap such that if the unification fails,
the heap remains unchanged.
The resulting heap is a global object with either a named or anonymous handle that evolves independently from the Prolog thread(s) that access it. If the heap is anonymous, it is subject to (atom) garbage collection.
:- use_module(library(heaps)). create_heap(E) :- empty_heap(H), engine_create(_, update_heap(H), E). update_heap(H) :- engine_fetch(Command), ( update_heap(Command, Reply, H, H1) -> true ; H1 = H, Reply = false ), engine_yield(Reply), update_heap(H1). update_heap(add(Priority, Key), true, H0, H) :- add_to_heap(H0, Priority, Key, H). update_heap(get(Priority, Key), Priority-Key, H0, H) :- get_from_heap(H0, Priority, Key, H). heap_add(Priority, Key, E) :- engine_post(E, add(Priority, Key), true). heap_get(Priority, Key, E) :- engine_post(E, get(Priority, Key), Priority-Key).