Use of setof/3 with recursive call in GOAL gets wrong, why?

219 Views Asked by At

I have problems using setof/3, some results are missing.

The context:

I load a xml-file using SWI-Prolog load_xml() to get a recursive list element (see testelement in the example). Then I want to look up specific elements in this list (in the xml tree).
Using findall/3 combined with sort/2, it works fine. But if I use setof/3, I miss one result. I suppose that setof/3 has problems due to the recursive call in askElement/3 to get/keep the elements? Knows anyone another solution to get the elements out of the recursive list?

My test code:

testElement([element('recipeml',[version=0.5], 
    [element('recipe',[],
        [element('head',[],
            [element('title',[],['Spaghetti Bolognese']
            )]
        ),
        element('ing-div',[type=titled], 
            [element('title',[],['sauce']),
             element('ingredients',[],
                [element('ing',[],
                    [element('item',[],['hackfleisch']),
                     element('item',[],['fleischtomaten']),
                     element('item',[],['zwiebeln']),
                     element('item',[],['sellerie']
                    )]
                )]
            )]
        )]
    ),
    element('recipe',[],
        [element('head',[],
            [element('title',[],['Erbsensuppe']
            )]
        ),
        element('ing-div',[type=titled], 
            [element('title',[],['elementar']),
             element('ingredients',[],
                [element('ing',[],
                    [element('item',[],['sahne']),
                     element('item',[],['erbsen']),
                     element('item',[],['gemüsebrühe']
                    )]
                )]
            )]
        )]
    )] 
)]).

askElement(Name, Child, Parent) :-
    (
        member( element(Name,_,Child),Parent)
    ;
        member( element(_,_,NewParent),Parent),
        [_|_] = NewParent,
        askElement(Name, Child, NewParent)
    ).

allRecipes_findall(RecipeName) :-
    testElement(Knot),
    findall(TmpR,(askElement('head',HKnot,Knot),askElement('title',TmpR,HKnot)),Bag),
    sort(Bag, RecipeName).

allRecipes_setof(RecipeName) :-
    testElement(Knot),
    setof(TmpR,(askElement('head',HKnot,Knot),askElement('title',TmpR,HKnot)),RecipeName).

My Output:

3 ?- allRecipes_findall(X).
X = [['Erbsensuppe'], ['Spaghetti Bolognese']].

4 ?- allRecipes_setof(X).
X = [['Erbsensuppe']] 

I expected that in both case I get

X = [['Erbsensuppe'], ['Spaghetti Bolognese']].

What's wrong?

Many thanks in advance!

PS: Every comment/review of my (first try of) Prolog code is very welcome :}

2

There are 2 best solutions below

5
Paulo Moura On BEST ANSWER

The standard setof/3 predicate gives you a solution per each different instantiation of the free variables in the goal. Using your code as-is gives:

?- allRecipes_findall(X).
X = [['Erbsensuppe'], ['Spaghetti Bolognese']].

?- allRecipes_setof(X).
X = [['Erbsensuppe']] ;
X = [['Spaghetti Bolognese']].

That's the expected result. You can, however, make setof/3 ignore the free variables by existentially quantifying them using the ^/2 operator:

allRecipes_setof(RecipeName) :-
    testElement(Knot),
    setof(TmpR,HKnot^(askElement('head',HKnot,Knot),askElement('title',TmpR,HKnot)),RecipeName).

With this change you'll get the same result as with the findall/3 predicate:

?- allRecipes_setof(X).
X = [['Erbsensuppe'], ['Spaghetti Bolognese']].

Regarding comments on your programming style, use underscores instead of CamelCase in atoms for code readability. E.g. ask_element instead of askElement. For variables, on the other hand, CamelCase is often used.

3
CapelliC On

Paulo already give plenty of advice about your current code. I'm here only to suggest to take advantage of library(xpath) when you need to handle XML. It does require a bit of exercise, but then you are rewarded with much functionality... for your example:

?- [library(xpath)].
true.

?- testElement(E), xpath(E, //head//title(text), T).
...
T = 'Spaghetti Bolognese' ;
...
T = 'Erbsensuppe' ;
false.