3

This other question asks almost the same, but not quite. Rather, how do I demand that a goal succeeds deterministically (exactly once) and does not leave behind any choice points?

This is especially useful in the context of Prolog programs that are used as command-line tools: possibly read from standard input, take arguments, and write to standard output. In such a program, leaving a choice point after doing the work is invariably an error of the programmer.

SWI-Prolog offers deterministic/1, so one could write:

(   deterministic(true)
->  true
;   fail
)

Another, more portable way to achieve the same was suggested:

is_det(Goal, IsDet) :-
    setup_call_cleanup(true, Goal, Det=true),
    (   Det == true
    ->  IsDet = true
    ;   !,
        IsDet = false
    ).

However, it seems useful to throw an error when this happens, but I don't know what this error would be. I looked quite carefully through the ISO error terms and I could not find an error that would obviously describe this situation.

Is it indeed better to throw an error, or should I just fail? If throwing an error is to be preferred, what would that error be?

EDIT: I am not sure what to because especially when side effects are involved, like writing something to standard output, it feels very wrong to have the side effect happen and then fail. It is almost necessary to rather throw an exception. This makes it also possible to decide that the remaining choice point is harmless (if not desirable) and just catch the exception, then write to standard error or return a different exit code.

But I really have no idea what describes the exception properly, so I don't know what term to throw.

Community
  • 1
  • 1
  • 2
    "it feels very wrong to have the side effect happen and *then* fail": I would have upvoted this for this sentence alone already! – mat Feb 02 '16 at 08:41
  • 2
    @mat: that's what haskell teaches, schedule your side-effects. If you need side-effects, keep them separate from your reasoning. – Miloslav Raus Feb 02 '16 at 10:46
  • @MiloslavRaus It is very nice to keep side effects separate, but you have to commit to them eventually. It is not practical to delay a side effect like output forever. And choice points do have their way of sneaking into your code, especially when one uses DCGs. (There are many well-described techniques for getting rid of unwanted non-determinism, as long as you notice it.) –  Feb 02 '16 at 11:06
  • I deleted my answer, the only highlight left would be "The pred doesn't act deterministically. Either it's a bug, wrong documentation or expectations". I haven't heard of error for "left choice-point". Haven't heard of prolog where determinism annotations were checked (mostly, it will be just part of comments / documentation). SWI-Prolog has optional typing listed as a goal, but don't know if it will cover determinism (and checking this is more aking to contracts anyways). And, especially when using DCG's, you expect choice-points, and use once accordingly. – Miloslav Raus Feb 02 '16 at 11:33
  • Wrt side-effects: So, It's practical to delay them at least until you are sure there will be no "fail later". – Miloslav Raus Feb 02 '16 at 11:54
  • @MiloslavRaus Yep, I agree. Part of the issue here (as discussed in the question) is that failing is not really the correct thing to do if your program did everything it had to do "correctly" and committed to the side effect. "Dangling" choice points are a more subtle problem, and knowledge of what the program does can be enough for the developer to decide how to react to such choice points. –  Feb 02 '16 at 11:59
  • @MiloslavRaus DCGs don't have to be non-deterministic. There are ways to get rid of non-determinism in DCGs, and, curiously enough, DCGs get way more efficient (space efficient _and_ time efficient), _and_ easier to read and understand. Actually, so far, the majority of unexpected choice points I have gotten were from DCGs, and they always have been a result of non-optimal design. –  Feb 02 '16 at 12:18
  • @MiloslavRaus See [this answer](http://stackoverflow.com/a/25759039/1812457) for one example of a technique that avoids the creation of choice points in the context of DCGs and makes the code, in my opinion, easier to work with. –  Feb 02 '16 at 12:21
  • Well, asking at runtime "was there a choice-point" is not "knowledge of what program does". If you know enough to ask this question at that place, you could've just stuck once there ... Btw, i remembered CIAO prolog, which has support for types etc. But they view deterministic as: "All calls of the form X are deterministic, i.e., produce at most one solution, or do not terminate. In other words, if X succeeds, it can only succeed once. It can still leave choice points after its execution, but when backtracking into these, it can only fail or go into an infinite loop." – Miloslav Raus Feb 02 '16 at 12:21
  • @MiloslavRaus This is a very good point. The thing about choice points that you didn't realize were there is that you don't yet know what might happen if for some reason you backtrack into them! –  Feb 02 '16 at 12:22
  • @Boris and thanks for your DCG kick, i thought about it and made a mental note "If you are once/1-ing a DCG, you were lazy & you know it" ;-) – Miloslav Raus Feb 02 '16 at 12:42
  • 3
    You need a new error term, not sure how to call it. [Related: `call_semidet/1`](http://stackoverflow.com/a/12942551/772868). – false Feb 02 '16 at 12:45
  • @false **Yes** this is it exactly! (Plus, not absolutely convinced initially if an error should be thrown.) –  Feb 02 '16 at 12:48
  • @Boris: `call_semidet((true;false))` succeeds deterministically, while you would like something that produces an error in this case. Right? – false Feb 02 '16 at 12:54
  • @false Yes, since I have come to the conclusion that an error might be the more flexible and correct option. My use case seems to be: catching unexpected non-deterministic behaviour during development, as this might point to design flaws. But also, this might signal, for the caller of the Goal, that their arguments might have not been instantiated properly. It almost sounds like an `instantiation_error`, but I didn't think this explains the error properly. –  Feb 02 '16 at 13:06
  • @Boris Aha, the "during development" eluded me. I nearly asked (@false's link led to chain of links; that delayed me enough ;-) if you are going to leave it there as explicit contract or you wanna do it as part of your tests. Once seemed like "better" (cheaper) to me in cases "I'm sure I can't be sure" for "production use". – Miloslav Raus Feb 02 '16 at 13:19
  • 1
    @Miloslav Raus: It's nice to see that other languages now also advocate this separation. – mat Feb 02 '16 at 13:20
  • @Mat Necessity advocates it, some languages give tools to enforce it (Haskell through monads & type system / "type tagging", some languages have effect systems). Majority is side-effects-by-default and things start to go "hilarious" when you start using your code in more interesting contexts ;-) Don't side-effect inside [Scala's, ...] STM transactions, Eiffel's command-query separation is also not enforced ... – Miloslav Raus Feb 02 '16 at 13:47

1 Answers1

3

Check out call_semidet/1 as proposed by Ulrich Neumerkel on the SWI-Prolog mailing list:

call_semidet/1 - clean removal of choice-points

In it, he proposes:

call_semidet(Goal) :-
   (  call_nth(Goal, 2)
   -> throw(error(mode_error(semidet,Goal),_))
   ;  once(Goal)
   ).

This proposed mode_error is a very good start.

In spirit, it follows the other errors: In this case, mode semidet is expected, and this is reflected in the thrown error term.

mat
  • 40,498
  • 3
  • 51
  • 78
  • I don't want to edit your answer but maybe it is useful to also add the link to the [answer here on SO](http://stackoverflow.com/a/12942551/1812457) that shows the same solution. –  Feb 14 '16 at 17:59
  • And thank you for writing this up as an answer: I actually have missed the relevant part (which you have put in bold) from the rather large answer I linked, and was not aware of this email thread, either. The `call_nth/2` of course does not work for my case (side effects!) but this is beside the point. –  Feb 14 '16 at 21:15
  • 1
    The link to the SO answer is also very good. Behold though how cool the SWI mailing list was not so long ago! The signal to noise ratio was **way** higher in the years in which Ulrich and Richard still participated. – mat Feb 15 '16 at 01:05
  • 1
    Well, people with strong opinions plus this very regrettable move to googlegroups. It would be indeed great if there were a _technical_ solution to the second problem. As it stands, not having a gmail address makes you a second-class netizen. –  Feb 15 '16 at 08:41
  • A series of bad project management decisions has rendered SWI-Prolog mostly uninteresting to me, but I hope you have more luck than I if you raise these issues, and I will support them! – mat Feb 15 '16 at 08:45
  • I can only guess what you really have in mind, but as I see it, SWI-Prolog is the Prolog implementation that combines to _sufficient_ degree the following: it is open source (if not truly free anymore, but this is a political and ideological, not a technical issue, IMHO); it is well documented; it has a very complete standard library. One word that summarizes this is _pragmatic_. As anything else, this has both positive and negative consequences. –  Feb 15 '16 at 08:53
  • It is hosted on a non-free platform, posting to the mailing list requires a Google account, the website is very often down or unexpectedly routes to previous versions, the "tutorials" are outdated and do not link to the actual documentation (their authors do not listen to advise), the ISO standard is ignored, and when you report an issue you are often told that it's not important. This is what I mean. Pragmatic is not how I would describe that. Rather, it has alienated the most important long-time contributors, like Richard O'Keefe and Ulrich Neumerkel. – mat Feb 15 '16 at 09:13
  • My feeling about the tutorials you mention is that they don't do as much harm as you might think. The ISO standard is a major, recurring issue that reeks of politics (there, I said it). I share your other concerns. I guess two good things are: SWI's license is very permissive; SWI does not have a monopoly on the Prolog minds by any stretch of the imagination. –  Feb 15 '16 at 09:23
  • I hope you are right about the tutorials. Personally, I understand that people are no longer interested in contributing libraries if what they contribute is then overshadowed by people who have not been involved at all. Previously, there was a nice list of features prominently placed on the SWI homepage, which did great justice to all contributors by linking to their work. Nowadays, this list is gone and uninvolved others push themselves to the fore. For these reasons, I have zero inclination to contribute any libraries to SWI, and will instead increasingly move to other systems. – mat Feb 15 '16 at 09:29