The exercise "flatten a list" from the LPN course seems impossible to do with only course material

6.4 Practical Session

I’ve spent many hours trying to figure out how the authors wanted me to do this exercise using only the info given in the course and NOT using append. It seems impossible!

I’ve found a solution here lpnes/ch06/ps03.pl at ac14773dae3468ae1c35366aea614091a2d667b0 · mrkkrp/lpnes · GitHub, they use not + is_list predicates which are not covered in the course…

Even the library implementation of flatten/2 does not need append/3. It does use cuts and var/1.

I personally struggled with this particular book mightily and had to drop it :-/ maybe the “Now!” at the end of the title is a bit too demanding.

1 Like

Real programmers don’t use flatten/2.

Ending up needing flatten/2 often indicates, like append/3 for appending two lists, a bad design. (from the docs)

Though I find it harsh to put append/3 in the same drawer as flatten/2. So, to summarize:

  • if a program needs flatten/2, it’s broken
  • if a book teaches flatten/2, it’s misleading
  • if a teacher wants you to learn flatten/2, tell him or her to use “pyhton”

Sorry for sharing my bad mood, but some my students recently raised a complaint that a specific assignment could not be solved by chatGPT.

They do, when they need it. There is also append/2 which is similar to flatten/2.

This isn’t fair because for all we know OP is trying to teach themselves Prolog, which is difficult enough. This is an exercise. Exercises are meant for you to practice the activity, not to necessarily learn any truths.

I don’t think this is a good textbook but I also don’t know there are any good modern books out there.

Dear @Boris and @mgondan1, thank you for your response!

I believe the “flatten” function is a kind of practicing exercise which makes you think, but people will not use it in the production env. It is like sorting algorithms - easy to grasp and practice. In the https://wiki.haskell.org/index.php?title=99_questions/1_to_10 list, problem 7/99 is to write the flatten function and it seems like a common exercise for functional programming langs.

I like the LPN course because it has the right mix of theory + practice and the exercises mostly very good. If you can recommend any other learning source with a good collection of exercises, I be happy to check it out.

I at least have liked “The Art of Prolog”. You can download a PDF from the publisher for free. Go to this page: The Art of Prolog and look towards the bottom for the “Open Access” tab. The book itself is now 31 years old :sweat_smile:

On the other hand, if you are already enjoying “Learn Prolog Now!” that is fine. It maybe wasn’t the right thing for me at the time.

Yeah, sorry, I basically wanted to rant a bit. I hope you accept my apologies. If you think it’s not appropriate, I will remove the response. What I dislike, and I consider this the little grain of truth in my post: Textbooks/Teachers should carefully consider the examples they use. It’s often a mixture of technique and content, and even if the technique is in the focus, the content shapes the views of the learner. And in the case of an exercise around flatten/2, the content is misleading. But I was happy to read the deprecation note in the man page of swi-prolog.

“The Art of Prolog” is great indeed, and even more, “The craft of Prolog”. None of the two starts from scratch, though.

No offense taken, no worries. I would say that “The Art of Prolog” is the only book that starts from scratch. “The Craft of Prolog” is more of a collection of case studies and requires practical experience from the reader.

We tend to pay too much attention to how and what we teach. We also tend to actively hide from students our own past struggles with the matter. Education, technical education, and academic education are also all somehow different in their goals.

Well, how about this ?

my_flatten(L, Flat) :-
   flatten_(L, Flat, []).
flatten_([], R, R).
flatten_([H | T], L, R) :-
   flatten_(H, L, R1),
   flatten_(T, R1, R).
flatten_(X, [X | R], R).

I am sure that your first response will be: Well, that is not deterministic, only the first response is the flatten version, etc
But the book does not specify anything regarding determinism or the number of solution, they only say: if you give it [a,b,[c,d],[[1,2]],foo], you get [a,b,c,d,1,2,foo]:

?- my_flatten([a,b,[c,d],[[1,2]],foo], L).
L = [a, b, c, d, 1, 2, foo] % pending choice points

I think this is what the authors were after.

This is the funniest thing I have ever read…
flatten/2 is one of the most useful procedural predicates when doing constraints programming.
Did you know that until very recently, the flatten/2 predicate was defined in the clpfd library ?
For example, when you setup the constraints for the sudoku puzzle, you will use a list of list of clpfd variables, and then when you need to do the labeling, you need a flatten version of that list.

1 Like

The first argument (i.e. the nested list) is meant to be ground/1, so can use cuts to distinguish lists vs everything else, and to prevent unwanted choicepoints:

flatten_simple(L, F) :-
    flatten_simple_(L, [], F).

flatten_simple_([], L, F) :-
    !,
    L = F.
flatten_simple_([H|T], L, F) :-
    !,
    flatten_simple_(T, L, L0),
    % Put the Head in front of the Tail
    flatten_simple_(H, L0, F).
flatten_simple_(E, L, [E|L]).

Results;

?- flatten_simple([a,b,[c,d],[[1,2]],foo], F).
F = [a, b, c, d, 1, 2, foo].
?- flatten_simple([a,b,[[[[[[[c,d]]]]]]],[[1,2]],foo,[]], F).
F = [a, b, c, d, 1, 2, foo].

Very annoyingly, this flatten exercise comes much before cuts and negation in this textbook…

1 Like

Who wants to contact/email the authors, to hopefully rectify this for future:

As you see @brebs neither cuts nor negation are needed

That doesn’t work as intended:

?- my_flatten([a,b,[[[[[[[c,d]]]]]]],[[1,2]],foo,[]], F).
F = [a, b, c, d, 1, 2, foo] ;
F = [a, b, c, d, 1, 2, foo, []] ;
F = [a, b, c, d, 1, 2, foo, []] ;
F = [a, b, c, d, 1, 2, foo, [], []] ;
...

The only issue I see is that the book should have its chapter on !/0 moved to before the flatten/2 exercise. That’s all.

@kwon-young your code of my_flatten is almost exactly my code I tried first. However, then I got the output exactly as @brebs and decided that such code is just logically wrong

Well, you could say that the set of solutions is not “sound” because it contains false solutions but it is “complete” in the sense that it contains the correct solution.
You actually have two improvements you can do to this code to improve its soundness:

  • you can either exclude list. But for this, you need a negation operator. Since it is not covered in the book yet, I suppose this disqualifies this option
  • or you can specify everything that can be element of the list:
my_flatten(L, Flat) :-
   flatten_(L, Flat, []).
flatten_([], R, R).
flatten_([H | T], L, R) :-
   flatten_(H, L, R1),
   flatten_(T, R1, R).
flatten_(a, [a | R], R).
flatten_(b, [b | R], R).
flatten_(c, [c | R], R).
flatten_(d, [d | R], R).
flatten_(1, [1 | R], R).
flatten_(2, [2 | R], R).
flatten_(foo, [foo | R], R).

But then the solution is not “complete” anymore, although it forces you to think on what is allowed in a list.