executing operation for each list element in swi-prolog and others

后端 未结 1 2221
谎友^
谎友^ 2021-02-19 11:33

How do I do an operation for each element of a list, in order?

Based on these two resources:

  1. http://www.swi-prolog.org/pldoc/doc/swi/library/lists.pl
1条回答
  •  长发绾君心
    2021-02-19 12:03

    Yes, but you have to worry about your predicate failing. If it can, the remaining elements in the list will not be processed because it produces a conjunction rather than a failure-driven loop.

    I would be more keen to use maplist/2 since I think it is more widely used than foreach/2 but I also hadn't seen this option before. :)

    Edit: Let's discuss what I mean about failure-driven loops.

    There are two primitive iteration methods in Prolog: recursion and failure-driven loops. Say I want to print out every item in a list. The recursive method is going to look like this:

    print_all([]).
    print_all([X|Rest]) :- write(X), nl, print_all(Rest).
    

    So given a list like [1,2,3], this is going to expand like so:

    print_all([1,2,3])
      write(1), nl, print_all([2,3])
        write(1), nl, write(2), nl, print_all([3])
          write(1), nl, write(2), nl, write(3), nl, print_all([])
            write(1), nl, write(2), nl, write(3), nl.
    

    This is how member/2 is usually implemented:

    member(X, [X|_]).
    member(X, [_|Xs]) :- member(X, Xs).
    

    So you can see the recursive method is pretty simple and general.

    Another simple but somewhat frowned-upon method is to simulate a failure to engage the backtracking mechanism. This is called a failure-driven loop and looks like this:

    print_all(List) :- member(X, List), write(X), nl, fail.
    print_all(_).
    

    When you run this version of print_all/1, what happens is a little more complex than simple expansion.

    print_all([1,2,3])
      member([1,2,3], 1)
        write(1), nl
          fail
      retry member([1,2,3], 2)
        write(2), nl
          fail
      retry member([1,2,3], 3)
        write(3), nl
          fail
    retry print_all(_)
      true
    

    Verbally, the fail forces Prolog to back up to the last choice point it made and try using the next solution. Well, write/1 and nl/0 don't produce choice points because they only have one solution, but member/2 does have multiple solutions—one for each item in the list. So Prolog takes each item out of the list and prints it. Finally when member/2 runs out of solutions, Prolog backs up to the previous choice point, which is the second body of the print_all/1 predicate, which always succeeds. So the output looks the same. I think people nowadays generally prefer not to use failure-driven loops, but I don't understand the arguments well enough to parrot them usefully.

    One thing that may help you to see what's going on is use the trace predicate and step through the expansion of both versions, and see if you can make sense of the differences. My notation above is completely made up for this answer and may not be as clear.

    Looking back over what I originally wrote and your actual question:

    • foreach is going to be deterministic
    • member will always iterate in order, because lists are defined in such a way that you must access each item in turn

    Moreover, these days at least on S.O. you will get a lot of people telling you to use maplist and its ilk, so it's probably not just going to work, but also a good idea.

    0 讨论(0)
提交回复
热议问题