However, when the same goals are called internally, those guarded lookup calls are redundant – they already happened, but now, they are executed again.
Short to creating two parallel api’s of guarded and unguarded calls – is there a better way to specify or detect that when an api is called “externally” it should be guarded and when called internally, guards can be skipped?
And just call goal_impl/2 internally. The cost of making that call from goal/2 is quite small.
You can possibly avoid the goal_impl/2 stuff by using one module for the external API and another module for the internal API, with something like :-use_module(internal, [goal_impl/2 as goal]).
Another possibility is to use wrap_predicate/4 to add the checks in some places. For an example of using “wraps”, see the implementation of the rdet pack.
Yes, that is one strategy that I am doing, and for lower level code where time is critical I was hoping to avoid the extra call.
I guess wrap predicate is what i am looking for – and the test is to somehow know if the call originated internally or externally.
Ideally, i was hoping that such a test could be achieved during compile time, and the effect achieved through some code generation capability.
The solution you suggest is static in nature and whether a call is external or internal could be simple to determine (a simple module or goal name lookup, perhaps).
Do not insert checks when you call some built-in or library that will do the check anyway. For example, if you pass an argument to the 2nd argument of arg/3, there is no point checking it is a compound. The arg/3 call will trap it soon after and the stack trace will show what is going on.
Only add additional checks when there is a fair chance that users will call the predicate with wrong arguments. Otherwise it mostly clobbers the code.
If you decide you need an additional check, use must_be/2 for checks that do not significantly affect performance and assertion/1 for more expensive checks so you can disable them using swipl -O.
If you really think type checks are needed on all APIs, use some declaration. There are several packages around, some based on using the PlDoc annotations, the restricted Ciao assertion language by @edison, the type checker by Tom Schrijvers, etc. Type checking based on declarations is easily only enabled during debugging and can also be used for static analysis. The wrap_predicate/4 API can be used to (de)activate checks at any time.
I normally only add minimal checks in after getting into a situation that proves to be hard to debug. I’m also a big user of trace/1 and trace/2 to search for failing predicates that are not supposed to fail.
Some generators are fast at checking the result, in which case, just gen(X) without the var(X) test suffices. (And if you’re doing a generate-and-test algorithm, it can often be sped up by creating delayed test goals first and doing the generator second.)