Skip to content

Strange Prolog Coder Hacks

Douglas R. Miles edited this page Aug 17, 2024 · 9 revisions

Prolog programmers often unconventional techniques to handle scenarios like undefined predicates, conditional execution, or prototyping code. This page documents some of these seemingly nonsensical things you'll see in the mettalog codebase.

The nop/1 Predicate

Example:

nop(_).

do_stuff:- 
  nop(step1),
  step2.

Explanation:

The nop/1 predicate always succeeds without doing anything, regardless of the argument passed to it. This trick is useful for temporarily disabling parts of code while maintaining readability and structure.

Why Use It?

  • Placeholders for Disabled or Future Code: Use nop/1 as a quick way to "comment out" code without removing it.
  • Debugging and Prototyping: Selectively deactivate steps in a process without breaking the flow of the program.
  • Readable and Modular Code: Even when a step is inactive, it remains visible in the code, preserving the overall structure.

Delayed Code Definition with call(call, ...)

Example:

do_something:- 
  call(call, some_pred(stuff)).

Explanation:

In SWI-Prolog, when you use call(some_pred(stuff)) and some_pred/1 is not yet defined, you’ll receive a warning like "Procedure some_pred/1 does not exist." However, by wrapping the call in an extra call, like call(call, some_pred(stuff)), you can reference a predicate that hasn’t been defined yet without triggering this warning.

Why Use It?

  • Deferring Definition: This trick is handy when you want to reference a predicate that you’ll define later. It allows you to build the structure of your code without needing every part implemented upfront.
  • Modular Development: You can design complex systems where certain predicates are loaded or defined dynamically, while still referencing them early in the code.
  • Avoiding Compilation Warnings: SWI-Prolog is smart enough to issue warnings about undefined predicates when you use call(some_pred(stuff)). Using call(call, some_pred(stuff)) suppresses these warnings until the predicate is defined.

How It Works:

  • The first call effectively delays the evaluation of the inner call(some_pred(stuff)), allowing Prolog to treat it as a more dynamic or indirect reference.
  • Once some_pred/1 is eventually defined, the code will run as expected.

When to Use It:

  • For scenarios where predicates are loaded dynamically, such as in modular or plugin-based systems.
  • When you’re building code that evolves over time, and you want to define certain predicates later, use nop/1 instead.

Here’s an expanded section for the Strange_Prolog_Coder_Hacks wiki page that includes using fail/0 to effectively comment out an entire clause:


Using fail/0 to Comment Out an Entire Clause

Example:

do_something :-
    step1,
    step2,
    step3.

do_something_else :-
    fail,  % This clause will always fail, effectively commenting it out
    stepA,
    stepB,
    stepC.

Explanation:

The fail/0 predicate is built into Prolog and, as the name suggests, it always fails. By placing fail/0 as the first goal in a clause, the entire clause will immediately fail when executed, preventing any further goals in that clause from being evaluated.

Why Use It?

  1. Temporarily Disable a Clause: During development, you might want to disable a specific clause without removing it from the code. Using fail/0 is a quick and effective way to "comment out" the entire clause while keeping it in place.

  2. Preserve the Structure for Future Use: Unlike traditional commenting (using % for line comments), using fail/0 allows you to keep the clause intact and visible within your code. This is useful when you plan to re-enable or modify the clause later.

  3. Maintain Control Over Clauses Without Deleting Them: By using fail/0, you can easily revert back to the original functionality by simply removing or commenting out the fail/0 line.

How It Works:

When Prolog encounters fail/0 in a clause, it stops evaluating the remaining goals in that clause and immediately considers the clause as having failed. If there are other clauses for the same predicate, Prolog will attempt to match those instead. If there are no other clauses, Prolog will return false for that predicate.

Example Use Cases:

  • Debugging and Testing: If you’re testing a different implementation or alternative logic, you can temporarily disable the original clause using fail/0.
  • Prototyping: When you’re unsure about the behavior of a specific clause or want to delay its execution until later, using fail/0 allows you to keep it in the codebase without it being active.
  • Conditional Logic: In some cases, you may want to programmatically control whether a clause should be active or not. By conditionally including or excluding fail/0, you can switch between enabling and disabling a clause.

Using a Cut (!) to Temporarily Disable Clauses Below It

Example:

do_something :-
    !,  % Cut prevents any further clauses from being considered
    step1,
    step2.

do_something :-
    stepA,
    stepB.

do_something :-
    stepX,
    stepY.

Explanation:

In Prolog, the cut (!) is used to commit to a specific choice, preventing Prolog from backtracking and considering alternative clauses. By placing a cut at the beginning of the first clause, you effectively disable all the clauses below it, making Prolog commit to the first clause regardless of whether it succeeds or fails.

Why Use It?

  1. Temporarily Disable Lower Clauses: If you’re still developing or refining the logic in the clauses below, you can use this technique to prevent Prolog from even considering them until they’re ready.

  2. Controlled Testing of a Single Clause: During development, you may want to test just one specific implementation of a predicate. By placing a cut in the first clause, you ensure that only this clause is executed, while the others are temporarily ignored.

  3. Postpone Decisions on Deleting Clauses: If you’re unsure whether you want to delete certain clauses, using a cut allows you to effectively "comment them out" without losing the code. You can later decide whether to keep or remove them based on further testing.

How It Works:

  • The cut (!) in Prolog tells the interpreter, "Commit to this choice and don’t backtrack." When placed at the start of a clause, it guarantees that if this clause is reached, no further clauses will be evaluated, even if this clause fails.
  • As a result, all clauses below the one with the cut are effectively ignored.

Example Use Cases:

  • Incremental Development: If you’re working on new logic and want to focus on testing one particular approach, you can temporarily disable the other clauses using a cut.
  • Selective Clause Activation: When you have multiple versions of a predicate, you can use a cut to activate only one version while leaving the others in place for future comparison.
  • Deferred Cleanup: Before permanently deleting old or deprecated clauses, you can disable them with a cut while ensuring the code remains functional.