scryer-prolog: Rebis: HTTP server no longer works

For example, with server.pl comprising:

:- use_module(library(http/http_server)).

run(Port) :-
        http_listen(Port, [get(/, request_response)]).

request_response(_, Response) :-
        http_status_code(Response, 200),
        http_body(Response, text("Hello!")).

and running:

?- run(6080).

and then accessing http://localhost:6080/, I get as output:

Listening at port 6080
2021-02-17 (22:44:40) get /
caught: error(existence_error(procedure,request_response/2),request_response/2)

This needs to be resolved maybe by taking the module into account. A meta_predicate declaration could maybe help, though it is unclear how exactly. This directive makes the compiler add a suitable module prefix to all predicate arguments that are declared as meta-goals.

@aarroyoc, I would greatly appreciate if you could take a look at this issue. Thank you a lot!

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 16 (12 by maintainers)

Most upvoted comments

I’ve used your ideas in #834 . Thank you so much!

It appears all that’s needed is:

:- meta_predicate http_listen(?, 2).

http_listen(Port, Module:Handlers0) :-
    maplist(module_qualification(Module), Handlers0, Handlers),
    http_listen_(Port, Handlers).

module_qualification(M, H0, H) :-
    H0 =.. [Method, Path, Goal],
    H =.. [Method, Path, M:Goal].

Due to the meta_predicate/1 declaration, we know that the Module: qualification will be present, the compiler will add it to the second argument.

The only problem with this is that, as you noted, the meta_predicate/1 declaration strictly speaking does not fit this situation. We are only using it to make the compiler add the module prefix, and at least we can use it as a workaround for now. There may be a more elegant solution to this that correctly models the situation. Expressing the situation more correctly could become more useful in the future when cross-referencers and other tools use the meta_predicate/1 declarations to really reason about the Prolog code, analysing which predicates are invoked, finding missing definitions etc.

With this change, it also works:

...
set_module(M, H0, H) :-
    functor(H0, A, N),
    (   A = (:), N = 2 ->
        H = H0
    ;   H0 =.. [Method, Path, Goal],
        H =.. [Method, Path, M:Goal]
    ).

:- meta_predicate http_listen(?, 2).
% Server initialization
http_listen(Port, Handlers0) :-
    maplist(set_module(user), Handlers0, Handlers), !,
    http_listen_(Port, Handlers).
http_listen(Port, Module:Handlers0) :-
    maplist(set_module(Module), Handlers0, Handlers),
    http_listen_(Port, Handlers).

http_listen_(Port, Handlers) :-
    must_be(integer, Port),
    must_be(list, Handlers),
    once(socket_server_open(Port, Socket)),
    format("Listening at port ~d\n", [Port]),
    accept_loop(Socket, Handlers).
...

But it doesn’t generalize meta_predicate/1.

I understand what @triska is proposing but now we have two problems: find a sensible generalization and implement it. TBH for most of the time, I thought that what meta_predicate/1 does was already done automatically (like in most languages with first-class functions) but I understand now why it is like this, the need to be able to differentiate goals from other terms (to which this transformation shouldn’t be done).

I agree that it may be generalizable enough to look for a new solution. To be more constructive, this is the first thing that pops into my mind, though I don’t like that it doesn’t resemble Prolog too much.

:- meta_predicate http_listen (?, [_(?, 2)]). This will look tell the compiler that the second argument is a list, which contains terms (any functor marked with _) and the second argument of each term is a goal.

Yes, completely correct.

I propose to use this opportunity to try to find a sensible and general approach to solve such issues instead of implementing an ad hoc change that is also not backwards compatible.

But it’s get(/, Goal) and get/2 isn’t a predicate. There is also the case of specifying a predicate from another module after importing it.

Yes, that works with and without the PR

Another way around is get(/, user:request_response) or get(/, server:request_response) if the module is server.

I’ll look into it but I’m not too sure how to approach this