Search code examples
xmlprologprolog-setof

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


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 :}


Solution

  • 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.