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\$
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\$
  • \$\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\$ – Christian Hujer Aug 28 '15 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\$ – Christian Hujer Aug 28 '15 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 '15 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\$ – Christian Hujer Aug 29 '15 at 19:58
  • \$\begingroup\$ Also, what if the file in question is very huge, like 20 GB? \$\endgroup\$ – Christian Hujer Aug 29 '15 at 20:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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