scryer-prolog icon indicating copy to clipboard operation
scryer-prolog copied to clipboard

Rational numbers canonical representation

Open bakaq opened this issue 1 year ago • 12 comments

Currently:

?- X is 1 rdiv 2.
   X = 1 rdiv 2.
?- X is 1 rdiv 2, X = 1 rdiv 2.
   % Ok, this implies that there is some internal state that differentiates the
   % composite term 1 rdiv 2 from the rational number 1 rdiv 2.
   false. 
?- X is 1 rdiv 2, Y = 1 rdiv 2, write_canonical(X-Y), nl.
   % However, write_canonical/1 doesn't represent this inner state at all! 
-(rdiv(1,2),rdiv(1,2))
   X = 1 rdiv 2, Y = 1 rdiv 2.

Expected behavior would be either of these:

  1. Have the composite term X rdiv Y actually be a rational number, assuming X and Y are integers and Y is positive (I like this one more).
  2. Have the canonical representation of a rational number represent this hidden state in some way.

Both of these imply having some way of constructing a rational number without (is)/2.

I stumbled on this trying to implement serialization of terms to JSON for the current efforts on embedding APIs like #2465. Probably related to #1983.

bakaq avatar Aug 10 '24 18:08 bakaq

SWI Prolog uses

$ swipl
?- X is 1 rdiv 2.
X = 1r2.
?- 

but @uwn objects to this notation.

infradig avatar Aug 11 '24 00:08 infradig

Yeah, I also think that rational numbers being simply a special composite term is a more friendly extension to the ISO Standard than inventing a new literal like 1r2 that complicates parsing and such.

bakaq avatar Aug 11 '24 00:08 bakaq

For what it's worth Trealla now deals with it at the top-level like:

$ tpl
?- X is 1 rdiv 2, Y = 1 rdiv 2, write_canonical(X-Y), nl.
-(1 rdiv 2,rdiv(1,2))
   X is 1 rdiv 2, Y = 1 rdiv 2.

infradig avatar Aug 11 '24 00:08 infradig

Well, this is also problematic, because the intent of write_canonical/1 (at least as I understand it) is to make a representation that can always be read back with the same meaning no matter the state of the system.

?- X is 1 rdiv 2, Y = 1 rdiv 2, write_canonical(X-Y), nl.
   % Ok, write_canonical/1 can differentiate between rationals
   % and composite terms.
-(1 rdiv 2,rdiv(1,2))
   X is 1 rdiv 2, Y = 1 rdiv 2.
?- Xcanon-_ = -(1 rdiv 2,rdiv(1,2)), X is 1 rdiv 2, Xcanon = X.
   % But the representation isn't faithful, in the sense that
   % it can't be read back with the same meaning.
   false.

In fact I would say that this is even worse, because now you have 1 rdiv 2 in the output of write_canonical/1, which seems to imply an operator, which is exactly the kind of thing that you shouldn't need to worry about with in the output of write_canonical/1.

bakaq avatar Aug 11 '24 01:08 bakaq

I wasn't addressing write_canonical output, just the top-level results display.

On Sun, Aug 11, 2024 at 11:30 AM Kauê Hunnicutt Bazilli < @.***> wrote:

Well, this is also problematic, because the intent of write_canonical/1 (at least as I understand it) is to make a representation that can always be read back with the same meaning no matter the state of the system.

?- X is 1 rdiv 2, Y = 1 rdiv 2, write_canonical(X-Y), nl. % Ok, write_canonical/1 can differentiate between rationals % and composite terms. -(1 rdiv 2,rdiv(1,2)) X is 1 rdiv 2, Y = 1 rdiv 2. ?- Xcanon-_ = -(1 rdiv 2,rdiv(1,2)), X is 1 rdiv 2, Xcanon = X. % But the representation isn't faithful, in the sense that % it can't be read back with the same meaning. false.

In fact I would say that this is even worse, because now you have 1 rdiv 2 in the output of write_canonical/1, which seems to imply an operator, which is exactly the kind of thing that you shouldn't need to worry about with in the output of write_canonical/1.

— Reply to this email directly, view it on GitHub https://github.com/mthom/scryer-prolog/issues/2473#issuecomment-2282341983, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFNKSESEBFZ6DWUAR7SMWHTZQ25ENAVCNFSM6AAAAABMKBBS4OVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEOBSGM2DCOJYGM . You are receiving this because you commented.Message ID: @.***>

infradig avatar Aug 11 '24 01:08 infradig

Let me give examples of how my two suggestions would look like.

1: Rational numbers as composite terms

This is the one that makes the most sense to me. Just treat rdiv(X,Y) with X and Y integers and Y positive specially internally, but still as a normal composite term in practice. Scryer already does this for lists and (partial) strings. In this case:

?- X is 1 rdiv 2, X = 1 rdiv 2.
   true.

2: Representing rational numbers canonically in another way

I can't really think how to do this without just repeating method 1 with another functor (and in that case, why not just use rdiv?) or creating a special literal syntax like 1r2.

?- X is 1 rdiv 2, X = canon_rational(1,2).
   true.
?- X is 1 rdiv 2, X = 1r2.
   true.

bakaq avatar Aug 11 '24 01:08 bakaq

I wasn't addressing write_canonical output, just the top-level results display.

Makes sense, and that is a very good way to represent the "internal state" that I talked about that differentiates a rational number from a compound term. This is better than what Scryer has, but I think it's going in the wrong direction because now you just admitted that there is hidden state that isn't representable by write_canonical/1.

bakaq avatar Aug 11 '24 01:08 bakaq

In theory I also like option 1 the best, but this kind of implicit representations are prone to lots of errors, so it needs to be carefully considered

aarroyoc avatar Aug 11 '24 20:08 aarroyoc

For completeness, here is IF/Prolog's approach:

[user] ?- X is 1 rdiv 3, X =.. As, write_canonical(X). 
0r1/3
X     = 0r1/3
As     = [0r1/3]

The / above is not an operator. That is, it also works with op(0,yfx,/).

UWN avatar Aug 14 '24 06:08 UWN

But can this canonical representation be faithfully read back?

?- X is 1 rdiv 3, X = 0r1/3.
   true.

That would be the kind of thing I was looking for. Also, it was mentioned above that you don't like this kind of rational literal syntax, could you explain why?

bakaq avatar Aug 14 '24 13:08 bakaq

But can this canonical representation be faithfully read back?

It can be read back even with op(0,yfx,/). (in IF).

Strictly speaking, 0r1/3 is not a valid extension since this could be valid Prolog text with r an appropriate infix operator. But then, there are so many other integer formats that start with 0, like 0b, 0o, 0x and 0'.

UWN avatar Aug 14 '24 15:08 UWN

I ran into this issue with scryer-js's bindings API (https://github.com/guregu/scryer-js?tab=readme-ov-file#binding-variables), and was about to make a discussion asking about literals for rationals when I found this issue.

// becomes: X = 1, write(X).
pl.queryOnce("write(X).", { bind: { X: 1 } }); 
// becomes: X is 1 rdiv 3, write(X).
pl.queryOnce("write(X).", { bind: { X: new Rational(1, 3) } }); 
// currently unsupported, needs something like: _X_1 is 1 rdiv 3, _X_2 is 1 rdiv 5, X = [_X_1, _X_2], write(X).
pl.queryOnce("write(X).", { bind: { X: [new Rational(1, 3), new Rational(1, 5)] } }); 

I'm OK with either solution (1 rdiv 3 being treated as a number even with (=)/2, or a special syntax).

guregu avatar Aug 09 '25 17:08 guregu