Do I have any hints? Oh dear.
The advice in this technical report is sound:
Coding Guidelines for Prolog
https://arxiv.org/abs/0911.2899
It goes over some style matters but also makes more practical recommendations about using the language for best results. I have followed those guidelines in the last few years, often before reading the book, and they have generally served me well.
My own, very personal advice is to avoid the dynamic database as if it was the plague. To clarify, the "dynamic database" refers to the use of assert/1, retract/1 and friends to add to, and remove clauses from the program database. Prolog programs are stored in a relational database and since we can open it up, write new program clauses to it, and compile it again, we call it the "dynamic database". Well, it may be dynamic, but it is also evil. Every time I use it, I end up hurting myself. So I've learned to hurt myself before I ever use it, just because that saves me most of the later pain.
In my current project I found myself going to great lengths to avoid using the dynamic database. For example, I have some code that auto-generates predicates to access the elements of a list that represents the state of the world as perceived by an autonomous agent guiding a robot. These "access predicates" look like this:
inspect(map_area,map_area(X_min,X_max,Y_min,Y_max),[map_area(X_min,X_max,Y_min,Y_max),_,_,_,_,_]).
inspect(scan_area,scan_area(X_min,X_max,Y_min,Y_max),[_,scan_area(X_min,X_max,Y_min,Y_max),_,_,_,_]).
inspect(position,position(X,Y,Theta),[_,_,position(X,Y,Theta),_,_,_]).
inspect(observations,observations(Contacts,Count),[_,_,_,_,observations(Contacts,Count),_]).
inspect(grouped_observations,grouped_observations(Groups,Count),[_,_,_,_,_,grouped_observations(Groups,Count)]).
% etc.
Here, the list that represents the state-vector is the last argument of inspect/3 and its terms can be inspected by passing an initialised state-vector list to that argument. For example:
?- initialised_state_vector(_Vs), inspect(position,P,_Vs).
P = position(x(0.0), y(0.0), theta(0.0)).
Modification operations work in a similar way:
modify(position,increment,x,[Map_area,Scan_area,position(X,Y,Theta),Scan_params,Observations,Grouped_observations],
[Map_area,Scan_area,position(X_,Y,Theta),Scan_params,Observations,Grouped_observations]) :-
increment_float(X,X_).
modify(position,decrement,x,[Map_area,Scan_area,position(X,Y,Theta),Scan_params,Observations,Grouped_observations],
[Map_area,Scan_area,position(X_,Y,Theta),Scan_params,Observations,Grouped_observations]) :-
decrement_float(X,X_).
% etc.
And then I can do:
?- initialised_state_vector(_Vs1), modify(position,increment,x,_Vs1,_Vs2), inspect(position,P,_Vs2).
P = position(x(0.1), y(0.0), theta(0.0)) ;
false.
All this is to avoid writing the terms in the state-vector (like position(X,Y,Theta)) to the dynamic database, then using asserts and retracts to modify them, which is the obvious thing to do. What I did above is also a more efficient way to access the elements of the state-vector list by unification, instead of having to scan the list to find a term (which can add up to a lot of scanning in the project I'm working on). Using asserts is also costly (but clauses can be accessed fast once asserted).
The hitch is that some of the terms in the state-vector above have arguments that are themselves lists and those lists need to be accessed to inspect or modify them. Which means scanning a list again. So I added clauses of inspect predicates that look like this:
inspect(group,GroupId,group(GroupId,Contacts,Count),[group(GroupId,Contacts,Count)|_]).
inspect(group,GroupId,group(GroupId,Contacts,Count),[_,group(GroupId,Contacts,Count)|_]).
inspect(group,GroupId,group(GroupId,Contacts,Count),[_,_,group(GroupId,Contacts,Count)|_]).
inspect(group,GroupId,group(GroupId,Contacts,Count),[_,_,_,group(GroupId,Contacts,Count)|_]).
inspect(group,GroupId,group(GroupId,Contacts,Count),[_,_,_,_,group(GroupId,Contacts,Count)|_]).
So that I can access the elements of a list-argument at constant time. So that's effectively compiling a Prolog list to act as an array, or a dictionary I guess. I don't know how big the "compiled" lists have to be, so I set a configurable "budget" before I generate the access predicates above and hope for the best. An alternative would be to simply add more inspect clauses for list-arguments as needed by using asserts but that's only going to happen over my cold, dead body.
All this could have been done much more simply if I used assert/retract, and also used Prolog lists as they're intended to be used, as flexible linear-time access structures. But if I did that, I'd end up in a world of pain later on. I know that from experience and I have the scars to show for it.
I suppose my approach could be seen as jumping through hoops to do something that is easy in other languages - just write some stuff to a database. But, I've worked with OOP languages on backends accessing relational databases all the time I was in the industry, and it's anything but easy. It's a constant source of pain instead: somehow every OOP textbook I've ever read forgets to tell you this, but OOP languages are simply not good at handling data. In Prolog at least my backend and my database are one and the same, my code _is_ my database, and I can has a clean and tidy representation where my entire program's state is in the source code (rather than spread out between the source and the database, and between the source and the runtime). So it takes a bit of work. And what doesn't?
My scheme above also serves to preserve the "logical purity" of Prolog, since accessing the state-vector is side effects-free, but that was really not my motivation. I don't care about purity. However, in Prolog, when you leave the declarative, logical, immutable paradigm behind and enter the world of destructive data structure updates, you enter a world of pain. The dynamic database is evil.
OK, so this got a bit too long, by far. You asked for a "hint" and I had to go and write a little white paper as usual. But I hope all that is useful to you, as a kind of case study.
Edit: I use SWI-Prolog. It's free and it's got the most libraries. Also a helpful community and a main developer who is clearly superhuman.