Testing term structure in has_type/2

In adding has_type/2 clauses to my code there is a need to also check the structure; currently using subsumes_term/2

e.g.

error:has_type(boolean_object,Object) :-
    must_be(ground,Object),
    (
        subsumes_term(object(boolean,_),Object)
    ;
        throw(error(type_error(boolean_object, Object), _))
    ),
    Object = object(_,Value),
    must_be(oneof([true,false]),Value).
:- begin_tests(boolean_tests).

test(101,[error(type_error(boolean_object,object(bool,true)),_)]) :-
    must_be(boolean_object,object(bool,true)).

test(102,[error(type_error(oneof([true,false]),1),_)]) :-
    must_be(boolean_object,object(boolean,1)).

test(103,[error(type_error(boolean_object,obj(boolean,true)),_)]) :-
    must_be(boolean_object,obj(boolean,true)).

:- end_tests(boolean_tests).

While I decided to use type_error from the list of ISO error types, I don’t know if that is the correct one; should a different one be used or a new error type created?

I think of this a structure error and not as type error, but is it common to think of the functor being invalid as a type error? If I think of object(boolean,true) instead like (object,boolean,true) as they did with Prolog of old, then it does make sense as a type error.

Any other useful feedback is welcome.


Related predicates of interest: Analysing and Constructing Terms

The distinction between a type error and a domain error can be applied to your example. This distinction is not always clear cut, however. Consider the following example (from the Logtalk type library object):

check(operator_priority, Term) :-
	(	var(Term) ->
		throw(instantiation_error)
	;	\+ integer(Term) ->
		throw(type_error(integer, Term))
	;	0 =< Term, Term =< 1200 ->
		true
	;	throw(domain_error(operator_priority, Term))
	).

In your case, you could check first for a compound term, throwing a type error if not the case, and then check for expected compound term structure, throwing a domain error if not correct.

1 Like

Interesting variation of looking at the term object(Type,Value) and obj(Type,Value) as being in the same domain and one being valid and the other invalid. Makes sense if I think of the them being used in a predicate that uses them, e.g. dict_add(Object,Dict0,Dict).

Thanks, will consider it; waiting to see other responses.

After proagating the change to more code came across the example where the term could have different arity.

So this code (simplified for inclusion here) is wrong

error:hast_type(location,Location) :-
    must_be(ground,Location),
    Location = location(Location_name),
    (
        subsumes_term(location(_),Location)
    ;
        throw(error(type_error(location, Location), _))
    ),
    is_of_type(oneof([a,b,c]),Location_name).
error:hast_type(location,Location) :-
    must_be(ground,Location),
    Location = location(c,N),
    (
        subsumes_term(location(c,_),Location)
    ;
        throw(error(type_error(location, Location), _))
    ),
    must_be(nonneg,N).

because if location is location(c,1) the first clause would see it as an error when in fact it should not cause an error.

This code works

error:hast_type(location,Location) :-
    Location = location(Location_name), !,
    must_be(ground,Location),
    is_of_type(oneof([a,b,c]),Location_name).
error:hast_type(location,Location) :-
    Location = location(c,N), !,
    must_be(ground,Location),
    must_be(nonneg,N).
error:hast_type(location,Location) :-   
    throw(error(domain_error(location, Location), _)).

This throws the error only when the other choices fail. Because this pattern fits better with domain_error than type_error used domain_error.

Notice that the change follows the pattern

Head :-
   Goal,
   !,
   Rest of clause.

as noted in “The Craft of Prolog” by Richard A. O`Keefe (WorldCat) is section 3.10 Pruning Alternative Solutions and was noted here.

Just for the record, error:has_type/2 should not throw exceptions. That is the task of the more general must_be/2 that uses has_type/2.

2 Likes