Rational numbers canonical representation
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:
- Have the composite term
X rdiv Yactually be a rational number, assuming X and Y are integers and Y is positive (I like this one more). - 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.
SWI Prolog uses
$ swipl
?- X is 1 rdiv 2.
X = 1r2.
?-
but @uwn objects to this notation.
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.
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.
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.
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: @.***>
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.
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.
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
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,/).
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?
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'.
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).