10
\$\begingroup\$

In order to learn Prolog, I have implemented the cat command in Prolog. I want to know if the code is idiomatic and what could be improved.

File args.pl:

:- module(args, [withFilesOrUserInput/2]).

withFilesOrUserInput(StreamFunction, []) :-
    call(StreamFunction, user_input).

withFilesOrUserInput(StreamFunction, [Filename]) :-
    withFile(StreamFunction, Filename).

withFilesOrUserInput(StreamFunction, [Head|Tail]) :-
    withFile(StreamFunction, Head),
    withFilesOrUserInput(StreamFunction, Tail).

withFile(StreamFunction, Filename) :-
    open(Filename, read, StreamIn),
    call(StreamFunction, StreamIn),
    close(StreamIn).

File cat.pl:

:- use_module(args).

main(Argv) :-
    prompt(_, ''),
    withFilesOrUserInput(catStream, Argv).

catStream(Stream) :-
    copy_stream_data(Stream, user_output),
    flush_output(user_output).

Note: I'm using SWI-Prolog.

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

+1, good question.

The Prolog convention is to use underscores for readability. Why? because_it_is_easy_to_read_even_long_names_with_underscores, butItIsExtremelyHardToReadEvenShorterNamesWithMixedCaps!

A good naming convention is to use one noun per argument, declaratively describing what the argument stands for.

In SWI-Prolog, check out library(pio): The pure way to do what you want is to use a DCG to describe a list, and then to use phrase_from_file/2 to apply the DCG to a file.

The advantage is clear: You can then readily test your predicates on the toplevel alone, without even requiring a file, just by stating the input as a regular Prolog term.

This helps a lot also when writing test cases.

EDIT: I give you one example how DCGs can help you here. It is not yet completely pure, because this DCG contains output itself. However, it is much simpler than your code, and you can test this predicate without even needing a file, and then transparently apply the same code to a file too:

:- use_module(library(pio)).
:- set_prolog_flag(double_quotes, codes).

cat --> [].
cat --> [C], { put_char(C) }, cat.

This DCG describes a list of character codes, and outputs each code.

Sample use:

?- phrase(cat, "test").
test
true.

Now, with phrase_from_file/2 from Ulrich Neumerkel's library(pio), we can transparently apply the same DCG to a file too:

?- once(phrase_from_file(cat, 'cat.pl')).

With output:

:- use_module(library(pio)).
:- set_prolog_flag(double_quotes, codes).

cat --> [].
cat --> [C], { put_char(C) }, cat.

i.e., the same program as above, which I had saved it cat.pl.

I am using once/1 to commit to the first solution found (there is no further solution, but a choice-point left).

\$\endgroup\$
10
  • \$\begingroup\$ According to the SWI-Prolog manual library(pio) doesn't support pipes. Can the situation handled by withFilesOrUserInput(StreamFunction, []) still be covered by phrase_from_file/2 resp. phrase_from_stream/2? \$\endgroup\$ Aug 28, 2015 at 12:13
  • \$\begingroup\$ Also, I don't see how a DCG helps here. I'm not looking at what I read, I'm just forwarding it to stdout. \$\endgroup\$ Aug 28, 2015 at 12:17
  • \$\begingroup\$ I have included a DCG that I hope you find useful to express this more declaratively and compactly. \$\endgroup\$
    – mat
    Aug 28, 2015 at 18:51
  • \$\begingroup\$ Sorry, I'm not proficient enough in Prolog to figure out how this would help. I can get this working with files, but I cannot get this working with stdin. Also, when I use your solution and the file contains binary data, I get ERROR: Unknown message: goal_failed(main([binaryFile])). \$\endgroup\$ Aug 29, 2015 at 19:58
  • \$\begingroup\$ Also, what if the file in question is very huge, like 20 GB? \$\endgroup\$ Aug 29, 2015 at 20:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.