I've got loads of other posts in this ng to respond to but I came across something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons such
as where
A < B < C
means that B is between A and C? Or should a programmer have to write
A < B && B < C
?
I like the visual simplicity of the first form but it doesn't look so intuitive with a mix of relational operators such as in
A < B > C
What does that mean?!
For programming, what do you guys prefer to see in a language? Would you rather have each relation yield false/true (so that A < B in the above
yields a boolean) or to allow the above kind of chaining where a meaning
is attributed to a succession of such operators?
If the latter, what rules should govern how successive relational
operators are applied?
I've got loads of other posts in this ng to respond to but I came across something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons such as where
A < B < C
means that B is between A and C? Or should a programmer have to write
A < B && B < C
?
I like the visual simplicity of the first form but it doesn't look so intuitive
with a mix of relational operators such as in
A < B > C
What does that mean?!
applied?
On 05/09/2021 11:50, James Harris wrote:
I've got loads of other posts in this ng to respond to but I came
across something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons
such as where
A < B < C
I think in most languages operators of the same precedence would
associate to the left. So that would mean whatever is meant by
(A < B) < C
which in most languages would be ill-formed (unless you had overloaded
'<' with some suitable meaning).
means that B is between A and C? Or should a programmer have to write
A < B && B < C
Yes
?
I like the visual simplicity of the first form but it doesn't look so
intuitive with a mix of relational operators such as in
A < B > C
What does that mean?!
Nothing unless you had defined some meaning. For sure you could define a language which allowed such constructs, but it would stick out as a
special case like a sore thumb.
I've got loads of other posts in this ng to respond to but I came across something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons such
as where
A < B < C
means that B is between A and C? Or should a programmer have to write
A < B && B < C
?
On 05/09/2021 11:50, James Harris wrote:
I've got loads of other posts in this ng to respond to but I came
across something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons
such as where
A < B < C
I think in most languages operators of the same precedence would
associate to the left. So that would mean whatever is meant by
(A < B) < C
which in most languages would be ill-formed (unless you had overloaded
'<' with some suitable meaning).
means that B is between A and C? Or should a programmer have to write
A < B && B < C
Yes
Note that
A + B + C
has a defined meaning, but
A + C + B
might be different (one or the other might overflow).
On 05/09/2021 12:50, James Harris wrote:
I've got loads of other posts in this ng to respond to but I came across
something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons such
as where
A < B < C
means that B is between A and C? Or should a programmer have to write
A < B && B < C
?
Ask yourself if the need to check that a value is within a range is
common enough that you need a special syntax to handle it.
I'd say no,
but there is always a balance to be found and it varies from language to language. I am not a fan of having lots of special syntaxes, or
complicated operators - whether they are done using symbols or vast
numbers of keywords.
My vote would be to make relational operators return a "boolean",
It is not particularly hard
to write "A < B && B < C" when you need it.
On 05/09/2021 11:50, James Harris wrote:
A < B < C
For programming, what do you guys prefer to see in a language? Would
you rather have each relation yield false/true (so that A < B in the
above yields a boolean) or to allow the above kind of chaining where a
meaning is attributed to a succession of such operators?
If the latter, what rules should govern how successive relational
operators are applied?
I support such operators (althought it might be limited to 4 operators). Something like:
A op1 B op2 C op3 D
is approximately the same as:
(A op1 B) and (B op2 C) and (C op3) D
except that the middle terms should be evaluated once, not twice.
In any case, any combinations will be well-defined. Your last example
means A < B and B > C, and will be true for A, B, C values of 5, 20, 10
for example.
If a language supports a concept of ranges, represented by "b .. c" (or similar syntax), then "a in b .. c" is a good way to handle such tests.
I would not invent such a syntax purely for such tests, but if it is
used for array slices, for-loops, etc., then you have a good feature.
On 05/09/2021 12:27, Bart wrote:
I take it that aside from single evaluation you treat
A op1 B op2 C
essentially as
(A op1 B) and (B op2 C)
but how did you choose to have that interact with higher and lower precedences of surrounding operators?
Do you treat chained comparisons
as syntactic sugar or as compound expressions in their own right?
My booleans operators (such as 'and' and 'not') have lower precedence
than comparisons (but with 'not' having higher precedence than 'and')
and my arithmetic operators have higher precedence.
In case that's confusing, for these few operators I have the following
order (higher to lower).
Arithmetic operators including +
Comparisons including <
Booleans including 'not' and 'and'
Therefore,
not a < b + 1 and b + 1 < c
would evaluate the (b + 1)s first, as in
B = (b + 1)
It would then apply 'not' before 'and' as in
(not (a < B)) and (B < c)
but if I were to implement operator chaining I think it would be better
for the 'not' in
not a < B < c
to apply to the entire comparison
not (a < B < c)
In a sense, the 'and' which is part of comparison chaining would have
the same precedence as the comparison operators rather than the
precedence of the real 'and' operator.
If you are still with me(!), what did you choose?
On 06/09/2021 10:55, David Brown wrote:
On 05/09/2021 12:50, James Harris wrote:
I've got loads of other posts in this ng to respond to but I came across >>> something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons such
as where
A < B < C
means that B is between A and C? Or should a programmer have to write
A < B && B < C
?
[OK, this is a /fourth/ reply to you within half an hour. You should
stop saying things I disagree with!]
Ask yourself if the need to check that a value is within a range is
common enough that you need a special syntax to handle it.
Yes, it is, in my code anyway.
I used to use A <= B <= C for that, until that was replaced by A in B..C:
elsif c='1' and lxsptr^ in '0'..'6' and ...
Some older code:
return int32.min <= x <= int32.max
(However a more common use of 'in' is to compare against several values:
'A in [B, C, D]')
I'd say no,
but there is always a balance to be found and it varies from language to
language. I am not a fan of having lots of special syntaxes, or
complicated operators - whether they are done using symbols or vast
numbers of keywords.
In C-style, the example above would be:
else if (c=='1' && *lxsptr>='0' && *lxsptr<='6' && ...)
My vote would be to make relational operators return a "boolean",
They do. Except that A=B=C is not two operators, but treated as a single operator: (A=B=C) returns a boolean.
It is not particularly hard
to write "A < B && B < C" when you need it.
* B has to be written twice
* It's not always a simple expression, so you need to ensure both are actually identical
* The reader needs to double check that it /is/ the same expression
* The compiler needs to do extra work to avoid evaluating it twice
* But if it has side-effects, the compiler is obliged to evaluate twice
On 2021-09-06 13:24, David Brown wrote:
If a language supports a concept of ranges, represented by "b .. c" (or
similar syntax), then "a in b .. c" is a good way to handle such tests.
I would not invent such a syntax purely for such tests, but if it is
used for array slices, for-loops, etc., then you have a good feature.
Yes, and this is another challenge for the type system, unless ranges
are built-in (e.g. in Ada).
In a more powerful type system "in", ".." could be operations as any
other. The range would be a first-class type.
Further generalization. The mathematical term for range is an indicator
set. A general case indicator set can be non-contiguous, e.g. a number
of ranges. Others examples are a set of indices extracting a column of a matrix, a submatrix, a diagonal of a matrix etc.
On 06/09/2021 11:28, James Harris wrote:
On 05/09/2021 12:27, Bart wrote:
Do you treat chained comparisons as syntactic sugar or as compound
expressions in their own right?
I used to transform them into explicit AND expressions, but that wasn't satisfactory. Now chained comparisons form a single AST node (see my
reply to Charles).
My booleans operators (such as 'and' and 'not') have lower precedence
than comparisons (but with 'not' having higher precedence than 'and')
and my arithmetic operators have higher precedence.
In case that's confusing, for these few operators I have the following
order (higher to lower).
Arithmetic operators including +
Comparisons including <
Booleans including 'not' and 'and'
You have the wrong precedence for 'not'. As a unary operator, it should
work like C's - (negate), !, ~.
but if I were to implement operator chaining I think it would be
better for the 'not' in
not a < B < c
to apply to the entire comparison
not (a < B < c)
As written here that's fine. But I'd have a problem with making a
special case for unary 'not':
not A < B means not (A < B)? (I assume it applies to single >)
-A < B means (-A) < B
For the first, just use parentheses. (I also use:
unless A < B ...
for the reverse logic)
In a sense, the 'and' which is part of comparison chaining would have
the same precedence as the comparison operators rather than the
precedence of the real 'and' operator.
If you are still with me(!), what did you choose?
These are my precedence levels:
8 ** # highest
7 * / rem << >>
6 + - iand ior ixor
5 ..
4 = <> < <= >= > in notin
3 and
2 or
1 := # lowest
And, Or are lower precedence than comparisons.
Unary op are always applied first (but multiple unary ops on either side
of a term - prefix and postfix - have their own rules).
On 06/09/2021 14:02, Dmitry A. Kazakov wrote:
On 2021-09-06 13:24, David Brown wrote:
If a language supports a concept of ranges, represented by "b .. c" (or
similar syntax), then "a in b .. c" is a good way to handle such tests.
I would not invent such a syntax purely for such tests, but if it is >>> used for array slices, for-loops, etc., then you have a good feature.
Yes, and this is another challenge for the type system, unless ranges
are built-in (e.g. in Ada).
In a more powerful type system "in", ".." could be operations as any
other. The range would be a first-class type.
Further generalization. The mathematical term for range is an indicator
set. A general case indicator set can be non-contiguous, e.g. a number
of ranges. Others examples are a set of indices extracting a column of a
matrix, a submatrix, a diagonal of a matrix etc.
I think you'd want to avoid that kind of generalisation, at least at the language level. Having an "in" operator that could be used with
different types would be all you need. The language could support
common cases that are simple to implement, such as Pascal-like sets, or
a range made as a pair of two types that support relational operators.
But support for more general sets, collections of ranges, etc., should
be left to classes.
On 05/09/2021 12:50, James Harris wrote:
I've got loads of other posts in this ng to respond to but I came across
something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons such
as where
A < B < C
means that B is between A and C? Or should a programmer have to write
A < B && B < C
?
Ask yourself if the need to check that a value is within a range is
common enough that you need a special syntax to handle it.
I'd say no,
but there is always a balance to be found and it varies from language to language. I am not a fan of having lots of special syntaxes, or
complicated operators - whether they are done using symbols or vast
numbers of keywords.
My vote would be to make relational operators return a "boolean", and to
make operations between booleans and other types a syntax error or
constraint error, and to disallow relational operators for booleans.
Then "A < B < C" is a compile-time error. It is not particularly hard
to write "A < B && B < C" when you need it. Put more focus on making it
hard to write incorrect or unclear code.
On 2021-09-06 13:24, David Brown wrote:
If a language supports a concept of ranges, represented by "b .. c" (or
similar syntax), then "a in b .. c" is a good way to handle such tests.
I would not invent such a syntax purely for such tests, but if it is
used for array slices, for-loops, etc., then you have a good feature.
Yes, and this is another challenge for the type system, unless ranges
are built-in (e.g. in Ada).
In a more powerful type system "in", ".." could be operations as any
other. The range would be a first-class type.
On 06/09/2021 12:25, Bart wrote:
On 06/09/2021 11:28, James Harris wrote:
On 05/09/2021 12:27, Bart wrote:
...
Do you treat chained comparisons as syntactic sugar or as compound
expressions in their own right?
I used to transform them into explicit AND expressions, but that
wasn't satisfactory. Now chained comparisons form a single AST node
(see my reply to Charles).
IIRC in your reply to Charles your node relied on the programmer using
the same operator as in the < of
A < B < C
whereas proper chaining requires support for different operators such as
A < B <= C
If one is going to support chaining then AISI that expression should
also be parsed to be a node.
In case that's confusing, for these few operators I have the
following order (higher to lower).
Arithmetic operators including +
Comparisons including <
Booleans including 'not' and 'and'
You have the wrong precedence for 'not'. As a unary operator, it
should work like C's - (negate), !, ~.
I am not copying C!
not (a < B < c)
As written here that's fine. But I'd have a problem with making a
special case for unary 'not':
not A < B means not (A < B)? (I assume it applies to single >)
-A < B means (-A) < B
Why are you equating a boolean with an arithmetic operator? What
connection do you see between them?
;-)
For the first, just use parentheses. (I also use:
unless A < B ...
for the reverse logic)
In a sense, the 'and' which is part of comparison chaining would have
the same precedence as the comparison operators rather than the
precedence of the real 'and' operator.
If you are still with me(!), what did you choose?
These are my precedence levels:
8 ** # highest
7 * / rem << >>
6 + - iand ior ixor
5 ..
4 = <> < <= >= > in notin
3 and
2 or
1 := # lowest
I have a different and arguably simpler order. Of those you mention,
highest to lowest I have
* bitwise ops
* arithmetic ops
* comparison ops
* boolean ops
* assignment ops
So
not A gt B
means
not (A gt B)
If you think about it some more I believe you'll agree that it makes
sense and will immediately change your compiler and all your source code
to suit. ;-)
I am not saying I would not change what I have so far but I've put a lot
of thought into the precedence table. Keeping the operators in logical
groups or 'families' makes the overall order a great deal easier to
remember. For example, **all** the boolean ops are applied after the comparison ops.
Within each family the operators are in familiar orders, e.g.
multiplication comes before addition, 'and' comes before 'or', etc.
And, Or are lower precedence than comparisons.
Unary op are always applied first (but multiple unary ops on either
side of a term - prefix and postfix - have their own rules).
Having "their own rules" sounds as though it could be confusing.
:-(
On 06/09/2021 13:20, James Harris wrote:
On 06/09/2021 12:25, Bart wrote:
On 06/09/2021 11:28, James Harris wrote:
You have the wrong precedence for 'not'. As a unary operator, it
should work like C's - (negate), !, ~.
I am not copying C!
I used C because that's widely known. But unary ops, in pretty much
every language I've tried, always bind more tightly than binary ops. So
they don't have meaningful precedence.
(There might be the odd exception such as -A**B which in maths means
-(A**B) not (-A)**B. Actually maths would be a good model for this:
not (a < B < c)
As written here that's fine. But I'd have a problem with making a
special case for unary 'not':
not A < B means not (A < B)? (I assume it applies to single >)
-A < B means (-A) < B
Why are you equating a boolean with an arithmetic operator? What
connection do you see between them?
Both not and - (negate) are unary ops, and should be parsed the same
way, regardless of what types they typically take.
These are my precedence levels:
8 ** # highest
7 * / rem << >>
6 + - iand ior ixor
5 ..
4 = <> < <= >= > in notin
3 and
2 or
1 := # lowest
I have a different and arguably simpler order. Of those you mention,
highest to lowest I have
* bitwise ops
* arithmetic ops
* comparison ops
* boolean ops
* assignment ops
That's the same as mine except that you treat bitwise ops (and or xor
shifts) as higher than anything else, and in the same group?
But I don't believe this is the complete set of precedence levels,
unless "+" has the same precedence as "*" so that:
So
not A gt B
means
not (A gt B)
If you think about it some more I believe you'll agree that it makes
sense and will immediately change your compiler and all your source
code to suit. ;-)
This is interesting, because I used to write expressions like this:
if not (A in B)
as without the parentheses, it would be (not A ) in B. But rather than introduce bizarre rules for 'not', contrary to every other language so
it would cause confusion, I instead allowed:
if A not in B
However this doesn't work well or comparison ops, or with a chain of
such ops.
The only way your proposal can make sense, is for either a single
comparison or chain of comparisons to bind so tightly that it forms a
single term.
So syntactically, A<B<C is treated like A.B.C.
That means dispensing with normal precedence rules for comparison, which would have consequences:
if N + 1 <= Limit then ++N ...
would end up being parsed as :
if N + (1 <= Limit) then ++N ...
Unary op are always applied first (but multiple unary ops on either
side of a term - prefix and postfix - have their own rules).
Having "their own rules" sounds as though it could be confusing.
:-(
This is to be in line with other languages. It means that this term:
op1 op2 X op3 op4
is parsed as:
op1(op2(op4((op3 X))))
In practice it works intuitively; you mustn't over-think it! So:
-P^ # ^ is a deref op
On 06/09/2021 12:41, Bart wrote:
[OK, this is a /fourth/ reply to you within half an hour. You should
stop saying things I disagree with!]
I've been interspersing with a few agreements...
If a language supports a concept of ranges, represented by "b .. c" (or similar syntax), then "a in b .. c" is a good way to handle such tests.
I would not invent such a syntax purely for such tests,
but if it is
used for array slices, for-loops, etc., then you have a good feature.
(Since I may have accidentally complemented you for a language feature,
I need to add balance - don't you have a space key on your keyboard?
Why don't you use it when writing code?)
On 06/09/2021 14:46, Bart wrote:
So syntactically, A<B<C is treated like A.B.C.
I don't get that. I could have
if X and not (A < (B + 1) == C)
where none of the parens would be necessary.
- a & b ;i.e. negate (a bitand b)
On 06/09/2021 12:24, David Brown wrote:
(Since I may have accidentally complemented you for a language feature,
I need to add balance - don't you have a space key on your keyboard?
Why don't you use it when writing code?)
Indeed!
In fairness, the absence of spaces doesn't look too bad when variable
names are short - such as might be used in example code fragments. But
IMO spaceless code becomes hard to read with longer identifiers as used
in proper programming.
I blame Knuth for my pedantry here. After having read "The TeX Book" in
my student days, I have been unable to ignore poor layout of code, mathematics, or text. Lamport's "LaTeX" book didn't help either.
On 06/09/2021 22:24, David Brown wrote:
...
I blame Knuth for my pedantry here. After having read "The TeX Book" in
my student days, I have been unable to ignore poor layout of code,
mathematics, or text. Lamport's "LaTeX" book didn't help either.
What's the exact title? I can find "Tex, The Program" but copies are too expensive.
On 06/09/2021 18:03, James Harris wrote:
On 06/09/2021 14:46, Bart wrote:
So syntactically, A<B<C is treated like A.B.C.
I don't get that. I could have
if X and not (A < (B + 1) == C)
where none of the parens would be necessary.
I mean that I have these broad levels
Syntax A() A.B A^ a[]
Unary -A +A abs A inot A istrue A not A ... and maths ops
Binary A+B etc
For comparison ops to bind more tightly than any unary, they'd have to
be in that top level,
which would then lead to thse issues:
* A + B < C would bind in funny ways like my example
* Only A.B in the syntactic group has an infix 'shape', and there is now ambiguity in A.B < C.D, which would be parsed as ((A.B) < C).D, unless I
now introduced different precedences at that level.
You get around that by allowing groups of unary ops in-between groups of binary ops, but that's a little too outre for me.
- a & b ;i.e. negate (a bitand b)
So - A & B means -(A & B), but - A * B still means (-A) * B ?
I really don't like precedences, or having to remember them,
and tried
to keep them minimal:
** is unusual, so that's easy to remember
:= is at the other end, so that's easy too
* and /, and + and -, are known to everyone, and must go between those two.
That leaves comparisons, AND or OR. Comparisons can naturally go just
below normal expressions.
AND and OR I just remember from Pascal.
The full set is 6 levels:
Exponentiation
Scaling
Adding
Comparing (includes 'in/not in')
AND
OR
I can leave out assignment as you don't need to think about it; in many languages you don't even have assignment in an expression. But when you don't, it'll be lowest of all.
I haven't mentioned shifts and bitwise ops. Since shifts do scaling,
they can lumped in that group. The other bitwise ones are lumped with
add, since I can't think of a good reason they should be (a) higher
predence then Add; (b) lower precedence than Add.
(There is one more "..", which is an odd one out. There are problems at
every level, but I'm trying it out between Add and Compare. That one I
can never remember where it goes.)
On 05/09/2021 12:50, James Harris wrote:
I've got loads of other posts in this ng to respond to but I came across
something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons such
as where
A < B < C
means that B is between A and C? Or should a programmer have to write
A < B && B < C
?
Ask yourself if the need to check that a value is within a range is
common enough that you need a special syntax to handle it. I'd say no,
but there is always a balance to be found and it varies from language to language. I am not a fan of having lots of special syntaxes, or
complicated operators - whether they are done using symbols or vast
numbers of keywords.
My vote would be to make relational operators return a "boolean", and to
make operations between booleans and other types a syntax error or
constraint error, and to disallow relational operators for booleans.
Then "A < B < C" is a compile-time error. It is not particularly hard
to write "A < B && B < C" when you need it. Put more focus on making it
hard to write incorrect or unclear code.
On 06/09/2021 10:55, David Brown wrote:
On 05/09/2021 12:50, James Harris wrote:
I've got loads of other posts in this ng to respond to but I came across >>> something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons such
as where
A < B < C
means that B is between A and C? Or should a programmer have to write
A < B && B < C
It is not particularly hard
to write "A < B && B < C" when you need it.
* B has to be written twice
* It's not always a simple expression, so you need to ensure both are actually identical
* The reader needs to double check that it /is/ the same expression
I haven't yet decided whether or not to include composite comparisons
but I can see readability benefits.
Though it's not just about ranges. For example,
low == mid == high
Some such as
x < y > z
is not, by itself, so intuitive. But as you say, they can all be read as written with "and" between the parts. In this case,
x < y && y > z
I haven't yet decided whether or not to include composite comparisons
but I can see readability benefits.
If you don't have chained comparisons, then you have to decide what
a < b < c or a = b = c mean. Where there isn't an intuitive alternative meaning, then you might as well use that syntax to allow chains.
But you might want to restrict it so that the sequence of comparisons[...]
are either all from (< <= =) or (> >= =).
Then, x < y > z would not be allowed (it's too hard to grok); [...]ISTM harder to restrict it than to allow it. But again
That has worked very well, and implemented much more simply when
built-in to a compiler, than trying to do it with language-building
features, as your example shows. For example, my way works for any types
for which < <= >= > are defined, for any types at all when only = <> are involved,
and it will work with mixed types.
On 27/10/2021 01:15, Bart wrote:
If you don't have chained comparisons, then you have to decide what
a < b < c or a = b = c mean. Where there isn't an intuitive alternative
meaning, then you might as well use that syntax to allow chains.
/Syntactically/, "a < b < c" is correct [FSVO!] in most
languages, and if not it's because the semantics are obtruding
into the syntax in ways that they ought not to. IOW, "a + b + c"
is correct syntax almost no matter how you slice and dice, and
there is no interesting syntactic difference between that and the
"<" case. Both of these fail [if they do] because of the types
of "a" [etc], not because "a OP b OP c" is a forbidden construct.
So really, this is a question of the semantics of "a < b".
We really, really don't want to mess around too much with "a < b"
itself, or else we all get confused. The trouble then is that conventionally "a < b" returns Boolean and throws away the "b".
Effectively, you either have [eg] "TRUE < c" [which is either
meaningless or very likely not what the perpetrator intended] or
else you have to "special case" "a < b < c" so that "b" is kept
around, and so that "a < b" means one thing if that is the whole
expression and means something quite different if it is the left
operand of another operator. Special-casing is clearly possible,
esp in a private language, but it's not desirable in general.
So we "need" a new operator.
All of which set me thinking about how to do it, or at
least something close to "it", in languages with user-defined
operators. The following is what I came up with for Algol 68G
[it's a complete program -- explanations available if anyone is
actually interested]:
====== 8< ====== 8< ====== 8< ====== 8< ====== 8< ====== 8< ======
PRIO +< = 5, +> = 5, +<= = 5, +>= = 5, += = 4, +/= = 4;
MODE IB = STRUCT (INT i, BOOL b);
OP +< = (INT i,j) IB: ( i < j | (j, TRUE) | (~, FALSE) ),
< = (IB p, INT k) BOOL: ( b OF p | i OF p < k | FALSE ),
+< = (IB p, q) IB:
( b OF p | (i OF q, i OF p < i OF q) | (~, FALSE) ),
+< = (IB p, INT k) IB:
( b OF p | (k, i OF p < k) | (~, FALSE) ),
+< = ([] INT a) BOOL:
IF UPB a <= LWB a THEN TRUE # vacuously #
ELSE INT p := a [LWB a];
FOR i FROM LWB a + 1 TO UPB a
DO ( p < a[i] | p := a[i] | e ) OD;
TRUE EXIT e: FALSE
FI;
# Repeat the above for the other five operators #
# Use as, for example: #
print ( 1 +< 2 +< 3 < 4);
print ( +< [] INT (1, 2, 3, 4, 5, 6) );
# cast needed as the MODE (type) of (1,2,3,...) is
not fully determined by the syntax in this position #
[] INT c = (6, 5, 4, 3, 2, 1); print ( +< c ) # no cast! #
# prints "TTF" #
====== 8< ====== 8< ====== 8< ====== 8< ====== 8< ====== 8< ======
I initially used "<<" as the new operator, but [for good if obscure
reasons] that can't be used as a monadic operator in Algol. More
generally, it's not hard to write new operators such that [eg]
IF 0 +< [] INT (a,b,c) +<= 100 THEN ...
works. Whether that's worth doing is another matter.
and [perhaps] more readable than
IF a > 0 AND a <= 100 AND b > 0 AND ...
but I've managed without over several decades of programming and
can't claim ever to have missed it.
But you might want to restrict it so that the sequence of comparisons[...]
are either all from (< <= =) or (> >= =).
Then, x < y > z would not be allowed (it's too hard to grok); [...]ISTM harder to restrict it than to allow it.
/Syntactically/, "a < b < c" is correct [FSVO!] in most
languages, and if not it's because the semantics are obtruding
into the syntax in ways that they ought not to.
On 29/10/2021 07:27, Dmitry A. Kazakov wrote:
An imperative programming language does not need any of that.
Inclusion tests are better with intervals
b in a..c
sets are better with aggregates
(a,b,c,d)
No doubt, but I don't want to have to mess with the
compiler to add and use them.
It is worth to mention that originally
a < b < c
is not an expression. It is a proposition and it is also meant to convey transitivity. a is less than b and also less than c. For that reason
a < b > c
is not used,
rather
a,c < b
while
a < b < c < d
is pretty common to define an ordered set.
An imperative programming language does not need any of that.
Inclusion tests are better with intervals
b in a..c
sets are better with aggregates
(a,b,c,d)
[...] MoreAnd what it actually means is another! I can't relate it what you write below ...
generally, it's not hard to write new operators such that [eg]
IF 0 +< [] INT (a,b,c) +<= 100 THEN ...
works. Whether that's worth doing is another matter.
It's neater... which I'd anyway write as:
and [perhaps] more readable than
IF a > 0 AND a <= 100 AND b > 0 AND ...
if a in 1..100 and b>0 then
I wouldn't use chained compares at all, as this is even better.
Syntax is so easy to add to a language!
I wouldn't like to go back to whatever I was using in the mid-80s...
I've just done it; it took 3 minutes, and some 8 lines of code. SoBut you might want to restrict it so that the sequence of comparisons[...]
are either all from (< <= =) or (> >= =).
Then, x < y > z would not be allowed (it's too hard to grok); [...]ISTM harder to restrict it than to allow it.
you're right, it was harder by requiring some extra effort and mode
code, but it probably saves some head-scratching later.
On 29/10/2021 01:03, Bart wrote:
[...] MoreAnd what it actually means is another! I can't relate it what you
generally, it's not hard to write new operators such that [eg]
IF 0 +< [] INT (a,b,c) +<= 100 THEN ...
works. Whether that's worth doing is another matter.
write below ...
It's neater... which I'd anyway write as:
and [perhaps] more readable than
IF a > 0 AND a <= 100 AND b > 0 AND ...
if a in 1..100 and b>0 then
That's only the first half of it! Or did you miss the "..."?
Put differently, it meant "a" and "b" and "c" all > 0 and <= 100.
[NB, "a > 0" is equivalent to "a >= 1" for integers, but not for
reals. But that's another can of worms.]
I wouldn't use chained compares at all, as this is even better.
I've never used them in programming [until yesterday!].
Syntax is so easy to add to a language!
Syntax is easy to change in your own private language,
It is essentially impossible to make any major changes to those
languages that have standards; it's also quite hard to make
sure that syntax changes don't impact (a) on legacy programs,
and (b) other parts of the syntax.
If /you/ bungle in this
sort of way in /your/ language, you can quietly revert to the
old version of the compiler; if the C committee does something
daft, chaos ensues, which is why they are /extremely/ cautious.
[...]
I wouldn't like to go back to whatever I was using in the mid-80s...
In my case, that would be primarily C [tho' thenabouts
I was jointly giving a module on comparative languages, and we
got through something like 20 languages in as many lectures,
with tops-and-tails describing various types of language and
projects in about six of them]. But C was merely the best of
a bad job [esp given the then-available resources and compilers
-- we had 2.4MB discs and 64KB limits on code and data on our
PDP-11 (with over 100 users and ~40 terminals)].
Personally,
I gradually became more and more bored with writing C programs,
and largely switched to shell scripts and similar. When A68G
came along, I suddenly regained the joy of programming, and of
using a language instead of fighting it. But that's back to
the '70s!
I've just done it; it took 3 minutes, and some 8 lines of code. SoBut you might want to restrict it so that the sequence of comparisons[...]
are either all from (< <= =) or (> >= =).
Then, x < y > z would not be allowed (it's too hard to grok); [...]ISTM harder to restrict it than to allow it.
you're right, it was harder by requiring some extra effort and mode
code, but it probably saves some head-scratching later.
When you say you've done it, you mean you've tweaked your compiler. More important from my PoV is to explain it to users,
inc re-writing the "User Manual" to include the new syntax.
For
that purpose, it's easier to say that all comparison operators can
be "chained", giving examples, than to say "these can be chained,
separately those can be chained, but you can't mix them, except
for the [in]equality operators", with a sub-text of "because you
wouldn't understand what they meant, so don't trouble your pretty
head about it".
If your example was short of the extra clutter, then it would be:
if 0 < (a,b,c) <= 100 then
It's a little clearer what's happening, but this list processing is
still causing some confusion. You've taken a chain of compare operators,
and combined that with list operations. So what would:
0 < (a,b,c) < (d,e,f)
mean?
Your original also has ambiguities: should the result be a single
value (True when 0 < x <= 100 for every x in (a,b,c))? Or should it be:
(True, False, True)
which is the the set of booleans from each 0 < x <= 100?
So this is a sort of red herring when talking about the benefits of
chained compares.
I've switched machines so DAK's posts are visible again; but he suggests syntax like this:
For
that purpose, it's easier to say that all comparison operators can
be "chained", giving examples, than to say "these can be chained,
separately those can be chained, but you can't mix them, except
for the [in]equality operators", with a sub-text of "because you
wouldn't understand what they meant, so don't trouble your pretty
head about it".
On 2021-10-30 13:32, Bart wrote:
If your example was short of the extra clutter, then it would be:
if 0 < (a,b,c) <= 100 then
It's a little clearer what's happening, but this list processing is
still causing some confusion. You've taken a chain of compare
operators, and combined that with list operations. So what would:
0 < (a,b,c) < (d,e,f)
mean?
Depends on the lists. Actually the above is abbreviations or OR-list vs AND-lists. Depending on that you expand macros (because these are
actually macros rather than operations):
0 < AND(a,b,c) < AND(d,e,f) --- expand --->
--- expand ---> (0 < a < d) and (0 < a < e) and (0 < a < f) ...
Here "and" is a proper logic operation.
Your original also has ambiguities: should the result be a single
value (True when 0 < x <= 100 for every x in (a,b,c))? Or should it be:
(True, False, True)
which is the the set of booleans from each 0 < x <= 100?
It is unambiguous because "for every x in (a,b,c)" makes the list a
macro list AND(a,b,c).
You do not want
a or b < c
to mean
(a < c) or (b < c)
It is a funny stuff, e.g.
(a and b) * (c or d) >= 100
means
((a * c) or (a * d) >= 100) and ((b * c) or (b * d) >= 100)
Cool, no? Even better, what about this one
(a xor b) > c
(:-))
On 30/10/2021 13:22, Dmitry A. Kazakov wrote:
On 2021-10-30 13:32, Bart wrote:
If your example was short of the extra clutter, then it would be:
if 0 < (a,b,c) <= 100 then
It's a little clearer what's happening, but this list processing is
still causing some confusion. You've taken a chain of compare
operators, and combined that with list operations. So what would:
0 < (a,b,c) < (d,e,f)
mean?
Depends on the lists. Actually the above is abbreviations or OR-list
vs AND-lists. Depending on that you expand macros (because these are
actually macros rather than operations):
0 < AND(a,b,c) < AND(d,e,f) --- expand --->
--- expand ---> (0 < a < d) and (0 < a < e) and (0 < a < f) ...
Here "and" is a proper logic operation.
That's one interpretation of it. And one implementation.
For example, it's not clear what happened to b and c, unless they will
each also be compared against d, e and f. (Making it more like a matrix operation.)
Your original also has ambiguities: should the result be a single
value (True when 0 < x <= 100 for every x in (a,b,c))? Or should it be:
(True, False, True)
which is the the set of booleans from each 0 < x <= 100?
It is unambiguous because "for every x in (a,b,c)" makes the list a
macro list AND(a,b,c).
It's ambiguous because there is more than one intuitive result. For the scalar-vector example:
x op (a, b, c)
one result might be:
(x op a, x op b, x op c)
A vector- or list-processing language needs careful design.
You do not want
a or b < c
to mean
(a < c) or (b < c)
It is a funny stuff, e.g.
(a and b) * (c or d) >= 100
means
((a * c) or (a * d) >= 100) and ((b * c) or (b * d) >= 100)
Cool, no? Even better, what about this one
(a xor b) > c
(:-))
Those are all weird constructions at odds with the normal precedences of
< and 'or'. Here:
(a and b) * (c or d) >= 100
it looks like you're multiplying two bools, then comparing that result
with 100. It would need special operators. But I still don't understand what's happening even if your expand version.
[...] So what would:
0 < (a,b,c) < (d,e,f)
mean?
Your original also has ambiguities: should the result be a single
value (True when 0 < x <= 100 for every x in (a,b,c))? Or should it
be:
(True, False, True)
which is the the set of booleans from each 0 < x <= 100?
So this is a sort of red herring when talking about the benefits of
chained compares.
It's easy to add syntax in a new language. And it really isn't hard
in an established language.
Where do you think all those new features in sucessive C standards
come from? It is specific implementations adding extensions.
So &&L in gnu C is special syntax to take the address of a label.
(Needed because labels live in their own namespace, and so &L would
be ambiguous.)
C syntax was already chaotic, it can't get much worse!
Eg. 'break' being overloaded; a*b meaning multiply a by b, OR declare variable b of type 'pointer to a'.
But I couldn't use A68; I'd be constantly fighting the syntax and
tyope system.
And switching between capitals and lower case all the
time...
When you say you've done it, you mean you've tweaked yourThat would just be a Note: you can only mix '= < <=' or '= > >='.
compiler. More important from my PoV is to explain it to users,
inc re-writing the "User Manual" to include the new syntax.
(But don't try to explain further or you'd get tied up in knots.)
It can be to avoid confusion and to be keep things readable.
On 30/10/2021 12:32, Bart wrote:
[...] So what would:
0 < (a,b,c) < (d,e,f)
mean?
Whatever you wanted it to mean. As Dmitri proposed and
[long ago ...] Algol specified, all operators [inc standard ones
such as "+" and "AND"] are in "user space". You can define or
re-define them as you choose. Your program is embedded in an
environment in which lots of things are defined for you, but you
don't have to stick with the normal definitions. It will all be
visible in your code, so it's up to you whether you choose to
confuse yourself or not.
As previously noted, I've never felt the need to use
"chained" compares, so I'm not going to start talking now
about the benefits of them [for programs, as opposed to in
mathematics, where some usages are well understood].
[...]
It's easy to add syntax in a new language. And it really isn't hard
in an established language.
Where do you think all those new features in sucessive C standards
come from? It is specific implementations adding extensions.
Most of them came from Algol! Each new standard thus
far has added something from Algol, and AFAIR nothing that is
Algol-like has ever been taken away.
[In view of discussions
with Brian Kernighan,
So &&L in gnu C is special syntax to take the address of a label.
(Needed because labels live in their own namespace, and so &L would
be ambiguous.)
It's not /needed/. It's a way of creating botches.
Lots of early languages had label variables, and associated
paraphernalia, and gradually mainstream languages dropped
them. I'm not a subscriber to "all labels are evil" [there
was one, and an elided "GOTO", in the code I showed], but
there is very much less need for them today than there was
[or seemed to be] in the '50s and '60s.
Eg. 'break' being overloaded; a*b meaning multiply a by b, OR declare
variable b of type 'pointer to a'.
That's the sort of thing that happens when a private
language goes public before it is properly defined, and
without proper critical scrutiny.
When you say you've done it, you mean you've tweaked yourThat would just be a Note: you can only mix '= < <=' or '= > >='.
compiler. More important from my PoV is to explain it to users,
inc re-writing the "User Manual" to include the new syntax.
Note again that in Algol "chained" comparisons, if
you choose to use them, are entirely private grief, and no
change to any syntax is required.
Here's a line from an old program I found today (to do with a Rubik cube):
if face[4] = face[5] = face[6] = face[2] = face[8] = face[1] =
face[3] then
There are several ways of doing this without using chain comparisons, especially as all terms are indices into the same list, but when
suddenly you have to test that 7 different things have the same value,
this was the simplest way to do so at the time.
So why not?
On 2021-10-31 11:52, Bart wrote:
Here's a line from an old program I found today (to do with a Rubik
cube):
if face[4] = face[5] = face[6] = face[2] = face[8] = face[1] =
face[3] then
There are several ways of doing this without using chain comparisons,
especially as all terms are indices into the same list, but when
suddenly you have to test that 7 different things have the same value,
this was the simplest way to do so at the time.
So why not?
Because it is very marginal and for a sane person suggests a typo error.
A cleaner way would to define a function like
All_Same (List, Ignore) -- Compare list elements ignoring one
Otherwise, you can do that in Ada, no problem:
----------------------------------------------
type Chained_Result is record
Partial : Boolean;
Tail : Integer;
end record;
function "=" (Left, Right : Integer) return Chained_Result is
begin
return (Left = Right, Right);
end "=";
function "=" (Left : Chained_Result; Right : Integer)
return Chained_Result is
begin
return (Left.Partial and then Left.Tail = Right, Right);
end "=";
function "=" (Left : Chained_Result; Right : Integer)
return Boolean is
begin
return (Left.Partial and then Left.Tail = Right);
end "=";
----------------------------------------------
That is all you need to do this:
a : Integer := 1;
b : Integer := 2;
c : Integer := 3;
begin
Put_Line (Boolean'Image ((a = b) = c));
will print
FALSE
Note, the parenthesis. These are a purely syntax requirement, not
because the language could not handle:
a = b = c
It would be all OK, but relational operators sharing operands are not
allowed in Ada. You must separate them. Other examples are
a or b and c
-a**b
a**b**c
etc. Ada does require you to remember 20+ levels of precedence.
And yes, you could make the above a generic package to work with any
integer type.
The problem of handling chained comparisons syntactically (as a macro)
rather than typed within the normal/sane syntax is that you will have numerous barriers the parser could not handle. Starting with parenthesis:
a = (b) = c
These you could work around. But what about chaining mixed types:
b := (x, y, z); -- List of elements
a = b = c
It is easy to handle in Ada since you have proper objects for which you
can define the equality operator you wanted. Macros cannot do that.
On 31/10/2021 11:45, Dmitry A. Kazakov wrote:
On 2021-10-31 11:52, Bart wrote:
Here's a line from an old program I found today (to do with a Rubik
cube):
if face[4] = face[5] = face[6] = face[2] = face[8] = face[1] =
face[3] then
There are several ways of doing this without using chain comparisons,
especially as all terms are indices into the same list, but when
suddenly you have to test that 7 different things have the same
value, this was the simplest way to do so at the time.
So why not?
Because it is very marginal and for a sane person suggests a typo error.
How about an example like this:
if hsample2 = vsample2 = hsample3 = vsample3 and
hsample1 <= 2 and vsample1 <=2 then
(The last part of that looks like a candicate for one of your (hsample1, vsample2) <= 2 ideas. But most likely syntaxes end up looking worse than
the original.)
The problem is that this is all quite complicated, and requires some
extra skills, especially if you want to work with any types.
I favour building it into a language. That also requires special skills,
but that is only required of the implementer.
On 2021-10-31 15:32, Bart wrote:
On 31/10/2021 11:45, Dmitry A. Kazakov wrote:
On 2021-10-31 11:52, Bart wrote:
Here's a line from an old program I found today (to do with a Rubik
cube):
if face[4] = face[5] = face[6] = face[2] = face[8] = face[1] =
face[3] then
There are several ways of doing this without using chain
comparisons, especially as all terms are indices into the same list,
but when suddenly you have to test that 7 different things have the
same value, this was the simplest way to do so at the time.
So why not?
Because it is very marginal and for a sane person suggests a typo error.
How about an example like this:
if hsample2 = vsample2 = hsample3 = vsample3 and
hsample1 <= 2 and vsample1 <=2 then
(The last part of that looks like a candicate for one of your
(hsample1, vsample2) <= 2 ideas. But most likely syntaxes end up
looking worse than the original.)
I would split that into several tests commenting on what is going on.
Long formulae are subject of careful revision. Mathematically, the above looks like an expression of some sort of symmetry. Surely the problem
space has a term for that and I expect to see it in the program, e.g. as
a function call. That is the difference between construed examples and real-life programs.
On 31/10/2021 15:43, Dmitry A. Kazakov wrote:
Below is the context for that example.
This checks a jpeg file configuration before choosing a suitable handler
for the rest of the file.
h/vsample1/2/3 are sampling rates in hoz/vert for Y, U and V channels;
the latter two must match each other.
[...] Your program is embedded in anI don't buy that. You will confuse yourself, and anyone who tries to
environment in which lots of things are defined for you, but you
don't have to stick with the normal definitions. It will all be
visible in your code, so it's up to you whether you choose to
confuse yourself or not.
read, understand, modify or port your program. And there will be
problems mixing code that has applied different semantics to the same
syntax.
Some things I think needs to be defined by the language.
I do very little in the way of listing processing, but it's handling
mostly with user-functions, and I do not support chained comparisons. However, they are in the standard library.
Something like your '0 < []int (a,b,c)' would be written as:
mapsv(('<'), 0, (a,b,c))
with a vector result.
[...] Each new [C] standard thusExamples? I can't think of any Algol features in C, unless you're
far has added something from Algol, and AFAIR nothing that is
Algol-like has ever been taken away.
talking about very early days.
Lots of opportunity to get C fixed. But people who like C aren'tEg. 'break' being overloaded; a*b meaning multiply a by b, OR declareThat's the sort of thing that happens when a private
variable b of type 'pointer to a'.
language goes public before it is properly defined, and
without proper critical scrutiny.
interested; every terrible misfeature is really a blessing!
Note again that in Algol "chained" comparisons, ifThe grief would be in having to write:
you choose to use them, are entirely private grief, and no
change to any syntax is required.
IF a=b AND b=c AND c=d THEN
or, in devising those operator overloads, which still doesn't give
you a nice syntax, and will restrict the types it will work on,
instead of just writing:
if a=b=c=d then
On 31/10/2021 10:52, Bart wrote:
[I wrote, re Algol:]
[...] Your program is embedded in anI don't buy that. You will confuse yourself, and anyone who tries to
environment in which lots of things are defined for you, but you
don't have to stick with the normal definitions. It will all be
visible in your code, so it's up to you whether you choose to
confuse yourself or not.
read, understand, modify or port your program. And there will be
problems mixing code that has applied different semantics to the same
syntax.
With respect, that's just silly. You aren't forced to define
or use your own operators; the standard ones are broadly the same as
those in every other normal language. So are the priorities, and the standard functions. If a skilled programmer /chooses/ to provide
some new operators [and aside from deliberate attempts to obfuscate
for competition purposes, and such like], then it is to make code
/clearer/.
For example, if you happen to be doing matrix algebra,
it is likely to be much clearer if you write new operators rather
than new functions, so that you can reproduce quite closely normal mathematical notations.
Code will only be "mixed" if several
people write different bits of a project /without/ agreeing the specifications of their own bits; that is a problem for /any/
large project, and nothing to do with Algol. You can write code
that is difficult to "read, understand, modify or port" in any
non-trivial language; again, nothing special about Algol, more to
do with unskilled programmers following dubious practices.
I do very little in the way of listing processing, but it's handling
mostly with user-functions, and I do not support chained comparisons.
However, they are in the standard library.
If you don't "support" them then what do you mean by "in the standard library"?
Something like your '0 < []int (a,b,c)' would be written as:
mapsv(('<'), 0, (a,b,c))
with a vector result.
Is that supposed to be clearer?
What are the specification
for your "mapsv" function, and what am I supposed to do if I want
something slightly different? [My Algol snippet was entirely user-
written, and could easily be tweaked any way I chose, all there and
visible in the code. If you want something different, take it and
tweak it your own way -- entirely up to you.]
Examples? I can't think of any Algol features in C, unless you're
talking about very early days.
Early days were important for C, as when there was only K&R
C, and C was "what DMR's compiler does", it was easy to change the
language, and there are a fair number of changes between the first
versions and 7th Edition Unix. More recently, they have added
type "Bool", dynamic arrays, stronger typing, parallel processing,
complex numbers, anonymous structures and unions, possible bounds
checking, "long long" types and related ideas, mixed code and
declarations, better string handling, and doubtless other things
I've forgotten.
You can't fix "break" or "a*b"; by the time of 7th Edition,
it was already too late,
The grief would be in having to write:
IF a=b AND b=c AND c=d THEN
or, in devising those operator overloads, which still doesn't give
you a nice syntax, and will restrict the types it will work on,
instead of just writing:
if a=b=c=d then
Allowing "a=b=c=d" shows that "a=b" means one thing if it
is "stand alone" and something quite different if it is a left
operand. You're very good at saying "my language allows XXX" for
all manner of interesting and perhaps even desirable "XXX", but
it's at the expense of specifying exactly what the related syntax
and semantics are.
In Algol, expressions are parsed by the usual
rules of precedence and [L->R] associativity, after which "a o b"
for any operands "a" and "b" and any operator "o" means exactly
the same as "f(a,b)" where "f" is a function with two parameters
of the same types as those of "o" and the same code body as that
of "o", all completely visible in the RR plus your own code.
What's the corresponding rule in your language?
On 01/11/2021 23:00, Andy Walker wrote:
Something like your '0 < []int (a,b,c)' would be written as:
mapsv(('<'), 0, (a,b,c))
with a vector result.
Is that supposed to be clearer?
I'm not pretending my language properly supports list operations. That
would be a very different kind of language.
My mapsv is clearer for me since the 's' and 'v' indicate that it takes
a 'scalar' and a 'vector' operand. ('scalar' here just means it is
treated as a single value, and 'vector' that it is a multiple value.)
On 23/10/2021 15:39, James Harris wrote:
Though it's not just about ranges. For example,
low == mid == high
Some such as
x < y > z
is not, by itself, so intuitive. But as you say, they can all be read
as written with "and" between the parts. In this case,
x < y && y > z
I haven't yet decided whether or not to include composite comparisons
but I can see readability benefits.
If you don't have chained comparisons, then you have to decide what a <
b < c or a = b = c mean. Where there isn't an intuitive alternative
meaning, then you might as well use that syntax to allow chains.
But you might want to restrict it so that the sequence of comparisons
are either all from (< <= =) or (> >= =).
So that if you were to plot the numeric values of A op B op C for
example when the result is True, the gradient would always be either >=
0 or <= 0; never mixed; ie no ups then downs.
(Which also allows you to infer the relationship of A and C).
Then, x < y > z would not be allowed (it's too hard to grok);
neither
would x != y != z, even if it is equivalent to:
x != y and y != z
(That is better written as y != x and y != z; I would write it as y not
in [x, z])
If a skilled programmer /chooses/ to provide
some new operators [and aside from deliberate attempts to obfuscate
for competition purposes, and such like], then it is to make code
/clearer/.
On 27/10/2021 01:15, Bart wrote:
On 23/10/2021 15:39, James Harris wrote:
Though it's not just about ranges. For example,
low == mid == high
Some such as
x < y > z
is not, by itself, so intuitive. But as you say, they can all be read
as written with "and" between the parts. In this case,
x < y && y > z
I haven't yet decided whether or not to include composite comparisons
but I can see readability benefits.
If you don't have chained comparisons, then you have to decide what a
< b < c or a = b = c mean. Where there isn't an intuitive alternative
meaning, then you might as well use that syntax to allow chains.
Good point. Should it even be possible to apply magnitude comparisons
(i.e. those including < and > symbols) to boolean values?
Assuming it should be, if one wanted to evaluate
a < b
and then ask if the boolean result was 'less than' c, without chained comparisons it would be as simple as
a < b < c
but how would one express that in a language which supported chained comparisons? I think my preference would be
bool(a < b) < c
where bool() would break up the comparison chain by having the form of a function call but would have no effect on the value. That would work on
your equals example, too: either of
bool(a == b) == c
a == bool(b == c)
There's another potential problem with chained comparisons, though.
Consider
a < b < c < d
What if a programmer wanted to evaluate the b < c part first?
Then, x < y > z would not be allowed (it's too hard to grok);
Is it really any harder to grok than
x < y && y > z
It would seem inconsistent to allow
a == b == c
and yet prohibit
a != b != c
On 03/11/2021 15:58, James Harris wrote:
and yet prohibit
a != b != c
Yes it looks inconsistent. But what does the latter mean?
It is possible to mechanically translate this as 'a != b and b != c',
but I believe the abbreviated version could be confusing; it looks like
is testing whether a, b, c are all different from each other, but a True result doesn't mean that: a could have the same value as c.
(For them all different, you'd need 'a != b != c != a'. Easier I think
to do 'not (a = b = c)'.)
On 03/11/2021 15:58, James Harris wrote:
It would seem inconsistent to allow
? a == b == c
and yet prohibit
? a != b != c
Yes it looks inconsistent. But what does the latter mean?
It is possible to mechanically translate this as 'a != b and b != c',
but I believe the abbreviated version could be confusing; it looks like
is testing whether a, b, c are all different from each other, but a True result doesn't mean that: a could have the same value as c.
(For them all different, you'd need 'a != b != c != a'. Easier I think
to do 'not (a = b = c)'.)
On 03/11/2021 15:58, James Harris wrote:
On 27/10/2021 01:15, Bart wrote:
Yes, I use parentheses to break up a chain. For these two tests:
if a = b = c then fi
if (a = b) = c then fi
there are these two different ASTs:
- 1 if:
- - 1 cmpchain: keq keq
- - - 1 name: a
- - - 1 name: b
- - - 1 name: c
- - 2 block:
- 1 if:
- - 1 cmp: keq
- - - 1 cmp: keq
- - - - 1 name: a
- - - - 2 name: b
- - - 2 name: c
- - 2 block:
In my case I don't have a Bool type (not exposed via the type system
anyway), so the second evaluates a=b to 0 or 1, then compares that to 'c'.
bool(a == b) == c
a == bool(b == c)
There's another potential problem with chained comparisons, though.
Consider
a < b < c < d
What if a programmer wanted to evaluate the b < c part first?
Parentheses again? That's what they're for!
Then, x < y > z would not be allowed (it's too hard to grok);
Is it really any harder to grok than
x < y && y > z
I can read the latter like I can 'a < b' and 'c < d'; as independent
tests. But combining them suggests some interelationships that aren't
really there.
What's the relationship between x and z? There isn't any, other than
they're both less than y (that is, if the expression is True).
It would seem inconsistent to allow
a == b == c
and yet prohibit
a != b != c
Yes it looks inconsistent. But what does the latter mean?
It is possible to mechanically translate this as 'a != b and b != c',
but I believe the abbreviated version could be confusing; it looks like
is testing whether a, b, c are all different from each other, but a True result doesn't mean that: a could have the same value as c.
(For them all different, you'd need 'a != b != c != a'.
Easier I think
to do 'not (a = b = c)'.)
On 2021-11-03 18:54, Bart wrote:
On 03/11/2021 15:58, James Harris wrote:
and yet prohibit
a != b != c
Yes it looks inconsistent. But what does the latter mean?
In fact this is very frequently used in mathematics, as proposition, of course, e.g.
∀x≠y≠z
And, also, carefully observe that while equality (=) is transitive
inequality (/=) is not. So
a != b != c
means this
(a != b) and (b != c) and (a != c)
Follow the science! (:-))
It is possible to mechanically translate this as 'a != b and b != c',
but I believe the abbreviated version could be confusing; it looks
like is testing whether a, b, c are all different from each other, but
a True result doesn't mean that: a could have the same value as c.
(For them all different, you'd need 'a != b != c != a'. Easier I think
to do 'not (a = b = c)'.)
That is not same.
You're suggesting that a pattern like this:
a != b != c
which would be otherwise not be allowed (according to my preference),
should be treated as a circular set of compares like the above?
That would be an idea, although introducing more inconsistency.
On 03/11/2021 19:56, Dmitry A. Kazakov wrote:
On 2021-11-03 18:54, Bart wrote:
On 03/11/2021 15:58, James Harris wrote:
and yet prohibit
a != b != c
Yes it looks inconsistent. But what does the latter mean?
In fact this is very frequently used in mathematics, as proposition,
of course, e.g.
∀x≠y≠z
And, also, carefully observe that while equality (=) is transitive
inequality (/=) is not. So
a != b != c
means this
(a != b) and (b != c) and (a != c)
Follow the science! (:-))
This came up in my post as needing:
a != b != c != a
You're suggesting that a pattern like this:
a != b != c
which would be otherwise not be allowed (according to my preference),
should be treated as a circular set of compares like the above?
That would be an idea, although introducing more inconsistency.
On 03/11/2021 17:54, Bart wrote:
On 03/11/2021 15:58, James Harris wrote:
On 27/10/2021 01:15, Bart wrote:
...
Yes, I use parentheses to break up a chain. For these two tests:
if a = b = c then fi
if (a = b) = c then fi
there are these two different ASTs:
- 1 if:
- - 1 cmpchain: keq keq
- - - 1 name: a
- - - 1 name: b
- - - 1 name: c
- - 2 block:
- 1 if:
- - 1 cmp: keq
- - - 1 cmp: keq
- - - - 1 name: a
- - - - 2 name: b
- - - 2 name: c
- - 2 block:
In my case I don't have a Bool type (not exposed via the type system
anyway), so the second evaluates a=b to 0 or 1, then compares that to
'c'.
AISI in the presence of chained comparisons your second example would
have to be implemented as
bool(a == b) == c
For the reasoning, see below about parens.
bool(a == b) == c
a == bool(b == c)
There's another potential problem with chained comparisons, though.
Consider
a < b < c < d
What if a programmer wanted to evaluate the b < c part first?
Parentheses again? That's what they're for!
In your earlier example parens change the semantics, don't they?
In this
instance I was asking about keeping the meaning unchanged, just carrying
out the inner evaluation first. Imagine that evaluation of the first
term, a, has a side effect that is not wanted unless b < c.
ISTM that if a language is going to include chained comparison that
there's a need for both approaches: explicit conversion of part of an expression to a boolean and ordering, and that parens should indicate ordering without changing the semantics. Thus
a < b < c < d
would be the same as
(((a < b) < c) < d)
IOW the chain should extend through parens.
This makes chained
comparisons not as simple for a programmer as we have been considering.
What do you think?
(((a < b) < c) < d)
Evaluation order is a different subject. How would you enforce either a
first or b first even on something as simple as:
a + b
f(a, b)
(((a < b) < c) < d)
This doesn't guaranteed that a<b is evaluated before c.
On 03/11/2021 20:55, James Harris wrote:
On 03/11/2021 17:54, Bart wrote:
On 03/11/2021 15:58, James Harris wrote:
On 27/10/2021 01:15, Bart wrote:
a < b < c < d
would be the same as
(((a < b) < c) < d)
IOW the chain should extend through parens.
I don't agree. A chain must be linear, or it's not a chain (see my first AST).
If you want each of a, b, c, d to be evaluated in a certain order, then parentheses is not the way. Say the desired order is d, a, c, b. The
only way to guarantee that is to do:
td:=d; ta:=a; tc:=c; tb:=b
if ta < tb < tc < td
If you want that without that boilerplate code, then how will you tell
the language the order? (How will the compiler manage it?)
If, further, you want the /comparisons/ to be done in a certain order,
then that's harder. If the desired order is b<c then c<d then a<b, you'd
have to write it the normal way:
if b<c and c<d and a<b
You can't use a chain which implies a particular order, short-circuiting
in a similar manner to 'and' and 'or':
If a < b is false in 'a < b < c < d', then there's no point in doing the rest. And especially no point in evaluating d first!
This makes chained comparisons not as simple for a programmer as we
have been considering.
What do you think?
Evaluation order is a different subject. How would you enforce either a
first or b first even on something as simple as:
a + b
f(a, b)
(((a < b) < c) < d)
This doesn't guaranteed that a<b is evaluated before c. I just means it
does (a<b) and c, not a and (b<c).
It also means that this is not a comparison chain.
On 03/11/2021 23:13, Bart wrote:
On 03/11/2021 20:55, James Harris wrote:
On 03/11/2021 17:54, Bart wrote:
On 03/11/2021 15:58, James Harris wrote:
On 27/10/2021 01:15, Bart wrote:
...
a < b < c < d
would be the same as
(((a < b) < c) < d)
IOW the chain should extend through parens.
I don't agree. A chain must be linear, or it's not a chain (see my
first AST).
That's good! Disagreement is key to advancement. :-)
Are you not, in this case, however, making the use of parens
inconsistent with their use elsewhere in the language? For example, in
a - (b - c)
the parens define a putative order for the subtraction operations and
the order matters.
If you want each of a, b, c, d to be evaluated in a certain order,
then parentheses is not the way. Say the desired order is d, a, c, b.
The only way to guarantee that is to do:
td:=d; ta:=a; tc:=c; tb:=b
if ta < tb < tc < td
Interesting. Is that how you see the evaluation of chaining? I see it differently. For me the trouble with your version
is that it evaluates
some of the terms before it knows that it needs to - which is not what I understand the chaining idea ought to do. AISI
a < b < c < d
should evaluate a then b, then if a < b is true evaluate c, then if b <
c is true evaluate d, then if c < d then the whole expression would be
true. Do you see it differently?
You can't use a chain which implies a particular order,
short-circuiting in a similar manner to 'and' and 'or':
Why not?
If a < b is false in 'a < b < c < d', then there's no point in doing
the rest. And especially no point in evaluating d first!
Indeed!
This makes chained comparisons not as simple for a programmer as we
have been considering.
What do you think?
Evaluation order is a different subject. How would you enforce either
a first or b first even on something as simple as:
a + b
f(a, b)
I don't understand. Why would that be a the problem? I do, in fact,
define the semantic evaluation order.
In practice a compiler /could/ evaluate operands in a different order
where it could be sure that that would not affect the semantics - which
would be most cases.
; (((a < b) < c) < d)
This doesn't guaranteed that a<b is evaluated before c. I just means
it does (a<b) and c, not a and (b<c).
It also means that this is not a comparison chain.
IYO...!
On 04/11/2021 10:32, James Harris wrote:
should evaluate a then b, then if a < b is true evaluate c, then if b
< c is true evaluate d, then if c < d then the whole expression would
be true. Do you see it differently?
Yes, that's what I do. 'if a < b < c < d' generates this intermediate code:
push t.start.a i64
push t.start.b i64
jumpge #4 i64
push t.start.b i64
push t.start.c i64
jumpge #4 i64
push t.start.c i64
push t.start.d i64
jumpge #4 i64
# <body of if>
#4:
(Currently this evaluates inner terms twice. I'll have to fix this by inserting extra stack manipulation instructions: dupl, swap etc.
Alternately there could be versions of jumpge etc that only pop one value.
Evaluation order is a different subject. How would you enforce
either a first or b first even on something as simple as:
a + b
f(a, b)
I don't understand. Why would that be a the problem? I do, in fact,So do I, in the case of f(a,b); 'b' is done first. But how to I get a
define the semantic evaluation order.
to be evaluated first? I can't do it by adding brackets!
In practice a compiler /could/ evaluate operands in a different
order where it could be sure that that would not affect the
semantics - which would be most cases.
If it doesn't affect the semantics, then why would a programmer want
a different order? It can only be for different results.
If a skilled programmer /chooses/ to provideNot sure about that. Defining new operators is probably a very poor
some new operators [and aside from deliberate attempts to obfuscate
for competition purposes, and such like], then it is to make code
/clearer/.
idea, making code less readable rather than more so.
For example, if a programmer defines an 'operator' called XX to be
used in function syntax such as
XX(a, b)
then while the meaning of XX may be clear to the original implementor
someone else reading the code needs to learn what XX means in order
to understand what's involved.
As if that's not bad enough things can get worse. If a programmer
defines a brand new operator called >=< as in
a and b >=< c + 1
then there is not even a clue in the source code as to the precedence
of the new operator relative to other operators.
On 03/11/2021 17:06, James Harris wrote:
[I wrote:]
If a skilled programmer /chooses/ to provideNot sure about that. Defining new operators is probably a very poor
some new operators [and aside from deliberate attempts to obfuscate
for competition purposes, and such like], then it is to make code
/clearer/.
idea, making code less readable rather than more so.
If it makes the code less readable [though there is no general reason why it should], then of course a good programmer won't do it.
As if that's not bad enough things can get worse. If a programmer
defines a brand new operator called >=< as in
a and b >=< c + 1
then there is not even a clue in the source code as to the precedence
of the new operator relative to other operators.
Of course there is, otherwise it would be impossible to
parse formulas. In Algol, dyadic operators must have an associated priority; I suppose other languages could have some other way.
[May or may not be worth noting that ">=<" is not actually a legal
operator symbol in Algol.]
Sorry, but I had a lot of trouble understanding your A68 example. For example, part of it involved arrays, which I thought was some extra
ability you'd thrown in, but I think now may actually be necessary to implement that feature. [I still don't know...]
For example, if you happen to be doing matrix algebra,It could well be clearer, AFTER you've had to implement it via code
it is likely to be much clearer if you write new operators rather
than new functions, so that you can reproduce quite closely normal
mathematical notations.
that is a lot less clearer than ordinary user-code.
(My first scripting language had application-specific types including
3D transformation matrices and 3D points. There, if A and B are
matrices, and P is a point, then:
C := A*B # multiples matrices
Q := C*P # use C to transform P
/That/ was clear, with the bonus that the user didn't need to
implement a big chunk of the language themself!)
Code will only be "mixed" if severalIf the effect is to create lots of mini, overlapping dialects or
people write different bits of a project /without/ agreeing the
specifications of their own bits; that is a problem for /any/
large project, and nothing to do with Algol. You can write code
that is difficult to "read, understand, modify or port" in any
non-trivial language; again, nothing special about Algol, more to
do with unskilled programmers following dubious practices.
extensions, then that is a problem. There will be assorted custom
preludes or drag in too.
They are implemented as user-functions which are placed in a library[...] I do not support chained comparisons.If you don't "support" them then what do you mean by "in the
However, they are in the standard library.
standard library"?
that comes with the language.
it was already too late,It's never too late. An alternate to 'break' could have been
introduced for switch, eg. have both 'break' and 'breaksw';
eventually only 'breaksw' is allowed, and 'break' is an error. Then,
further along, 'break' inside switch is allowed to be used for
loop-break.
However, try taking a program from 1980 and try compiling it now.
Actually, take a program from 2021 and try building it with two
different compilers.
Allowing "a=b=c=d" shows that "a=b" means one thing if itI copied the feature from Python. You'd need to ask Guido what it
is "stand alone" and something quite different if it is a left
operand. You're very good at saying "my language allows XXX" for
all manner of interesting and perhaps even desirable "XXX", but
it's at the expense of specifying exactly what the related syntax
and semantics are.
means!
In Algol, expressions are parsed by the usualIf I write:
rules of precedence and [L->R] associativity, after which "a o b"
for any operands "a" and "b" and any operator "o" means exactly
the same as "f(a,b)" where "f" is a function with two parameters
of the same types as those of "o" and the same code body as that
of "o", all completely visible in the RR plus your own code.
What's the corresponding rule in your language?
A = B = C
in static code, then it works something like this:
* The dominant type of A, B, C is determined
* A, B, C are converted to that type as needed, as values A', B', C'
(This is for numeric types; "=" also works for exactly compatible
arbitrary types with no conversions applied)
* The expression returns True when A', B', C' have identical values
It is, of course, not possible to force bad programmers to produce
good code but it's at least better if a language does not encourage
bad practices - and I put it to you that creating brand new operators
with new symbols (which I believe is what you are thinking about) can
be such a feature.
For example, an operator that a programmer creates may help him at
the time because he is thinking about the problem and knows what the
operator is for. But it's easy to forget that the same symbol will be
a complete black box to someone else who looks at the code later.
All the later programmer will have to give a clue as to what is going
on is an unfamiliar operator symbol which tells him three quarters
of nothing - which is what I was trying to illustrate with the
example, below.
So in the example expression,
a and b >=< c + 1
what would be the priority of >=< (or whatever legal form is used)
relative to those of the adjacent operators 'and' and '+'?
On 02/11/2021 01:38, Bart wrote:
Sorry, but I had a lot of trouble understanding your A68 example. For
example, part of it involved arrays, which I thought was some extra
ability you'd thrown in, but I think now may actually be necessary to
implement that feature. [I still don't know...]
??? Arrays in Algol are sufficiently similar to those in
other languages [even C!] that I don't see why you would think them
some "extra ability".
(My first scripting language had application-specific types including
3D transformation matrices and 3D points. There, if A and B are
matrices, and P is a point, then:
C := A*B # multiples matrices
Q := C*P # use C to transform P
/That/ was clear, with the bonus that the user didn't need to
implement a big chunk of the language themself!)
Every language has to decide which facilities should be
provided as a standard library [or equivalent], and which left
to either purveyors of application-specific libraries or ordinary
users. Algol took the view, very reasonably, that it was not the
job of the language itself to specify matrix packages, statistics
packages, windowing systems, tensor calculus, cryptography, or a
host of other things that informed users can write themselves.
If the effect is to create lots of mini, overlapping dialects or
extensions, then that is a problem. There will be assorted custom
preludes or drag in too.
That too is nothing at all to do specifically with Algol
[or Fortran or C or Pascal or ...]. It's a "problem" for any
general purpose language.
It's not an unmitigated good for a
language to supply lots of facilities "as of right"; it makes
manuals and specifications much more complicated,
and it means
that every implementer has to be able to write stats functions,
symbolic algebra packages, sound editors, ..., whatever it is
you decide is important enough to be an integral part of the
language. What you do in your own private language is, of
course, entirely up to you, and you will presumably provide
those and only those things that you both want and know about.
The rest of the world has different wants and knowledge.
They are implemented as user-functions which are placed in a library
that comes with the language.
So they are supported!
I copied the feature from Python. You'd need to ask Guido what it
means!
I thought it was your language we were talking about. How
can I, or anyone else, judge your language against C or Algol or
any other language if /you/ can't tell us what its syntax and
semantics are?
OK, so we now know what "A = B = C" means. Are there
different rules for every operator, for every number of operands,
for every type, ..., amounting to perhaps hundreds of pages, or
is there [as in Algol] a general rule that can explain the syntax
in a couple of paragraphs and the semantics in another few? It
affects how hard your language is to explain to potential users,
or to people here trying to understand your arguments.
On 04/11/2021 20:37, James Harris wrote:
It is, of course, not possible to force bad programmers to produce
good code but it's at least better if a language does not encourage
bad practices - and I put it to you that creating brand new operators
with new symbols (which I believe is what you are thinking about) can
be such a feature.
Sorry, but why do you think that creating a new operator
is [other things being equal] a worse practice than creating a
new function with the same parameters, code, specification, etc?
What demons are you fighting?
For example, an operator that a programmer creates may help him at
the time because he is thinking about the problem and knows what the
operator is for. But it's easy to forget that the same symbol will be
a complete black box to someone else who looks at the code later.
So [and to the same extent] will be a function.
All the later programmer will have to give a clue as to what is going
on is an unfamiliar operator symbol which tells him three quarters
of nothing - which is what I was trying to illustrate with the
example, below.
It tells the programmer exactly as much as an unfamiliar
function name. Whether you see "a >=< b" or "myweirdfunc (a, b)"
you're going to have to go to the defining occurrence of the operator/function to find out what is going on [same in both
cases!].
[...]
So in the example expression,
a and b >=< c + 1
what would be the priority of >=< (or whatever legal form is used)
relative to those of the adjacent operators 'and' and '+'?
Whatever the programmer set it to be. In the case of
Algol, there is a table of standard priorities in the Revised
Report [RR10.2.3.0], which any programmer really has to learn
[same as in C or any other major language apart from the oddities
where all operators have the same priority or where expressions
are written in reverse-Polish notation]. The programmer gets to
choose where in that table the new operator comes.
I'm talking about introducing arrays to what appeared to be anSorry, but I had a lot of trouble understanding your A68 example. For??? Arrays in Algol are sufficiently similar to those in
example, part of it involved arrays, which I thought was some extra
ability you'd thrown in, but I think now may actually be necessary to
implement that feature. [I still don't know...]
other languages [even C!] that I don't see why you would think them
some "extra ability".
implementation of chained operators. Are they central to being able
to implement an arbitrary list of such comparisons, or are they there
for a different reason?
Your code layout is poor IMO;
for example you define multiple new
operators, but in a comma-separated list! That's poor choice for
separating such major bits of code.
I rewrote your operator-defining code in a style more like my current
syntax. Now the code is much clearer (I can see where one op
definition ends, and the next begins!)
though I still can't make out
how it works.
(I'm not sure what ~ does either.)
record intbool = [...]
operator "+<"(int i, j)intbool = [...]
operator "<"(intbool p, int k)bool = [...]
[...]
It's not an unmitigated good for aYet Algol68 - and C - include facilities for complex numbers. I've
language to supply lots of facilities "as of right"; it makes
manuals and specifications much more complicated,
never used them and never will. And A68 has all those advanced ways
of creating slices in several dimensions.
I see several levels of user-defined/overloaded operators:
TYPE 1 New alphanumeric operators can be created. While this doesn't
appear much different from new function names, it means syntax can
look like this:
a b c d e f
Problems:
(1) Which of those are operators, and which variables? You can't tell
from the 'shape' of the code.
(2) The compiler can't tell either, because those names may not be
resolvable until the next stage after parsing. So it cannot properly
form an AST of the right structure
(3-6) [... various confusions ...]
(14) Common to all of these is that if you see:
A + B
where A and B are of user-defined types (or maybe even of standard
types, if overriding normal behaviour is allowed), what exactly will
the code do?
It could decide to subtract the value of B from A!
On 05/11/2021 13:48, Bart wrote:
I see several levels of user-defined/overloaded operators:
TYPE 1 New alphanumeric operators can be created. While this doesn't
appear much different from new function names, it means syntax can
look like this:
a b c d e f
Problems:
(1) Which of those are operators, and which variables? You can't tell
from the 'shape' of the code.
This is easily solved, as in Algol, by using a different
"font" for operators. This idea was recognised as far back as IAL
["Algol 58"]. Every usual language already recognises something
of the sort; typically, quote symbols switch between code and
strings, comment symbols between code and comments, brackets into
or out of a "subscript" mode, and so on. Textbooks commonly use
one font for code examples, another for the ordinary text. The
snag back in the '50s was that card equipment had only upper case
letters so everything in the program had to be wedged into that.
Today we could do better, but the styles of the '50s and '60s are
too entrenched. The result of all that is that your example might
be written in A68G as [eg]
a B c D E f
where upper case denotes operators and lower case variables, or
in earlier versions of Algol-like languages as
On 05/11/2021 01:56, Bart wrote:
I'm talking about introducing arrays to what appeared to be anSorry, but I had a lot of trouble understanding your A68 example. For??? Arrays in Algol are sufficiently similar to those in
example, part of it involved arrays, which I thought was some extra
ability you'd thrown in, but I think now may actually be necessary to
implement that feature. [I still don't know...]
other languages [even C!] that I don't see why you would think them
some "extra ability".
implementation of chained operators. Are they central to being able
to implement an arbitrary list of such comparisons, or are they there
for a different reason?
Algol doesn't have "lists" as opposed to arrays, so if we
want to implement [eg] "+< somearray" to mean a chained comparison
between the elements of the array [similar, apparently to your
"mapsv (('<'), ...)", then of course "somearray" has to be an
array. I still don't see why it's so strange to involve/introduce
arrays to implement an indefinitely-long chain.
Your code layout is poor IMO;
"De gustibus ..." and all that jazz.
for example you define multiple new
operators, but in a comma-separated list! That's poor choice for
separating such major bits of code.
But the "major bits" were appropriately indented; adding unnecessary operator tokens would obscure the indentation for no
gain in clarity. It's not as though they were each two or three
pages long; I expect you to be able to follow indentation over a
couple of lines.
"~" is the "doesn't matter" token; it saves inventing some expression of the right type to fill in, and to confuse readers who
wonder why you've written 17 [or whatever]. Eg, "WHILE ... DO~OD"
is a loop where the controlled statement would be empty if Algol
allowed empty statements.
record intbool = [...]
operator "+<"(int i, j)intbool = [...]
operator "<"(intbool p, int k)bool = [...]
[...]
Note that the effect of your "clarity" is that the tokens
"+<" etc are now buried in the middle of the line instead of at the
front, where they would be more prominent.
a fan of layout wars.]
More importantly, your version is 51 lines, where mine was
15. So mine fits comfortably into one window [24 lines] even with
five appended lines of comments and three of examples, yours takes
three [esp with examples], meaning that anyone trying to understand
yours will be scrolling up and down like a yo-yo.
I see several levels of user-defined/overloaded operators:
On 04/11/2021 20:37, James Harris wrote:
It is, of course, not possible to force bad programmers to produce
good code but it's at least better if a language does not encourage
bad practices - and I put it to you that creating brand new operators
with new symbols (which I believe is what you are thinking about) can
be such a feature.
Sorry, but why do you think that creating a new operator
is [other things being equal] a worse practice than creating a
new function with the same parameters, code, specification, etc?
What demons are you fighting?
For example, an operator that a programmer creates may help him at
the time because he is thinking about the problem and knows what the
operator is for. But it's easy to forget that the same symbol will be
a complete black box to someone else who looks at the code later.
So [and to the same extent] will be a function.
All the later programmer will have to give a clue as to what is going
on is an unfamiliar operator symbol which tells him three quarters
of nothing - which is what I was trying to illustrate with the
example, below.
It tells the programmer exactly as much as an unfamiliar
function name. Whether you see "a >=< b" or "myweirdfunc (a, b)"
you're going to have to go to the defining occurrence of the operator/function to find out what is going on [same in both
cases!].
On 04/11/2021 10:32, James Harris wrote:
On 03/11/2021 23:13, Bart wrote:
a < b < c < d
should evaluate a then b, then if a < b is true evaluate c, then if b
< c is true evaluate d, then if c < d then the whole expression would
be true. Do you see it differently?
Yes, that's what I do. 'if a < b < c < d' generates this intermediate code:
push t.start.a i64
push t.start.b i64
jumpge #4 i64
push t.start.b i64
push t.start.c i64
jumpge #4 i64
push t.start.c i64
push t.start.d i64
jumpge #4 i64
# <body of if>
#4:
You can't use a chain which implies a particular order,
short-circuiting in a similar manner to 'and' and 'or':
Why not?
Because in the case of 'a<b<c<d', which may exit early when a>=b, c and
d would not get evaluated. But you're saying you want the programmer to
tell the language they should be evaluated first anyway?
In practice a compiler /could/ evaluate operands in a different order
where it could be sure that that would not affect the semantics -
which would be most cases.
If it doesn't affect the semantics, then why would a programmer want a different order? It can only be for different results.
For faster code? That's the compiler's job!
; (((a < b) < c) < d)
This doesn't guaranteed that a<b is evaluated before c. I just means
it does (a<b) and c, not a and (b<c).
It also means that this is not a comparison chain.
IYO...!
No, it's just not a chain. Unless you want to argue about the difference between a linked list, a binary tree, and a degenerate(?) binary tree
which is more of a vertical linked list.
My chain of comparison ops is equivalent to a linked list. Any other
shape, is not the same thing. Not in my language, because I say so!
On 05/11/2021 13:48, Bart wrote:
...
I see several levels of user-defined/overloaded operators:
...
That (now snipped) looks like an extensive write-up on operator
overloading options and a good springboard into the topic. The thing is,
this discussion is about chained comparisons. Why didn't you start a new thread?
On 06/09/2021 21:10, James Harris wrote:
On 06/09/2021 12:24, David Brown wrote:
(Since I may have accidentally complemented you for a language
feature, I need to add balance - don't you have a space key on
your keyboard? Why don't you use it when writing code?)
Indeed!
In fairness, the absence of spaces doesn't look too bad when
variable names are short - such as might be used in example code
fragments. But IMO spaceless code becomes hard to read with longer identifiers as used in proper programming.
It is a matter of style and opinion (and Bart knows that, of course).
But it is a serious point. Layout of code is vital to readability,
and spaces are a big part of that (as is consistency - you don't want different spacing depending on the length of the variables). It is
better to write "c == 1" than "c==1", because the "==" is not part of
either then "c" or the "1".
I've got loads of other posts in this ng to respond to but I came
across something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons
such as where
A < B < C
means that B is between A and C?
Or should a programmer have to write
A < B && B < C
?
I like the visual simplicity of the first form but it doesn't look so intuitive with a mix of relational operators such as in
A < B > C
What does that mean?!
For programming, what do you guys prefer to see in a language? Would
you rather have each relation yield false/true (so that A < B in the
above yields a boolean) or to allow the above kind of chaining where
a meaning is attributed to a succession of such operators?
If the latter, what rules should govern how successive relational
operators are applied?
On Sun, 5 Sep 2021 11:50:18 +0100
James Harris <james.harris.1@gmail.com> wrote:
I've got loads of other posts in this ng to respond to but I came
across something last night that I thought you might find interesting.
The issue is whether a language should support chained comparisons
such as where
A < B < C
Ok.
means that B is between A and C?
Why does this have something to do with B? ...
I.e., you seem to think it's the result of:
(A < B) && (B < C)
Charles thinks it's generally parsed as:
(A < B) < C
Whereas, I would've assumed it had something to do with A:
A < (B < C)
In other words, I generally assume that the assigned to variable, or
the intended final comparison of a chained sequence, is on the left, as
in C or BASIC etc, albeit there is no explicit assignment operator in
your chained comparison.
Sorry, but why do you think that creating a new operatorFighting demons may describe your approach to problem solving if you
is [other things being equal] a worse practice than creating a
new function with the same parameters, code, specification, etc?
What demons are you fighting?
are doing so by defining brand new operator symbols but I suggest
that there are specific problems with doing so:
1. While well-known operators are enunciable (e.g. := can be read as "becomes" and >= can be read as "is greater than or equal to") in a
way that reflects their meaning that's not true of new ones. For
example, how would you read any of these:
@:
#~
%!?
2. While the meaning of the new composite symbol may be logical to
the person who makes it up its appearance can be meaningless to
someone else. For example,
>=<
3. You may not like to hear criticism of Algol and I wouldn't
criticise it unnecessarily
but didn't you recently show code in which
Algol allows programmers to define the precedence of new operators?
If so, that also helps to make expressions unreadable for the reasons
I set out before, i.e. that in an expression such as
a or b #? c + d
there is no clue as to how #? it will be parsed relative to the
operators on either side of it.
It tells the programmer exactly as much as an unfamiliarI would say two things to that. First, the best approach is for a
function name. Whether you see "a >=< b" or "myweirdfunc (a, b)"
you're going to have to go to the defining occurrence of the
operator/function to find out what is going on [same in both
cases!].
language to come with a comprehensive standard library which provides
common (and not so common) data structures, operations and
algorithms. That saves many different programmers inventing the same solutions to problems and calling them by different names thus making
code easier to read for everyone.
Second, I'd say that where a programmer has to produce a new utility
function (it's not in the language, not in the standard library, and
not even in a library which someone else has published) it should be identified by a name rather than by a string of punctuation
characters, and that name should be used in a syntax in which the
precedence is apparent and unquestionable; the name will at least
give a reader some idea as to what the function does.
In your example, a >=< b gives no clue but shuffle_bits(a, b) tells
the reader something about the purpose, would happen with obvious
precedence and is meaningfully enunciable and is therefore a better
approach, IMO.
On 07/11/2021 15:49, James Harris wrote:
[I wrote:]
Sorry, but why do you think that creating a new operatorFighting demons may describe your approach to problem solving if you
is [other things being equal] a worse practice than creating a
new function with the same parameters, code, specification, etc?
What demons are you fighting?
are doing so by defining brand new operator symbols but I suggest
that there are specific problems with doing so:
I wasn't talking about problem solving but about a weird
frame of mind in which you are happy for people to write
proc wertyuiop = (int a, b) bool: a > b;
[adjust to whatever your preferred syntax for procedure declarations
is], but
op wertyuiop = (int a, b) bool: a > b;
is a no-no?
On 07/11/2021 15:49, James Harris wrote:
1. While well-known operators are enunciable (e.g. := can be read as
"becomes" and >= can be read as "is greater than or equal to") in a
way that reflects their meaning that's not true of new ones. For
example, how would you read any of these:
@:
#~
%!?
In Algol?
but didn't you recently show code in which >> Algol allows programmers to define the precedence of new operators?
It not merely allows, it requires that for /new/ dyadic
operators. How else could a compiler know what precedence to
use?
If so, that also helps to make expressions unreadable for the reasons
I set out before, i.e. that in an expression such as
a or b #? c + d
there is no clue as to how #? it will be parsed relative to the
operators on either side of it.
Again, you're seeing gremlins where none exist. IRL,
bad programmers write unreadable and incompetent code in any
language
[even Basic], and good programmers write readable
and competent code in any language [even (deleted)]. But in
return, the reader has to make the effort to become familiar
with the language.
In your example, a >=< b gives no clue but shuffle_bits(a, b) tells
the reader something about the purpose, would happen with obvious
precedence and is meaningfully enunciable and is therefore a better
approach, IMO.
You've perhaps forgotten, but ">=<" was /your/ example,
as was the idea that it was to shuffle bits. I used "+<", as
"<" couldn't be used, to give at least some impression of a
chained "<".
It would have been much less clear as a function.
User-assignable precedence for user-defined operators is no problem
for the compiler! I was saying that it is a problem for humans
reading the code because there's nothing in the context in which they
appear to indicate how they are supposed to be combined with adjacent operators.
Yes, a human could find where the unfamiliar operator had been
defined and given a priority and then work out how that relates to
adjacent operators but I suggest that it would be better if the way
the operator relates to its surroundings were to be present in the
code where the operator was used.
What's bad about the expression, above? It involves only threeIf so, that also helps to make expressions unreadable for the reasonsAgain, you're seeing gremlins where none exist. IRL,
I set out before, i.e. that in an expression such as
a or b #? c + d
there is no clue as to how #? it will be parsed relative to the
operators on either side of it.
bad programmers write unreadable and incompetent code in any
language
operators! Yet someone reading it cannot tell what order they are
applied in. I'm sorry but you cannot blame the programmer. The
facility itself is at fault.
On 10/11/2021 08:48, James Harris wrote:
User-assignable precedence for user-defined operators is no problem
for the compiler! I was saying that it is a problem for humans
reading the code because there's nothing in the context in which they
appear to indicate how they are supposed to be combined with adjacent
operators.
IRL, it quite simply is not and never has been a problem
for humans. Yes, of course you can write puzzles; but people with
serious intent to write useful code don't do that. There are four
uses for new operators:
-- monadic operators ["if isprime i then ..."]. Not a problem.
-- extensions of standard operators to new types. Not a problem.
-- operators that relate to the problem at hand [eg, dot and cross
multiplication of vectors], where you can reasonably expect the
precedence to follow the maths/physics/genetics/whatever.
-- The rest. I've never seen any of these.
Yes, a human could find where the unfamiliar operator had been
defined and given a priority and then work out how that relates to
adjacent operators but I suggest that it would be better if the way
the operator relates to its surroundings were to be present in the
code where the operator was used.
If there's a difficulty [I've never seen one], you could
always add a comment. Otherwise, what do you have in mind? Some
new syntax for every time you use an operator? ???
[...] Otherwise, what do you have in mind? SomeCreating a new operator with a new precedence /is/ changing the syntax.
new syntax for every time you use an operator? ???
Actually, in Algol68 you can change the syntax even with no new operators:
PRIO + = 9;
PRIO * = 5;
print((2+3*4))
To me this is all undesirable. I would favour:
* Having only a fixed set of built-in operator with fixed precedences
* Possibly, having some additional built-in symbolic operators which
are unassigned, available for user-programs to overload. Again with
fixed precedences (but likely to be just one)
On 10/11/2021 14:15, Bart wrote:
[I wrote (to James):]
[...] Otherwise, what do you have in mind? SomeCreating a new operator with a new precedence /is/ changing the syntax.
new syntax for every time you use an operator? ???
It's a point of view; but James was talking about /applied/ occurrences of the operator, not /defining/ occurrences.
Actually, in Algol68 you can change the syntax even with no new
operators:
What do you mean by "change the syntax"? Any non-trivial
change to a program is changing the syntactic structure of that
program; but neither in Algol 68 nor in most other languages is
there any way for a program to change its underlying syntax.
PRIO + = 9;
PRIO * = 5;
print((2+3*4))
Yeah, you can write puzzles. And ...? As an entry in an "Obfuscated Algol" competition, you can sensibly do things like
that. In normal code, you can't,
as Bad programming. But [in Algol] there has to be provision for
it, otherwise there would be no way to define "+" and "*" in the
first place -- they're not hard-wired in the syntax, only in the
code of the standard prelude.
[...]
To me this is all undesirable. I would favour:
* Having only a fixed set of built-in operator with fixed precedences
With or without allowing code to re-define operators for
other types?
* Possibly, having some additional built-in symbolic operators which
are unassigned, available for user-programs to overload. Again with
fixed precedences (but likely to be just one)
IOW, you want to exercise control over the symbolisms
used in my programs, as though you were an expert in whatever
field happens to be relevant to my programming task? I want
to use something for, say circle-plus, and I need to find out
whether you have graciously provided a symbol for just that
purpose, otherwise I'm SOL, and will have to resort to Algol
instead to write my program. Fine, but I [therefore] won't
be interested in your language.
Syntax for me includes whether a + b * c is parsed as a + (b * c) orActually, in Algol68 you can change the syntax even with no new operators: >> What do you mean by "change the syntax"? Any non-trivialchange to a program is changing the syntactic structure of that
program; but neither in Algol 68 nor in most other languages is
there any way for a program to change its underlying syntax.
as (a + b) * c. In other words, the structure of an expression.
[...] I think hard-coded
precedences, fixed in the grammar, are better.
[...] As an entry in anThen disallow it.
"Obfuscated Algol" competition, you can sensibly do things like
that. In normal code, you can't,
[...] But [in Algol] there has to be provision for(1) If that's how Algol is implemented, then OK, but it's not drawing
it, otherwise there would be no way to define "+" and "*" in the
first place -- they're not hard-wired in the syntax, only in the
code of the standard prelude.
a proper line between different kinds of code: implementation,
standard libraries, third party libraries, and user libraries and
code.
(2) Even if PRIO is needed to set a precedence, it could allow it
just once per distinct operator
(3) How is "+" defined in Algol 68?
Doesn't Algol68 limit the available symbols anyway? Eg. you couldn't
use "<". I couldn't use "+++" when I tried that.
I assume there are rules so that you can resolve "+++" and "+ + +", especially of the language likes to ignore white space.
On 10/11/2021 21:45, Bart wrote:
Syntax for me includes whether a + b * c is parsed as a + (b * c) orActually, in Algol68 you can change the syntax even with no newWhat do you mean by "change the syntax"? Any non-trivial
operators:
change to a program is changing the syntactic structure of that
program; but neither in Algol 68 nor in most other languages is
there any way for a program to change its underlying syntax.
as (a + b) * c. In other words, the structure of an expression.
If your language has a syntax that specifies that, then fine;
but Algol doesn't. It has a two-level grammar, so that the syntax
includes lots of other things, such as whether an identifier has
been declared, whether types match, and so on. I don't propose to
write an essay on 2LGs, it's all in the RR, and I've previously
referred you to the actual syntax of formulas in RR5.4.2.1. It
includes what other languages would call semantics.
[...] I think hard-coded
precedences, fixed in the grammar, are better.
Yes, I understand that to be your opinion. But the only
rationale you have produced for that is that rogue programmers
can abuse declared priorities. Oh, and that your own compiler
can't handle it.
Are we inventing languages for rogue programmers
or for productivity?
[...] As an entry in anThen disallow it.
"Obfuscated Algol" competition, you can sensibly do things like
that. In normal code, you can't,
It's 46 years too late to change the RR.
(3) How is "+" defined in Algol 68?
RR10.2.3.0a for the priority and an infinity of rules
typified by RR10.2.3.3i which defines it for integers, plus
similar versions for monadic "+", other lengths and other
parameter types. If you're really interested, you need to see
also RR10.1.3, which gives the same freedom as C's "as-if" rule,
and RR2.1.3.1e which sets out the assumed properties of numbers.
use "<". I couldn't use "+++" when I tried that.
I could have used "<", but that would have over-written
the standard meaning of "<". If you mean that you couldn't
declare an operator "+++", then [as I have already explained in
this thread several times] A68 operators are not free-for-all;
see RR9.4.2.1F for dyadic operators, RR9.4.2.1K for monadic.
[Trying things is Good, but you will ultimately save your own,
and more importantly my, time if you read the Revised Report.]
In the user code the elements do not form an array. The comparisonsI'm talking about introducing arrays to what appeared to be anAlgol doesn't have "lists" as opposed to arrays, so if we
implementation of chained operators. Are they central to being able
to implement an arbitrary list of such comparisons, or are they there
for a different reason?
want to implement [eg] "+< somearray" to mean a chained comparison
between the elements of the array [similar, apparently to your
"mapsv (('<'), ...)", then of course "somearray" has to be an
array. I still don't see why it's so strange to involve/introduce
arrays to implement an indefinitely-long chain.
are done with dedicated code for each, not using a loop. Inside the
compiler, it could make use of arrays, but the input source and
output code probably won't.
The consequences of your A68 solution is that it takes 4 times as
long to do 'a <+ b <+ c < d', as it does to do 'a < b AND b < c AND c
< d' [10 secs vs 2.5 secs for 10 million iterations].
When I make the same comparison in my languages, I get the same
timing, although that's because these simple expressions end up as
the same code. [0.75 secs for 1 billion iterations static, 0.85 secs
for 100M, dynamic]
So in this case, emulating such a construct in user code results in
an inferior version: funnier syntax, and not as efficient.
But a side
effect is that you get +< or <+ (how do you remember which it is) to
act on arrays of ints.
(A reminder that this solution only works for "<",
only for INTs,
and
may not allow mixed comparisions like a <= b < c.)
The trend is now to declare only one thing per line, even variables.
(A very strange feature. print(~) just shows SKIP. Assigning it to an
int stores 1. Assigning to a REAL stores some random value. Assigning
to a string stores "".
At the least I would have expected 0 and 0.0 to be stored for
numbers, ie. 'empty' or 'zero'.)
(My editor also displays 60 lines, not 25.)
On 07/11/2021 11:55, Bart wrote:
In the user code the elements do not form an array. The comparisonsI'm talking about introducing arrays to what appeared to be anAlgol doesn't have "lists" as opposed to arrays, so if we
implementation of chained operators. Are they central to being able
to implement an arbitrary list of such comparisons, or are they there
for a different reason?
want to implement [eg] "+< somearray" to mean a chained comparison
between the elements of the array [similar, apparently to your
"mapsv (('<'), ...)", then of course "somearray" has to be an
array. I still don't see why it's so strange to involve/introduce
arrays to implement an indefinitely-long chain.
are done with dedicated code for each, not using a loop. Inside the
compiler, it could make use of arrays, but the input source and
output code probably won't.
When I make the same comparison in my languages, I get the same
timing, although that's because these simple expressions end up as
the same code. [0.75 secs for 1 billion iterations static, 0.85 secs
for 100M, dynamic]
Or because your version is compiled and optimised, whereas
mine is interpreted and unoptimised.
It's not surprising that an unoptimised interpretation is
"less efficient" than compiled and optimised.
... and a further waste of time writing out further copies
for every possible combination of parameter types. If this had
been a real problem and not a mere proof of concept, I would have
written a shell script to generate the ~1440 [~5000 with your
preferred layout] lines of very repetitive code needed. Yet
again, it really would have been silly.
(A very strange feature. print(~) just shows SKIP. Assigning it to an
int stores 1. Assigning to a REAL stores some random value. Assigning
to a string stores "".
At the least I would have expected 0 and 0.0 to be stored for
numbers, ie. 'empty' or 'zero'.)
It's a "skip" token. If you want 0 or 0.0 then write that,
don't tell the compiler that you don't care and then be surprised by
the result.
"What drink do you want?" "Don't care." ... "Why have
you brought me iced tea, I was expecting white wine."
[...]
(My editor also displays 60 lines, not 25.)
Lucky you. Most of my screens are not tall enough for that
You misunderstood. My compilers for decades have had table-driven[...] I think hard-codedYes, I understand that to be your opinion. But the only
precedences, fixed in the grammar, are better.
rationale you have produced for that is that rogue programmers
can abuse declared priorities. Oh, and that your own compiler
can't handle it.
expression parsers, but with non-alterable priorities. I'm using grammar-driven parsers now as they are clearer, and make it easier to
add specialist handling to some operators (eg. chained comparisons).
Are we inventing languages for rogue programmersBoth mine are for own productivity. That means 90% working on on my
or for productivity?
code instead of 90% battling the compiler.
I don't suffer from the lack of user-defined ops in the stuff I do.
But I /would/ suffer from the lack of many of my features, such as 'tabledata', using a foreign language like yours.
Why does it have to be fixed? Other languages evolve.Then disallow it.It's 46 years too late to change the RR.
OK, my question should have been, how do you /implement/ "+" when "+"
meaning 'add' doesn't yet exist. There must either be some core
BUILT-IN features to make it practical, or "+" is secretly built-in
already.
I was asking how arrays came in it. You said of course there has to
be an array. But not really; you don't need arrays for:
a + b + c
so why do you need them for:
a < b < c
The answer was that your A68 solution needs to use arrays to make it
work even for a handful of scalar values.
And a side-effect of that
was to be able to do < (10,20,30) (even if needing a cast).
This is yet another problem. Your script to generate that lot may be
short, but it would still be 1400 lines of dense code to end up doing
a poorer, slower emulation of something my language can do in 100
non-dense lines because it is natively supported.
You may also need to process all that extra code when a program
doesn't use the feature.
On 12/11/2021 00:59, Bart wrote:
I was asking how arrays came in it. You said of course there has to
be an array. But not really; you don't need arrays for:
a + b + c
so why do you need them for:
a < b < c
??? I don't and didn't. There is no array in [from my OP]
OP +< = (INT i,j) IB: ( i < j | (j, TRUE) | (~, FALSE) )
[which was the operator used in "a +< b < c"].
The answer was that your A68 solution needs to use arrays to make it
work even for a handful of scalar values.
No, there was an array [unsurprisingly] in the definition
of a /monadic/ "+<" designed to check whether the elements of the
array passed as its parameter are in order.
This is yet another problem. Your script to generate that lot may be
short, but it would still be 1400 lines of dense code to end up doing
a poorer, slower emulation of something my language can do in 100
non-dense lines because it is natively supported.
You may also need to process all that extra code when a program
doesn't use the feature.
So I wrote 15 lines to demonstrate a point of principle in
a completely unimportant application [I have much more important
uses of user-defined operators in my real code]; and you wrote
100 lines to implement that unimportant application. Whatever.
None of my [real] programs will ever use that application, so
the 1400 lines will never get written and even less processed.
On 11/11/2021 20:59, Bart wrote:
But I /would/ suffer from the lack of many of my features, such as
'tabledata', using a foreign language like yours.
Well, yes, if you like piggling with compilers and your
own language,
then of course it will have your features rather
than mine. That's no use to me or anyone else here, as your
compiler is both changing under our feet and undocumented, so
it's [as far as we can tell] full of things we don't know about
and so can't use, even if we snaffle your binary.
Why does it have to be fixed? Other languages evolve.Then disallow it.It's 46 years too late to change the RR.
Yes, and either it's a total incompatible mess or else
[more sensibly] the versions are called [eg] "K&R C", "C99", ...
"C2x". The RR is in hard copy. We don't have magic typesetters
to re-write history. If you want an Algol 23, contact IFIP and
offer your services. Good luck in finding enough people with
enough interest. It's more important to me to be able to run
the A68 programs I've accumulated over ~50 years than to write
a somewhat similar language with cosmetic changes and no real
advantages. A68G will see me out; Marcus has added some
interesting things [partial parametrisation and Torrix, amongst
others, but sadly not yet modals] without losing backwards
compatibility.
OK, my question should have been, how do you /implement/ "+" when "+"
meaning 'add' doesn't yet exist. There must either be some core
BUILT-IN features to make it practical, or "+" is secretly built-in
already.
I just find it astonishing that after 50 years of practical use, even
if meagre, no one has ideas for enhancements. What is really that
perfect?
I'd have loads. Another is to replace that 'm OF p' business with the commonly used 'p.m', or allow both. 'm OF p' just scans badly and
tends to bleed into surrounding terms.
Then how would you write a function to add two integers, say, when
"+" does not exist.
But I guess some magic is used here.... no matter what the official definition, practical
On 12/11/2021 21:53, Bart wrote:
[Algol 68:]
I just find it astonishing that after 50 years of practical use, even
if meagre, no one has ideas for enhancements. What is really that
perfect?
There were then, and have been since, lots of ideas for improvement. But you can't change Algol 68 any more than we can
change C99. There's a hint in the name.
I'd have loads. Another is to replace that 'm OF p' business with the
commonly used 'p.m', or allow both. 'm OF p' just scans badly and
tends to bleed into surrounding terms.
See Marcel's book, p60.
If didn't stop there being myriad versions of C, many with the their extensions, which later became official.I just find it astonishing that after 50 years of practical use, evenThere were then, and have been since, lots of ideas for
if meagre, no one has ideas for enhancements. What is really that
perfect?
improvement. But you can't change Algol 68 any more than we can
change C99. There's a hint in the name.
I'd have loads.
Another is to replace that 'm OF p' business with the
commonly used 'p.m', or allow both. 'm OF p' just scans badly and
tends to bleed into surrounding terms.
See Marcel's book, p60.I don't see anything like that on page 60, which seemed to be about
complex numbers. This is that 687-page PDF?
However, I did stumble cross the first program below, which is to do
with Hamming Numbers.
It made quite an impression, because it was so dreadful. Only some of
that is to do with the language; mostly just poor style.
The definition of +:= also seems very dodgy: not just appending, but
doing something to the first part of the array too. (It doesn't need
it; it works fine with a normal append.)
Hamm1.a68 only works for the first test with small numbers. Above$ a68g --help | grep size
that it needs more memory, but I can't remember how that is done
(--help said nothing about it).
On 15/11/2021 14:45, Bart wrote:
If didn't stop there being myriad versions of C, many with the theirI just find it astonishing that after 50 years of practical use, evenThere were then, and have been since, lots of ideas for
if meagre, no one has ideas for enhancements. What is really that
perfect?
improvement. But you can't change Algol 68 any more than we can
change C99. There's a hint in the name.
extensions, which later became official.
Yes, but the later versions of C99 weren't called C99.
Similarly, other versions of Algol were called things like A68R,
A68RS, A68S, A68C and A68G, and doubtless others I've forgotten.
If you want to invent another language called A68Bart, feel free,
but it won't gain much traction
On the other hand, "age of person" reads better than
"person.age". It perhaps dependsnon what you're used to, and
A68 came very early into the field.
It made quite an impression, because it was so dreadful. Only some of
that is to do with the language; mostly just poor style.
Style wars are always unedifying, and are usually caused by unfamiliarity with the language. If you don't like the style, you
can always run the source through a pretty-printer.
Note that the algorithm is well-known, and I would expect
any CS professional to know the problem and [at least] Dijkstra's
algorithm for solving it. There are lots of version on the web.
The one here is [allegedly] derived from a version in Python.
[...]
The definition of +:= also seems very dodgy: not just appending, but
doing something to the first part of the array too. (It doesn't need
it; it works fine with a normal append.)
There is no "normal append" in Algol.
On 17/11/2021 20:56, Andy Walker wrote:
Style wars are always unedifying, and are usually caused by
unfamiliarity with the language. If you don't like the style, you
can always run the source through a pretty-printer.
In this case, it's simply poor.
If you want to invent another language called A68Bart, feel free,I did that in 1981/82! I called it M.
Style wars are always unedifying, and are usually caused byIn this case, it's simply poor.
unfamiliarity with the language. If you don't like the style, you
can always run the source through a pretty-printer.
A pretty printer might change the
layout and indent things more consistently, but it would still leave
those OP definitions in the middle of the algorithm,
or leave alone
this puzzling style of declaration (abbreviated):
SERIES h:=1, UNT m2:=2, INT i:=3;
The puzzle was the use of commas instead of semicolons to separate
distinct declarations.
Even if the language allows it, you need to
consider clarity especially in a textbook aimed at people learning
the language.
* I've dispensed with the extra high precision; it only works withYour code, as given, uses only "LONG INT", which is why
LONG LONG INT
* I've changed MIN to a function
* It doesn't have that funny +:= op
* I've had to use a fixed size array as I couldn't figure out how
FLEX is supposed to work. (It's perhaps not as 'flex' as the name
suggests if you can only assign a whole new array, rather than
incrementally grow an array.)
---------------------------------------------------
PROC min = (LONG INT a,b)LONG INT: (a<b | a | b);
PROC hamm = (INT n)LONG INT:
BEGIN
CASE n
IN 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 # n = 1..10 maps to these #
OUT
[1:2000]LONG INT h; h[1]:=1;
INT hsize:=1;
LONG INT m2:=2, m3:=3, m5:=5;
INT i:=1, j:=1, k:=1;
TO n-1 DO
hsize +:= 1;
h[hsize] := min(min(m2,m3),m5);
IF h[hsize] = m2 THEN m2 := 2*h[i +:= 1] FI;
IF h[hsize] = m3 THEN m3 := 3*h[j +:= 1] FI;
IF h[hsize] = m5 THEN m5 := 5*h[k +:= 1] FI
OD;
h[hsize]
ESAC
END;
FOR k TO 20 DO
print ((hamm(k), newline))
OD;
print ((hamm(1691), newline))
On 18/11/2021 13:25, Bart wrote:
[I wrote:]
If you want to invent another language called A68Bart, feel free,I did that in 1981/82! I called it M.
Mine, from around the same period, was called Portuguese,
being part of a chain starting from Pascal == Albanian, via Belgian,
Chinese, ... and intended to reach Zulu == Algol, each language being
easily derived from its predecessor and replacing misfeatures of
Pascal by features of Algol.
this puzzling style of declaration (abbreviated):
SERIES h:=1, UNT m2:=2, INT i:=3;
The puzzle was the use of commas instead of semicolons to separate
distinct declarations.
Well, you've made it more of a "puzzle" by putting them all
on one line,
You found it unclear only because your conditioned rules
for whether declarations should be separated by comma or semicolon
differ from those of Algol [and probably from those of C]. Matter
of experience, again. Beginners don't share your conditioning or [therefore] your puzzlement.
[... and later:]> I created a version in my own A68 style, shown below.
* I've dispensed with the extra high precision; it only works withYour code, as given, uses only "LONG INT", which is why
LONG LONG INT
you can't get to "hamm(1000000)".
** This "BEGIN ... END" pair is redundant. Matter of style!You clearly don't believe in properly delimiting function bodies so that
CASE n
IN 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 # n = 1..10 maps to these #
** I don't see the point in you [and Marcel] picking out the first 10
cases. They're essentially instant anyway. OTOH, there ought to
be a test that "n >= 1".
My version: [I had seen Marcel's, but not yours when I wrote it. I
use "INC" rather than "+:=", so that indices wrap on reaching "upb".
I haven't bothered to implement faster methods; it's not /that/
interesting a problem, and it's fast enough when n ~ 10^6. May be
worth noting that ~95% of the time [for n ~ 10^6], x2 == x3 == x5,
so there is perhaps a small but real optimisation possible. In case
you ever try this with a real A68 compiler, note that A68G has an
extension whereby "INTs" are lengthened where appropriate. Enjoy.]
=== 8< ====== 8< ====== 8< ====== 8< ====== 8< ====== 8< ====== 8< ===
PR precision=65 PR # enough for n ~ 10^6 #
PROC hamm = (INT n) LONG LONG INT:
IF n <= 0
THEN print (("Error: hamm called with parameter ",
whole (n, 0), " [<= 0]", newline));
stop
ELSE INT upb = ENTIER exp(ln(n)*2/3 + 1); # allows a small margin #
PRIO MIN = 5;
OP MIN = (LONG LONG INT a, b) LONG LONG INT: (a < b | a | b ),
INC = (REF INT i) INT: ( i = upb | i := 1 | i +:= 1 );
[upb] LONG LONG INT store;
store[1] := 1;
INT i2 := 1, i3 := 1, i5 := 1, k := 1;
LONG LONG INT x2 := 2, x3 := 3, x5 := 5;
TO n-1 DO store [INC k] := x2 MIN x3 MIN x5;
( x2 <= store[k] | x2 := 2 * store [INC i2] );
( x3 <= store[k] | x3 := 3 * store [INC i3] );
( x5 <= store[k] | x5 := 5 * store [INC i5] )
OD;
store[k]
FI;
FOR i TO 20 DO print (" " + whole (hamm(i), 0)) OD;
print (( newline, whole (hamm(1691), 0), newline ));
print (( whole (hamm (1000000), 0), newline ))
[...] each language beingMisfeatures?
easily derived from its predecessor and replacing misfeatures of
Pascal by features of Algol.
I rated Pascal above Algol68 (but I was never able to use the latter
then). Because I could understand a Pascal program better, and
therefore could write Pascal more easily. I could also easily see how
things might be implemented.
But I rated Algol68 syntax above Pascal's.
Too many things are lists when they don't need to be.
I also use this pattern (declarations separated by commas), but only
in parameter lists, which are usually written on one line. But I'm
slightly uneasy about it, and might require a semicolon separator at
some point to match similar syntax elsewhere.
I wanted to get away from "UNT" and "PR2" (and also from all those
leading spaces which is a silly way of printing wide numbers).
Hamm(1691) was supposed to be representable in 32 bits, but I got an
overflow with INT; I had to use LONG INT.
To do hamm(1000000), I would have needed not only LONG LONG INT, but
also PR 100; this part seems a little too trial and error.
** This "BEGIN ... END" pair is redundant. Matter of style!You clearly don't believe in properly delimiting function bodies so
that they don't blend into surrounding code. This was one problem (of
many) with Python's indent syntax.
I did try manipulating the list lower abound to keep memory down, but
it made no difference on those. It did allow hamm(100 million) (in 3
mins IIRC) otherwise it would have run out of memory.
On 20/11/2021 16:51, Bart wrote:
[I wrote:]
[...] each language beingMisfeatures?
easily derived from its predecessor and replacing misfeatures of
Pascal by features of Algol.
If you aren't aware that Pascal has misfeatures, that accounts
for a lot! You could start with Brian Kernighan's famous "Why Pascal
is not my favourite language". I don't agree with everything he says,
but it's a good start.
I rated Pascal above Algol68 (but I was never able to use the latter
then). Because I could understand a Pascal program better, and
therefore could write Pascal more easily. I could also easily see how
things might be implemented.
Ie, you were more familiar with Pascal. It shows. If you
had been taught Algol as your first language, you would have found
Pascal v frustrating.
But I rated Algol68 syntax above Pascal's.
Despite not knowing it!
There is no incredibly interesting distinction between, say,
"int i, j, real x" when it's part of a parameter specification, or
when it's part of a structure definition, or when it's a declaration.
So the question is not really whether commas should be allowed, but
why semicolons should be required.
A better question is what is the difference between comma and
semicolon in Algol68?
I use commas to separate list elements, but an element of a list can
be what I think Algol68 calls an /sunit/ (statements separated with semicolons; ie. not a list!).
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 185 |
Nodes: | 16 (1 / 15) |
Uptime: | 135:21:39 |
Calls: | 3,758 |
Calls today: | 2 |
Files: | 11,180 |
Messages: | 3,469,426 |