I am writing a http_server
application with http_session
s in Prolog. A request is handled with a dicontiguous
(multiple definitions) predicate app
, which must not fail (otherwise a 500 error appears to be given to the client). I need to early-quit the predicate somehow, so that the rest is not ran, but a custom message is shown and it ends without failure:
:- discontiguous app/1.
app(Request) :-
ensure_path(Request), !, % I want a `!` after I determined the path is valid, so no other `app` is ran
% grab session id as `Session`
http_session_id(Session),
% try to get session data, fail otherwise (I check something else in my code, but `http_session_data` should suffice for this example)
(http_session_data(my_data(Data), Session); show_error, ...), % some syntactic magic here...
% render something if session data has value
render_some_success_html(Data).
Here is a piece of pseudocode of a non-Prolog language (don't scold me for comparing languages of different paradigms) that I am trying to do:
function app(request) {
if (!ensure_path(request)) ...; // try other app(request)
let session = http_session_id();
let data = http_session_data('my_data', session);
if (!data) {
show_error();
return true; // this is important
}
render_some_success_html(data);
}
I tried to use a !, fail
in place of ...
, but that does a failure (swipl says goal unexpectedly failed
), and I need a true
instead.
EDIT: there may be multiple such early-returns in the predicate, each one having own variant of show_error
.
The point of the early-returns is to make the code not too nested, and to keep it top down, with the failure actions close to those checks, making it much more readable.
Thanks to @TA_intern 's suggestion I decided to use two variants of that predicate(Args)
on each failure point. The following code will use the !
variant, but the reader may try out the new "Picat" syntax as well.
So here is my solution:
Split the predicate into multiple ones upon such failure points.
Given a simple example of a chain of dependent calls, each giving some result for the next bit in the chain to consume:
predicate_with_failure_points(In1) :-
failure_point1(In1, Out1), % want to `show_error1` else on fail
failure_point2(Out1, Out2), % and `show_error2` here
success(Out2).
The split will look like:
predicate_with_failure_point1(In1) :-
failure_point1(In1, Out1), !, % `!` is not strictly necessary, but it might save a lot of headaches, might be necessary when dealing with side-effects
predicate_with_failure_point2(Out1).
predicate_with_failure_point2(In2) :-
failure_point2(In2, Out2), !,
success(Out2). % could split this bit here, especially if `success` is not a single line of code but quite large and would put `show_error2` too far down. let's assume this `success` is already a split off part.
Give each predicate an alternative variant as a handler for that failure point.
predicate_with_failure_point1(In1) :-
failure_point1(In1, Out1), !,
predicate_with_failure_point2(Out1).
predicate_with_failure_point1(In1) :- % will execute if `!` was not reached
show_error1(In1).
predicate_with_failure_point2(In2) :-
failure_point2(In2, Out2), !,
success(Out2).
predicate_with_failure_point2(In2) :-
show_error2(In2).
The resulting code:
show_error
right next to the failure point.The example given in the original question would then look like following:
:- discontiguous app/1.
app(Request) :-
ensure_path(Request), !,
http_session_id(Session),
check_session_data(Session).
check_session_data(Session) :-
http_session_data(my_data(Data), Session), !,
render_some_success_html(Data).
check_session_data(_Session) :-
show_error.