Skip to content

Conversation

@dougransom
Copy link

A few examples on using reif.


Review the summary at 25:45 [Power of Prolog meta_predicate declarations](https://www.youtube.com/watch?v=m3cbgebcKng). You will need some understanding of meta_predicate declarations.

Typically, reif predicates provided as arguments typically require the last additional argument will be a reified binary value.
Copy link
Contributor

@hurufu hurufu Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"typically" that is followed by another "typically" in the same sentence. Also it isn't "typically" but "always" :), but there was a proposal to extend this: #2722 (reply in thread)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed typically.


## Introduction and meta_predicate Review

Review the summary at 25:45 [Power of Prolog meta_predicate declarations](https://www.youtube.com/watch?v=m3cbgebcKng). You will need some understanding of meta_predicate declarations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think a beginner should really know about meta-predicates to use reif? What was your feeling? I thought that you should only know how partial goals work, like: call(length("123"), N) and that's all.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are correct.

Fortunatley, you don't need to understand that mechanism to use the reif module. Below there examples of how to use if_.


Also, don't be confused when something like this works:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall lamenting style might give wrong impression for a novice reader. IMHO, API reference should be very dry and to the point, but tutorials might have more inviting style.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the wording a bit, better?

Comment on lines 111 to 118
length(L1,2),length(L2,2),memberd_t(A,L1,X), memberd_t(A,L2,Y), dif(X,Y).
L1 = [A,_C], L2 = [_A,_B], X = true, Y = false, dif:dif(_A,A), dif:dif(_B,A)
; L1 = [_A,A], L2 = [_B,_C], X = true, Y = false, dif:dif(_A,A), dif:dif(_B,A), dif:dif(_C,A)
; L1 = [_A,_B], L2 = [A,_C], X = false, Y = true, dif:dif(_A,A), dif:dif(_B,A)
; L1 = [_A,_B], L2 = [_C,A], X = false, Y = true, dif:dif(_A,A), dif:dif(_B,A), dif:dif(_C,A)
; false.
?-
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have more examples like this in general, because it is difficult to realize versatility of even the simplest of predicates. #2971 (reply in thread)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you wan to add some, before or after this PR is merged?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that was a general comment.

Comment on lines 169 to 170
greater_than_two(Y,T) :- Y #> 2 ,T=true.
greater_than_two(Y,T) :- Y #< 2 ,T=false.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not greater_than_two(Y, true) :- Y #> 2.? Also seconds clause should use #=< relation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

tfilter(C_2, Es, Fs).
```

The meta_predicate directive is important. It specifies the first parameter to tfilter is a predicate needing two more arguments. The first required argument is the value to be compared from the list, and the second is the reified result of the comparison (true, or false).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meta_predicate is and isn't important at the same time. It is a hint to the Prolog system, but this code will work without meta-predicate declaration too (just not always as expected ;) )

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed discussion of meta predicates.

Comment on lines 139 to 143
## if_/3

if_1 calls predicate If_1, adding on the last argument the predicate expects, which is a boolean value, T.
if T then calls the predicate Then_0
otherwise, calls the predicate Else 0.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not an expert, but IMO the point of if_/3 is "indexing" it doesn't do any magic you can do everything it does while using dif/2, but it cleverly reduces amount of choice points – which is a real deal.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am less of an expert. I understand these predicates are also likely to be more monotonic or pure and therefore harder to write incorrect programs as well as some efficiency considerations. You want to add some language?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I don’t think “indexing” is explained adequately either.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal at this point is to provide enough examples that make it easier to get going using reif for the basics.

I might (or any other contributor might) add more examples with an explanation of benefits such as efficiency or monotonicity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal at this point is to provide enough examples that make it easier to get going using reif for the basics.

Totally. That's why I don't think it helps to explain in terms of jargon like "indexing". I think you're right that if_ should be understand in terms of what it gets YOU, the programmer, in terms of correctness.

@dougransom dougransom requested a review from hurufu September 26, 2025 14:23
Copy link

@UWN UWN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend to read the cited article first. Not conforming to the naming convention, starting with an incorrect example is not helpful for a tutorial. Instead the major focus should be on using the existing conditions.

```prolog

```
pound_gt(X,Y,true):- X #>Y,!.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-relational.

?- pound_gt(3,1,false).
   true.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

But (in my system at the time of writing) #>/2 exists, but there is no #>/3.
A workaround is to define a three-arity predicate to do the comparison (it could be #>/3, but I choose pound_gt):
```prolog

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is inappropriate, if it is reifying, then the last argument is either _t, or it is an operator.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. Is there a reference to that convention?

Copy link

@UWN UWN Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the link in library(reif)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attempted to update the names throughout to be consistent with the convention.


```prolog
```
?- memberd_t('c',"branch free",true).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No single quotes needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

?-
'''
```
What is happening is that reif has provided =/3, and that is what is being used in tfilter (not the usual =/2).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's (=)/2 and not =/2 which is just invalid syntax

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attempted to correct syntax for operators throughout the document, where they were not correctly surrounded by parenthesis.


```prolog
```
length(L1,2),length(L2,2),memberd_t(A,L1,X), memberd_t(A,L2,Y), dif(X,Y).
Copy link

@UWN UWN Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing ?-

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

corrected.

## (=)(A,B,T).
```

## (=)(A,B,T).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Syntactically incorrect.

## Introduction

Review the summary at 25:45 [Power of Prolog meta_predicate declarations](https://www.youtube.com/watch?v=m3cbgebcKng). You will need some understanding of meta_predicate declarations.
reif predicates will have as an argument a predicate requiring at least one additional argument. That predicate's last argument will be the reified value of the predicate.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what are reif predicates?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed that language.

@rotu
Copy link
Contributor

rotu commented Oct 1, 2025

You keep using the term "reify" in many different senses, which complicates understanding. Are we reifying a predicate? A goal? A condition? A truth value? A definition? A concept?

I would stick to only talking about "reified predicates" or predicates "reified by a variable".

In reif the further conventions hold (and I think this is what you mean by the phrase "reif predicates"):

  1. There are two truth values, corresponding to the atoms true and false. This is not trivial - the SICStus manual defines a reified constraint as having truth value 0 or 1.
  2. The predicates in reif are all reified to their last argument.

@dougransom
Copy link
Author

You keep using the term "reify" in many different senses, which complicates understanding. Are we reifying a predicate? A goal? A condition? A truth value? A definition? A concept?

I would stick to only talking about "reified predicates" or predicates "reified by a variable".

In reif the further conventions hold (and I think this is what you mean by the phrase "reif predicates"):

  1. There are two truth values, corresponding to the atoms true and false. This is not trivial - the SICStus manual defines a reified constraint as having truth value 0 or 1.
  2. The predicates in reif are all reified to their last argument.

I think I have made this more clear.

@dougransom
Copy link
Author

Ouptput from doclog, so you don't have to run it yourself:

reif_examples.html

@dougransom dougransom requested review from UWN and rotu October 6, 2025 00:14
@dougransom
Copy link
Author

Is this ok now for a first cut? I plan to add to it in another version one i understand how to use (,) and (;). I’d like to get this published first.

@triska
Copy link
Contributor

triska commented Oct 8, 2025

Is there any need to mention (->)/2 in this text, especially so close to the beginning? Or (is)/2, which is mentioned even earlier? These constructs are very hard to understand, and (->)/2 easily leads to elementary mistakes in programs.

@dougransom
Copy link
Author

Is there any need to mention (->)/2 in this text, especially so close to the beginning? Or (is)/2, which is mentioned even earlier? These constructs are very hard to understand, and (->)/2 easily leads to elementary mistakes in programs.

Probably not. Honestly i never tried (->) before writing this tutorial. I’ll address that. I may reintroduce-> and (is) in a later version to illustrate the problems with them.

Copy link

@UWN UWN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a good example of defining reifying membership: https://stackoverflow.com/q/79275439/772868

@rotu
Copy link
Contributor

rotu commented Oct 30, 2025

Would FizzBuzz be a good example of how to use reification meaningfully?

e.g.

?- use_module(library(reif)).
?- use_module(library(clpz)).
?- N=3,
(0 is N mod 3 -> Fizz = true; Fizz = false),
(0 is N mod 5 -> Buzz = true; Buzz = false),
( Fizz,Buzz -> Answer = "FizzBuzz"
; Fizz -> Answer = "Fizz"
; Buzz -> Answer = "Buzz"
; number_chars(N, Answer)).

Versus a version using reif, which has the advantage that the clause order doesn't matter and you can backtrack through different solutions of N with the semicolon key:

?- use_module(library(reif)).
?- use_module(library(clpz)).
?- (N=2;N=3;N=6;N=15),
#=(0, N mod 3, Fizz),
#=(0, N mod 5, Buzz),
(
 Fizz=true,Buzz=true,Answer="FizzBuzz"
;Fizz=true,Buzz=false,Answer="Fizz"
;Fizz=false,Buzz=true,Answer="Buzz"
;Fizz=false,Buzz=false,number_chars(N, Answer)
).

Can the above be improved maybe? I don't think that 4-way fork can be simplified with use of if_ but maybe I'm wrong.

@triska
Copy link
Contributor

triska commented Oct 30, 2025

Personally, I think a good program makes clear that it follows the specification. Therefore, if we have for example the following specification of FizzBuzz I found online:

Given an integer n, for every positive integer i <= n, the task is to print,

    "FizzBuzz" if i is divisible by 3 and 5,
    "Fizz" if i is divisible by 3,
    "Buzz" if i is divisible by 5
                   "i" as a string, if none of the conditions are true.

Then what about this program:

i_output(I, Os) :-
    if_((I mod 3 #= 0, I mod 5 #= 0),
        Os = "FizzBuzz",
        if_(I mod 3 #= 0,
            Os = "Fizz",
            if_(I mod 5 #= 0,
                Os = "Buzz",
                number_chars(I, Os)))).

Yielding for example:

?- N = 15, between(1, N, I), i_output(I, Os).
   N = 15, I = 1, Os = "1"
;  N = 15, I = 2, Os = "2"
;  N = 15, I = 3, Os = "Fizz"
;  N = 15, I = 4, Os = "4"
;  N = 15, I = 5, Os = "Buzz"
;  N = 15, I = 6, Os = "Fizz"
;  N = 15, I = 7, Os = "7"
;  N = 15, I = 8, Os = "8"
;  N = 15, I = 9, Os = "Fizz"
;  N = 15, I = 10, Os = "Buzz"
;  N = 15, I = 11, Os = "11"
;  N = 15, I = 12, Os = "Fizz"
;  N = 15, I = 13, Os = "13"
;  N = 15, I = 14, Os = "14"
;  N = 15, I = 15, Os = "FizzBuzz".

We can test it also in other ways, for instance: Which integers yield "Fizz"?

?- length(_, I), catch(i_output(I, "Fizz"), error(syntax_error(_),_), false).
   I = 3
;  I = 6
;  I = 9
;  I = 12
;  I = 18
;  I = 21
;  I = 24
;  I = 27
;  I = 33
;  I = 36
;  ... .

@rotu
Copy link
Contributor

rotu commented Oct 30, 2025

I think that's reasonable but I still don't love the if_s nested 3 deep. Is there a reasonable alternative?

The more legible but less logically pure alternative is the following, and I imagine there must be a better pure option:

i_output_bad(I, Os) :-
  (I mod 3 #= 0, I mod 5 #= 0) -> Os = "FizzBuzz"
; (I mod 3 #= 0) -> Os = "Fizz"
; (I mod 5 #= 0) -> Os = "Buzz"
; number_chars(N, Os).

@bakaq
Copy link
Contributor

bakaq commented Oct 30, 2025

match/2 from my reified pattern matching library Qupak helps flatten this thing while remaining pure. It was primarily made to do patterns, but you can abuse guards to do cond style things like this. This code is basically equivalent to the 3 deep if_/3 ladder that @triska showed earlier:

number_canonchars(N, Cs) :-
    number_chars(N, Cs),
    number_chars(N, Cs1),
    Cs1 = Cs.

fizzbuzz(N, Fizzbuzz) :-
    match(N, [
        ((*) | #N mod 3 #= 0, #N mod 5 #= 0) ~> Fizzbuzz = "FizzBuzz",
        ((*) | #N mod 3 #= 0) ~> Fizzbuzz = "Fizz",
        ((*) | #N mod 5 #= 0) ~> Fizzbuzz = "Buzz",
        (*) ~> number_canonchars(N, Fizzbuzz)
    ]).

?- length(_,N), fizzbuzz(N, Fb).
   N = 0, Fb = "FizzBuzz"
;  N = 1, Fb = "1"
;  N = 2, Fb = "2"
;  N = 3, Fb = "Fizz"
;  N = 4, Fb = "4"
;  N = 5, Fb = "Buzz"
;  N = 6, Fb = "Fizz"
;  N = 7, Fb = "7"
;  N = 8, Fb = "8"
;  N = 9, Fb = "Fizz"
;  N = 10, Fb = "Buzz"
;  N = 11, Fb = "11"
;  N = 12, Fb = "Fizz"
;  N = 13, Fb = "13"
;  N = 14, Fb = "14"
;  N = 15, Fb = "FizzBuzz"
;  N = 16, Fb = "16"
;  ... .
?- length(_, I), catch(fizzbuzz(I, "Fizz"), error(syntax_error(_),_), false).
   I = 3
;  I = 6
;  I = 9
;  I = 12
;  I = 18
;  I = 21
;  I = 24
;  ... .

This also made me realize a bug on my handling of modules, which was kinda expected given the cursed things I'm doing with them here.

@rotu
Copy link
Contributor

rotu commented Oct 30, 2025

That Qupak example is cool, but it's bringing in a lot!

I wonder if it makes sense to think of reif as something like a monad. Is this cursed?

:- meta_predicate(lift_t(0, ?)).
lift_t(G, true) :- G.

:- meta_predicate(eval_reif(1)).
eval_reif(G_1) :- call(G_1, true).

:- meta_predicate('->'(1, 0, ?)).
'->'(If_1, Then_0, T) :- cond_t(If_1, Then_0, T).

fizzbuzz(I, Os) :- eval_reif((
    (I mod 3 #= 0, I mod 5 #= 0) -> Os = "FizzBuzz"
  ; (I mod 3 #= 0) -> Os = "Fizz"
  ; (I mod 5 #= 0) -> Os = "Buzz"
  ; lift_t(number_chars(I,Os))
)).

% e.g.:
% N = 15, between(1, N, I), fizzbuzz(I, Os).
% length(_, I), catch(fizzbuzz(I, "Fizz"), error(syntax_error(_),_), false).

omit(["ops_and_meta_predicates.pl", "tabling"]).
learn_pages_source_folder("learn").
learn_pages_categories(["First steps", "Tutorials"]).
reif_learn_pages_categories( ["First steps", "Tutorials"]).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unnecessary (it doesn't do anything), if you want to use the Tutorials category you don't need to modify anything, just add the page in learn_pages/1 (as you also did).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants