Failing a Generator on First Failure
There are occasions when working with some generative predicate where you don't know when you need to quit the generation until some condition fails. Trouble is, you'll fail with no chance of future success—but you're now lost in an infinite loop as the generator keeps on trying. In this post, we'll work through a blog pagination example and show how to use a dirty little trick with a cut.
So for our example, let's say you want to do an HTTP request of every page of some blog, but we don't know how many pages there will be until the server returns a 404. We also don't want to hard-code each possible URL; we want to use some code to generate URLs until we get that 404.
The Generator
Generating the URLs can be done with an adaptation of succ/2
to make a transitive version plus a little atom manipulation:
?-
Trouble is, that'll generate URLs forever. So when we go and make an HTTP request for a page that doesn't exist, we have to be careful to not backtrack to the generator and keep getting URLs to test that also don't exist. We want to kill this on the first 404.
The Exit Clause
The trick to exiting the generator is dual guards: in the success case we can allow backtracking, but in the failure case we cut all the choicepoints before failing, as shown in paginate/1
. We'll simulate the request and 404 with a random success/failure chance.
Note, without the first StatusCode == 200
guard, the success case would fail. Without the second StatusCode \= 200
guard, the ;
disjunction is the last choicepoint, so backtracking would resume from there, causing an immediate cut and fail after the first success.
Unlike a naive version, this one will fail at some point and exit as desired.
☞ We have the same key bindings as Prolog in the terminal, so you can keep hitting the
;
key to search for an additional solution.
?-
Which means we can also do this:
?-
Conclusion
This is a pattern that crops up now and then, so is worth remembering. If you'd like to get a little practice in, you could use this pattern to improve upon the countdown/1
predicate from Rock, Paper, Scissors, Prolog! by making it a countdown/2
predicate and moving the writing to the console side effects out of the counting.
Until next time, Happy Prologing!
Paul