Can I make Prolog's "format/x" family behave leniently?

Coincidentally, I got annoyed at getting an exception from format/2 on incorrect “n placeholder”/“m arguments” matching if “n > m” and used this annoyance to look at what other systems are doing.

IMHO, Perl does it best. It warns if there is a problem (even if this problem is due to wrong argument type) - STDERR was invented for this - and continues. This absolutely makes sense in a dynamic language, where much more high-level stuff is going on and surprises during formatting output should probably rarely, if ever, lead to program halt.

#!/usr/bin/perl -w

printf "%d %s %s %s %s\n", "x", "a", "b", "c";
printf "Ok, Buddy!\n";
Argument "x" isn't numeric in printf at test.pl line 3.
Missing argument in printf at test.pl line 3.
0 a b c 
Ok, Buddy!

Just my 2 cents, as a decision on “erroring out in all cases” has been taken already.

Best regards,

– David

1 Like

While I have been following the thread at StackOverflow, perhaps you could add more details into your post for those here.

When I read the details on StackOverflow it appeared to me you were looking for a change to the code or pointing out a bug that could be fixed. When I read your post here I get an entirely different view; it looks like you are just adding your 2 cents, but I know you desire more. :smiley:

Also I was surprised to see you add it to this topic, IMHO it should be a separate topic, but time will tell.

EDIT 01.25.2020

For others, see related comment in the SWI-Prolog documentation for catch/3

Typically format/3 takes a format and a fixed list of arguments that should match by type and length, If you are not sure about the type there is always ~w and similar generic write predicates that will always do the job. That is already a lot more relaxed than e.g., C where a wrong argument type can easily cause SEGV.

Format/3 has two typical use scenarios, one to print real output to a real user. Bailing out if the format and arguments do not match sounds totally right to me, The other is to print debug messages, mostly though debug/3. Baling out may be a bit drastic in that case but I never experienced it as much of a problem. If you are interested in the debug message and there is an error in the format/argument combination you probably want to fix that ASAP and restart the debugging.

In the exceptional cases were you cannot predict exactly what will happen there is always catch/3.

Yup - almost every time where a format/{2,3} call was wrong, it manifested itself by there not being some information I wanted, so I needed to re-run anyway. And when format/{2,3} didn’t complain about extra args, I often thought “there should be more output … where is it? … oh, yeah, the args don’t match the format string.”
So, I heartily approve of format/{2,3} throwing an exception.

(Also: if a format-args mismatch merely outputs a warning, where should that warning go?
user_error? if so, it might not show in the same stream as where the original message is, so I’d be back to wondering about missing information.
current_output (or the 1st arg to format/3)? if so, it could mess up some other processing in a puzzling way.)

Very briefly:

First, my original post was only because of the inconsistency between the two symmetrical cases: a) too many arguments and b) not enough arguments. One was an error and the other one was swallowed silently.

You got now several suggestions as to how to make format/2,3 behave leniently. It seems to me that there are two easy things:

  1. Write the code exactly as you mean it;
  2. Make a mistake while writing down what you mean.

and there is one very difficult thing: write code that makes the computer do what you mean even though you didn’t quite write it down properly. If anyone knows how to solve this in the general case, I would really like to know.

It should be straight-forward to write code that handles output leniently on a case-by-case basis (not enough arguments, too many arguments, wrong type, and so on) but to me it sounds like you’d need some heuristic to guess what to output.

user_error correspond to STDERR, right?

Then definitely there. That’s why it exists.

Observe the perl program mentioned above:

What’s on STDERR:

$ perl -e 'printf "%d %s %s %s %s\n", "x", "a", "b", "c"; printf "Ok, Buddy!\n";' >/dev/null
Argument "x" isn't numeric in printf at test.pl line 3.
Missing argument in printf at test.pl line 3.

What’s on STDOUT:

perl -e 'printf "%d %s %s %s %s\n", "x", "a", "b", "c"; printf "Ok, Buddy!\n";' 2>/dev/null
0 a b c 
Ok, Buddy!

Being able to tell the output apart on the user-side is a presentation issue of the surrounding system, not an issue of the program generating the output. This can be done by coloring or styles or having different text areas for STDOUT and STDERR etc. etc.

I agree, I agree. But: it all depends on “how big is the mistake” and should it really block me or the CI pipeline etc. In effect, there should be a setting going from “picky/crash” to “whatever/I’ll print something”, to be set on the in-program context (maybe scoped to the procedure, or the module, or the clause) and the out-of-program context (debugging mode, manual testing, production mode aka. “Will your boss give you heat if your process crashes because there is a list that doesn’t contain “Hello, valuable customer” on the last position but is accidentally missing?” and more).

OTOH, what is “format” really?

Writing output like this is ultimately straight from C, a language without a proper map construction syntax. Hence position-based (think about that!) poor man’s templating. Suddenly data (possibly anytyped, or worse pointer-based) that you pass to a function is dependent on:

  1. its position in a list and
  2. the contents of a string with basically embedded type assertions and
  3. atomic values, as opposed to objects that know how to print themselves, to be printed by a swiss army knife function

This is 80s old school, low-level and exceedingly fragile. Which gives you discussions about linting the printf format string, like this:

No real conclusion, but I’m thinking:

  • For quick and dirty generation of output or low-level processing at the base of points 2 and 3: format (which may or may not catch fire if it doesn’t like what it gets, I prefer Perl’s way but that’s just me). Students can work without undue baggage, trial and error coding can be attempted etc. etc.
  • For proper output: Put “structured things” through a templating engine (something like StringTemplate)
  • For proper error reporting and logging: Put “structured things” through a logging framework. print_message/2 should be the right predicate for that. It ultimately calls format/2, so can it be made lenient? And that’s a must btw, because the nature of error reporting and logging is that you don’t know what you will be reporting or logging.

(SWI-)Prolog’s I/O indeed has some old flavor to it. It isn’t too bad though:

  • For quick and dirty output for debugging purposes, use debug/3. That is by default silent, so your application won’t crash. If you enable a message channel you probably want to see the output. Still, debug/3 ultimately uses print_message/2, which is lenient (see below). In general though, use almost invariably use ~p as this prints anything for debug purposes and thus only the number of arguments must match the format.

  • For printing messages to the user, use print_message/2. The print_message/2 predicate is lenient (in recent versions) in the sense that it it prints the exception that occurred without propagating the exception.

  • Proper printing of text is a bit rare these days, but format/3 can be used for that. This indeed suffers from positional arguments. We can use format_types/2 to realise a lint-like checker:

    ?- format_types('~w: ~d', Types).
    Types = [any, integer].
    
  • In most cases it is probably nicer to output HTML, for which we do have a quite clean infrastructure. There is even a quite reasonable html-to-text renderer that is used by help/1 and available in the lynx directory of the library.

  • If you want text templating, quasi quotations would be the way to go in SWI-Prolog. One day we should add a nice quasi quotation rule for plain text. As is, only a few lines of code should suffice to connect the HTML quasi quoter to the above lynx library and enjoy nice paragraphs, underline, bold, color, lists and simple tables being rendered on your console.

  • None of this provides good support for logging. The HTTP server provides logging infra structure. The idea is to use library(broadcast) which implements a publish/subscribe interface. Now the application broadcasts events as Prolog terms and a subscriber can send these events somewhere (write to a file, send over the network, add to a database, …).

4 Likes

None of this provides good support for logging.

See SWI-Prolog package: log4p

Note: Prolog packages may not have good documentation and or support, so caveat emptor

1 Like

See recent changes regarding format/2 format/3 in this post.

1 Like