Detecting anonymous variable

Is there a way to check if a predicate argument has been explicitly bound to _ by the caller? The idea is to detect when my predicate gets called with (output) variable bound to _ and skip the expensive calculation altogether. It wouldn’t have to handle complex cases like later binding, e.g. foo(X, Y), Y = _, just foo(X, _).

Do you mean an uninstantiated variable in general or one that’s specifically “_” in a query.

For the former: var/1 or nonvar/1.

For the latter: read_term/2 has options such as singletons and variable_names and write_term/2 has options to allow displaying those variable names (see also numbervars and related predicates).

…read_term/2, yes, but not at use/call time (the compiler will have removed the names in any case).

The alternative is of course to have a few predicates for the various use cases. As the programmer writes _, the programmer definitely knows that he/she won’t use any term that got depsited into _. Might as well use another predicate.

var/1 won’t tell me if the variable is called _ in the calling predicate, so I have no way of differentiating if the result is going to be used or not. I’m thinking some kind of term expansion that attributes the variable before passing it, then check for that attribute in the predicate that wants to skip work might work? But as @dtonhofer pointed out, separate predicate might be cleaner solution after all :slight_smile:

I have found that there is Conditional compilation and program transformation which can certainly be used to transform a program at load time so that usage of _ gives rise to a specific predicate call … but I have no idea how to use that practically.

1 Like

Agreed. For me, I’d have two versions, that way the code is explicit at the cost of not much extra code. Plus, you don’t have to wonder what the other variable is actually for!

Close. Actually something rudimentary was implemented. Thought let us complete it. So, using the latest GIT you can do (for example):

user:goal_expansion(append(V, End, List), last(List, Last)) :-
    var_property(V, singleton(true)),
    is_list(End),
    End = [Last].

t(List, Last) :-
    append(_, [Last], List).

After loading you get

108 ?- listing(t).
t(List, Last) :-
    last(List, Last).

Ideally we would have properties that verify that some variable only appears in the context of the current goal being expanded and a property that checks that this is the last goal in which the variable appears. The first can be used for scope warnings, while the latter can be used to avoid computing results that are not used. Might have a look at that.

3 Likes