I'm not understanding why underscore gets grounded in meta-predicates

I'm using SWI-PROLOG.

Given a list where almost all elements are compound terms like:

 ?- MyList = [json([a=1, b=2]), json([a=4, b=1]), 999, json([a=7, b=2])]. 

I want to filter only the compound term elements, so i tried the following:

?- MyList= ..., include(=(json(_)), MyList, FilteredElems). 
FilteredElems = [json([a=1, b=2])]. 

i was hoping to get the following result FilteredElems= [json([a=1, b=2]), json([a=4, b=1]), json([a=7, b=2])] but only the first one is included.

I discovered that after the first call of the predicate, the underscore gets grounded to: [a=1, b=2] value, so the next attempts of succeding the unification with json(_) fail.

Does exists a clean way to solve this? Am i missing something?

To test the grounding of the underscore i used my modified include version:

inc(Goal, List, Included) :-
    inc_(List, Goal, Included).

inc_([], _, []).
inc_([X1|Xs1], P, Included) :-
          writef("Calling call(%t, %t)\n", [P, X1]),
          call(P, X1)
      ->  Included = [X1|Included1], 
      ;   Included = Included1,
      inc_(Xs1, P, Included1).


I tried with predicate functor/3 and lambda (library yall); in this case the underscore get bounded to different values (in the below examples becomes 1; 1; _ ; 1; 2)

?-  MyList = [json(a), json(b), 999, json(c), json(d, 123)], include([E]>>(functor(E,json, A), writef("Arity: %t \n",[A])), MyList, FilteredElems).
Arity: 1
Arity: 1
Arity: 1
Arity: 2
FilteredElems = [json(a), json(b), json(c), json(d, 123)]. % As wanted

All is working as I expect in this case.

The same "problem" still happens when not using lambda; the underscore in this case get bounded after the first call.

test_functor_(FunctorName, Arity, Term):-
      writef("Term: %t, Arity: %t\n", [Term, Arity]),
      functor(Term, FunctorName, Arity).

?- MyList = [json(a), json(b), 999, json(c), json(d, 123)], include(test_functor_(json, _), MyList, FilteredElems).
Term: json(a), Arity: _8794
Term: json(b), Arity: 1
Term: 999, Arity: 1
Term: json(c), Arity: 1
Term: json(d,123), Arity: 1
MyList = [json(a), json(b), 999, json(c), json(d, 123)],
FilteredElems = [json(a), json(b), json(c)]. % Missing the last element

Can someone explain?


  • I think you are on the right track!

    However, you were not asking one question, but three: (1) Does there exist a clean way to solve this? (2) Am I missing something? (3) Can someone explain?

    Let me try to answer them separately...

    Regarding (2) and (3) let me point out that you are observing the same effect with

    ?- ..., include(=(json(_)), MyList, FilteredElems). 


    ?- ..., include(test_functor_(json,_), MyList, FilteredElems).

    In both cases, the anonymous variable stands for one (and only one) value.

    For the first query it is [a=1, b=2], for the second one 1.

    Regarding (1): Yes, lambdas can help by providing "internal" variables, just as you observed.

    Alternatively, you can use an auxiliary predicate like so:

    is_json(X) :-
       functor(X,json,_).   % "_" will be a fresh variable ...
                            % ... everytime is_json is called!

    Sample query:

    ?- include(is_json,[json(a),json(X),123,foo,foo(2),json(a,b),bar(f),baz(z)],Xs).
    Xs = [json(a),json(X),json(a,b)].