Skip to content

Conversation

@dougransom
Copy link

A few examples on using reif.

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.

@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).

@dougransom
Copy link
Author

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).

Is that what cond_t is for? if without the else?

@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.

removed the offending (->) and is.

@dougransom
Copy link
Author

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
; ... .

Added this example since it shows pretty well how to use if_.

@dougransom
Copy link
Author

dougransom commented Nov 15, 2025

Can it be merged now, at least as the first version? Who is making the decision about which PRs are merged? I presume @mthom?

Copy link
Contributor

@rotu rotu left a comment

Choose a reason for hiding this comment

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

Many typos. I'm sure some things can be better explained but I think this is a good start and definitely better than leaving reif undocumented!

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

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\_t):
Copy link
Contributor

Choose a reason for hiding this comment

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

pound_gt_t is just miserable. What's wrong with #>? It's already an operator as defined by clpz.

Otherwise, I'd use greater_t or gt_t.

Copy link
Author

Choose a reason for hiding this comment

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

Because (#>)/3 was not available on my system despite loading clpz and reif. That quite confused me and likely any other Prolog beginner.

I also think it is likely (#>)/3 will one day be available so I didn't want to define one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe even one day very soon... #3163

Copy link
Author

Choose a reason for hiding this comment

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

I didn't mean to cause any trouble :).

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.

8 participants