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.
1 Answer 1
+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).
-
\$\begingroup\$ According to the SWI-Prolog manual
library(pio)
doesn't support pipes. Can the situation handled bywithFilesOrUserInput(StreamFunction, [])
still be covered byphrase_from_file/2
resp.phrase_from_stream/2
? \$\endgroup\$Christian Hujer– Christian Hujer2015年08月28日 12:13:56 +00:00Commented 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\$Christian Hujer– Christian Hujer2015年08月28日 12:17:35 +00:00Commented 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– mat2015年08月28日 18:51:46 +00:00Commented 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 getERROR: Unknown message: goal_failed(main([binaryFile]))
. \$\endgroup\$Christian Hujer– Christian Hujer2015年08月29日 19:58:29 +00:00Commented Aug 29, 2015 at 19:58 -
\$\begingroup\$ Also, what if the file in question is very huge, like 20 GB? \$\endgroup\$Christian Hujer– Christian Hujer2015年08月29日 20:12:08 +00:00Commented Aug 29, 2015 at 20:12