Course:CPSC-312-2019-Prolog-Restaurant-Queries
Authors: Cindy Hsu (q3a1b), Theodorus Jossoey (v9y1b)
What is the problem?
So far, we have done a fair amount of list manipulation in Prolog, but it has mostly been on words, pairs, or numbers. We are curious as to whether or not
functional programing (and Prolog specifically), are well-suited to querying larger objects, that more closely resemble what one would find in an OOP program
or as would be stored in a relational database. We will be writing a program that offers a list of restaurants that match the search parameters. We will be
judging the readability/maintainability of the resulting logic program's code, and noting any constraints we find. From this, we can form an opinion as to whether
functional programming is still a viable choice for working with imported or large objects.
What is the something extra?
Instead of a mock database, we will use http/json
and http/http_client
libraries to make calls from Prolog to the Yelp API to retrieve restaurants.
By working with real restaurant data, our findings will have higher validity and generalizability to filtering on other types of objects. It also increases
complexity due to having to parse nested JSON fields, working with strings text, and generating valid HTTP requests based on user queries.
What did we learn from doing this?
1. Prolog has a lot of good client/server built-ins.
It was impressive how easily it was to make an HTTP request in Prolog. The http_client comes with powerful built-ins that allow for the connection to be
opened, read, and closed, without any handling of streams by the user. Query parameters could be inputted as key=value pairs and automatically
converted to URI format. There was no need for an application_json header with the `http/http_json` module imported. The datatype read could be
customized between atoms and strings. There was no custom code needed to get anything in the right format for the request. However, it is very
hard to trace through this code due to all of the automatic conversions and header setting happening in the background, and the fact that we can't add
breakpoints in an ide like we would to debug Java or C++.
2. Error handling can be (over)simplified.
Because 4xx and 5xx http responses were throwing nasty looking exceptions, we looked into how we could catch and print error messages.
The try-catch block is widely used in most programming languages, and we were pleasantly surprised that Prolog has a `catch/3` method
that allows you to specify a "goal" to execute, and a handler function to call if an exception is thrown. There is also a `print_messages/2`
built-in that we ended up using, because it converted the Prolog error type to a human-readable message.
Our only gripe with this is that the error contexts are not specific enough, and that the messages are sometimes misleading.
When one adds a bad input to the search parameters, Yelp servers respond with 400 Bad Request. The context only has that, and not
anything about why the request is bad, which makes it hard for the user to correct their request. Also, I believe anything thrown by a GET
request is of type existence_error, which leads to the print_message "url ... does not exist", preceding the status code. This message somewhat
implies that the error is a 404 Not Found, which could be misleading.
3. How to manipulate a 'json' object and convert it into a list.
The Yelp api return a json object, but we wanted to remove/flatten/rename certain structures for a better presentation of information. In our approach,
the list is first converted into a list of json object, then the list needs to be converted again in order to be used for further filtering, such as removing
unecessary information in the returned data, and flattening nested structures like 'location'. In this process we learn how to manipulate a json object
and learn more about dealing with lists. It is apparently quite simple to convert json object into a list, by simply putting in json(Obj) to extract the Obj
inside it. There are no specific function to convert a json object into a list directly.
Conclusion
Prolog has a surprising amount of support for I/O as a functional programming language, and we were able to find many of the
language features we have come to expect in our modern languages. We believe that one could build full HTTP client/servers quite easily
by importing the SWI modules, which handle a lot of stuff like conversion to json, or setting headers automatically/with predicates. Writing
to stdout was easy with `format/2`, which acted similar to printf in any other programming language. One extra detail we thought was nice
was that the program could also be compiled into an executable, allowing it to be accessed on different machines and with the source obfuscated.
However, deconstructing the JSON into basic terms so we could compare the data with our filters was quite messy. Things had to be
passed back and forth between different functions since the JSON was heavily nested, and the notion of json being a key=value list made
it hard to reason what was happening since the execution was all over the file, and there were no global variables to store values as we went.
Also, since there were no classes, to really understand the structure of the object and we had to refer to the Yelp documentation where
the properties on the restaurant object and their nesting levels were clear.
After we got the deconstruction to work, filtering was similar to our class assignments, and there were no problems with writing the logic
for that. To do the filtering, there are a lot of different ways, in this project we filter the result after converting it from json object into a list. There
are not much differences between different ways to filter it because of small amount of returned data. However, the run time will be minimized if
the filter is executed while converting json object instead of converting all of them first then do the filtering. But, since we limit the data into 20
restaurant that will be shown, the running time difference is insignificant. In conclusion, Prolog is fully capable of handling real scenarios
(like being a lightweight HTTP client), and has many of the expected features in modern languages, but it may not be the preferred choice
for such applications due to the complexity of parsing highly nested objects.