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 a 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.
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 for ever. So when we go and make a 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 little randomness.
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 the previous 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:
This is a pattern that crops up now and then, so is worth remembering. If you'd like to get a little practise in you could use this pattern to improve upon the
countdown/1 predicate in 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 Prologging!