3:- module(with, [
    4              with/2,           % +Term, :Goal
    5              manage_context/3 % +Term, :Setup, :Cleanup
    6          ]).

Context managers for SWI Prolog

This module provides context management for various types of Prolog objects, such as IO streams, dynamic clauses, and settings. User defined contexts can be implemented using the multifile predicate manage_context/3. manage_context(Term, Setup, Cleanup) defines a setup/cleanup pair for a specific type of term. `with(Term, Goal)` calls Goal using setup_call_cleanup/3, with the corresponding setup and cleanup goals.

There is one difference between the semantics of setup_call_cleanup/3 and the corresponding goal using this library. Setup and Cleanup goals must succeed. If they fail, the error `error(mode_error(must_succeed, FailingGoal))` is thrown.

For example, the provided manage_context/3 clause for opening files could be defined:

with:manage_context(open(File, Mode, Stream),
                    open(File, Mode, Stream),
                    close(Stream)).

The result is that the following are equivalent:

?- setup_call_cleanup(open(File, read, Stream),
   is_stream(Stream),
   close(Stream)).

?- use_module(library(with)),
   with(open(File, read, Stream),
        is_stream(Stream)).

To show defined context managers, using `listing/1`. E.g, the context managers packaged with this module are:

?- use_module(library(with)), listing(with:manage_context/3).

manage_context(open(A, C, D),  (absolute_file_name(A, B), open(B, C, D)), close(D)).
manage_context(assertz(A), assertz(A, B), erase(B)).
manage_context(setting(A, B),  (setting(A, C), set_setting(A, B)), set_setting(A, C)).
author
- Eyal Dechter <eyaldechter@gmail.com>

*/

 with(+Term, :Goal) is det
Call Goal with the context manager associated with Term.
throws
- error(instantiation_error, _) If Term is a variable.
- error(mode_error(must_succeed, Goal)) If Goal is a setup or cleanup goal for context and Goal does not succeed.
   70:- meta_predicate with(:, 0).   71with(MTerm, Goal) :-
   72    strip_module(MTerm, M, Term),
   73    (term_has_context_manager(Term, M, Setup, Cleanup) ->
   74         setup_call_cleanup(ctx_must_succeed(M:Setup),
   75                            Goal,
   76                            ctx_must_succeed(M:Cleanup))
   77    ;
   78    existence_error(context_manager, MTerm)
   79    ).
   80
   81ctx_must_succeed(M:Goal) :-
   82    (M:Goal -> true
   83    ;
   84    throw(error(mode_error(must_succeed, M:Goal), _))
   85    ).
 term_has_context_manager(+Term, +Module, -Setup:callable, -Cleanup:cleanup) is semidet
True if there is a unique context manager for Term.
   91term_has_context_manager(Term, M, M:Setup, M:Cleanup) :-
   92    must_be(nonvar, Term),
   93    manage_context(Term, Setup, Cleanup).
 manage_context(Term, :Setup, :Cleanup) is det
If true, goals Setup and Cleanup are called to manage context associated with Term. Use this multifile predicate to define context managers.
  105:- multifile manage_context/3.  106
  107
  108/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  109     IO Stream context managers
  110- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  111manage_context(open(File, Mode, Stream),
  112               (
  113                   absolute_file_name(File, AbsFile), 
  114                   open(AbsFile, Mode, Stream)
  115               ),
  116               close(Stream)).
  117
  118/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  119     Dynamic DB context managers
  120- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  121manage_context(assertz(Clause), assertz(Clause, Ref), erase(Ref)).
  122manage_context(asserta(Clause), asserta(Clause, Ref), erase(Ref)).
  123
  124
  125
  126/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  127     library(settings) context managers
  128- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  129manage_context(setting(Setting, NewValue),
  130               (
  131                   setting(Setting, OldValue),
  132                   set_setting(Setting, NewValue)
  133               ),
  134               set_setting(Setting, OldValue)
  135              )