GRoutar
GRoutar

Reputation: 1425

Cut(!) vs return

I am developing a predicate in Prolog and it is possible for it to terminate before the end of it. For that reason I was looking for a command similar to return; (C++). I used a cut ! but I'm doubtful as to what it literally stands for and if it does exactly what return; does. Ex:

pred(X) :- 
           X = 1 -> do this, !; else do that,
           write('x =/= 1').

void pred(int X) {
       if (X = 1) {do this; return;} else do that;
       cout << "x =/= 1";
}

Are the functions above exactly the same?

Upvotes: 3

Views: 580

Answers (4)

repeat
repeat

Reputation: 18726

I would like to add that having solid coding guidelines can also help quite a bit to increase code readability and to avoid code brittleness.

Starting with @DanielLyons' code:

foo :-
  cond1 -> 
    handle_cond1
  ; (cond2 ->
       handle_cond2
     ; do_other_stuff 
     ).

In practice, cascades of multiple nested if-then-else constructs occur: "if-then-elseif-then-elseif-then-elseif-then-...-else".

To increase readability, the code layout can be brushed up and the level of indentation adjusted:

foo :-
   (  cond1 -> handle_cond1
   ;  cond2 -> handle_cond2
   ;           do_other_stuff 
   ).

Whenever code lines get too wide, a slightly less wide and more tall style may be preferable:

foo :-
   (  cond1
   -> handle_cond1
   ;  cond2 
   -> handle_cond2
   ;  do_other_stuff 
   ).

Upvotes: 2

Nicholas Carey
Nicholas Carey

Reputation: 74267

As was pointed out, Prolog doesn't map well to procedural thought.

I find the best way to think of a Prolog program and its "database" as a tree (forest?). The analogy is a bit rough since the graph contains cycles (recursion).

When you ask the prolog engine to determine the truth or falseness of a particular assertion (predicate), it commences to do a depth-first, left-to-right traversal of the tree using unification (pattern matching) to guide the traversal. When the traversal reaches a leaf node, the predicate is true. On backtracking, it... backtracks and continues the tree walk. When there are no more reachable leaf nodes, the predicate fails.

Prolog is a descriptive language: you describe the conditions for success in terms of predicate calculus. Then you simply let Prolog's inference engine find the applicable solutions. If you try to shoehorn procedural, imperative thought into the model, in addition to making things more difficult than they should otherwise be, in my experience, you're pretty much guaranteeing poor performance.

I found Leon Sterling and Eliot Shapiro's textbook, The Art of Prolog, to be invaluable and far more instructive and enlightening than Clocksin & Mellish's Programming in Prolog.

Art of Prolog Cover

Edited to note: Your sample predicate

pred(X) :- 
   X = 1 -> do this , ! ; else do that ,
   write('x =/= 1')
   .

has some problems.

  • First, just like C or C# or other procedural languages where the and and or operators have different precedences so an expression like if ( a && b || c && d ) ... probably doesn't bind the way you think it does, due to operator precedence, your example predicate is probably not doing what you think it's doing: as written, it binds as

    pred(X) :-
      X=1 ->
        ( do_this , ! )
      ;
        ( do_that , write( 'x =/= 1' ) )
      .
    

    When what you probably wanted was

    pred(X) :-
      ( X=1 ->
        ( do_this , ! )
      ;
        do_that ,
      ) ,
      write( 'x =/= 1' )
      .
    

    You need to use parentheses to omake your intended binding explicit:

    pred(X) :- 
       ( X=1 ->
           ( do_this , ! )
       ;
           do_that
       ),
       write('x =/= 1')
       .
    
  • Further, the cut (!) in your example is unnecessary, since the implication operator -> acts as if there were a cut involved. This:

    foo(X) :-
      truthy(X) ->
        writeln('truthy!')
      ;
        writeln('falsy')
      .
    

    is pretty much exactly the same thing as

    foo(X) :- truthy(X) , ! ,
              writeln( 'truthy' ) .
    foo(_) :- writeln( 'falsy'  ) .
    
  • Third, you should make use of unification and pattern matching in the head. Ignoring the write/1, your example might make better sense as

    pred(1) :- do_this , ! .
    pred(X) :- do_that . 
    

And in general, if you're learning prolog, I would say avoid the implication operator (and alternation (logical OR, ';'/2) in general. Prefer explicit predicates with multiple clauses to express choice. Instead of something like

foo(X) :- ( try_this(X) ; try_that(X) ; finally(X) ) .

prefer

foo(X) :- try_this(X) .
foo(X) :- try_that(X) .
foo(X) :- finally(X) .

And instead of implication:

foo(X) :- X=1 -> try_this(X) ; try_that(X) .

prefer something like this:

foo(1) :- ! , try_this(X) .
foo(X) :- try_that(X) .

I think it makes it easier to grok what's going on since it makes the choice points (and their elimination) explicit.

Upvotes: 3

Daniel Lyons
Daniel Lyons

Reputation: 22803

So you have some procedural code like this:

def foo():
  if cond1:
    handle_cond1()
    return

  if cond2:
    handle_cond2()
    return

  do_other_stuff()

You can transform this in the procedural domain to have no explicit returns, first doing this:

def foo():
  if cond1:
    handle_cond1()
    return

  if cond2:
    handle_cond2()
  else:
    do_other_stuff()

And then doing this:

def foo():
  if cond1:
    handle_cond1()
  else:
    if cond2:
      handle_cond2()
    else:
      do_other_stuff()

Once you have eliminated the return statements you can transform this into Prolog:

foo :-
  cond1 -> 
    handle_cond1
  ; (cond2 ->
       handle_cond2
     ; do_other_stuff 
     ).

There is no way to immediately succeed in Prolog. (You can immediately fail with fail). You will have to perform a transformation like this to achieve a similar flow. Best of all would be to follow @false's advice and learn Prolog on its own terms.

Upvotes: 3

false
false

Reputation: 10102

There is no direct correspondence of Prolog's execution mechanism and those of traditional imperative languages. So any analogy rather leads you on a dead path.

In your example, the cut has no effect whatsoever: The (->)/2 alone will already exclude the Else branch. In a sense it does a "tiny" cut on If and the alternative. Would there be another clause to pred/1, your cut would exclude that branch too.

Prolog's execution mechanism is much more complex. If you insist on an analogy in imperative languages, think of iterators. The cut causes all iterators in scope of the cut to produce a done on the next next. So it is a bit like a break. Somewhat. But only in a language that supports iterators in the first place.

If you want to learn Prolog, don't try to develop your notions out of these (half) broken analogies.

Better start by imagining what relations a Prolog predicate describes and approximate from that the meaning of a predicate. Procedural notions will fit in, one by one.

Upvotes: 7

Related Questions