/** Safe type tests. "si" stands for "sufficiently instantiated". It can also be read as "safe inference", so possibly also other predicates are candidates for this library. A safe type test: - throws an *instantiation error* if the argument is not sufficiently instantiated to make a sound decision - *succeeds* if the argument is of the specified type - *fails* otherwise. For instance, `atom_si(A)` yields an *instantiation error* if `A` is a variable. This is logically sound, since in that case the argument is not sufficiently instantiated to make any decision. The definitions are taken from [Safer type tests in Prolog](https://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog). Examples: ``` ?- chars_si(Cs). error(instantiation_error,list_si/1). ?- chars_si([h|Cs]). error(instantiation_error,list_si/1). ?- chars_si("hello"). true. ?- chars_si(hello). false. ``` */ :- module(si, [atom_si/1, integer_si/1, atomic_si/1, list_si/1, character_si/1, term_si/1, chars_si/1, dif_si/2, when_si/2]). :- use_module(library(lists)). atom_si(A) :- functor(A, _, 0), % for the instantiation error atom(A). integer_si(I) :- functor(I, _, 0), integer(I). atomic_si(AC) :- functor(AC,_,0). % list_si(L) :- % \+ \+ length(L, _), % sort(L, _). list_si(L0) :- '$skip_max_list'(_,_, L0,L), ( nonvar(L) -> L = [] ; throw(error(instantiation_error, list_si/1)) ). character_si(Ch) :- functor(Ch,Ch,0), atom(Ch), atom_length(Ch,1). term_si(Term) :- ( ground(Term) -> acyclic_term(Term) ; throw(error(instantiation_error, term_si/1)) ). chars_si(Chs0) :- '$skip_max_list'(_,_, Chs0,Chs), ( nonvar(Chs) -> Chs == [] ; true ), % fails for infinite lists too failnochars(Chs0, Uninstantiated), ( nonvar(Uninstantiated) -> throw(error(instantiation_error, chars_si/1)) ; true ). failnochars(Chs0, U) :- ( var(Chs0) -> U = true ; Chs0 == [] -> true ; Chs0 = [Ch|Chs1], ( nonvar(Ch) -> atom(Ch), atom_length(Ch,1) ; U = true ), failnochars(Chs1, U) ). dif_si(X, Y) :- X \== Y, ( X \= Y -> true ; throw(error(instantiation_error,dif_si/2)) ). :- meta_predicate(when_si(+, 0)). %% when_si(Condition, Goal). % % Executes Goal when Condition becomes true. Throws an instantiation error if % it can't decide. when_si(Condition, Goal) :- % Taken from https://stackoverflow.com/a/40449516 ( when_condition_si(Condition) -> ( Condition -> Goal ; throw(error(instantiation_error,when_si/2)) ) ; throw(error(domain_error(when_condition_si, Condition),_)) ). when_condition_si(Cond) :- var(Cond), !, throw(error(instantiation_error,when_condition_si/2)). when_condition_si(ground(_)). when_condition_si(nonvar(_)). when_condition_si((A, B)) :- when_condition_si(A), when_condition_si(B). when_condition_si((A ; B)) :- when_condition_si(A), when_condition_si(B).