Chained comparisons A < B < C

76 views
Skip to first unread message

James Harris

unread,
Sep 5, 2021, 6:50:21 AMSep 5
to
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?


--
James Harris

Bart

unread,
Sep 5, 2021, 7:27:20 AMSep 5
to
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.

(I thought I was doing them once, but it seems to have lapsed back to
double evaluation; I'll have to check it out.

Double evaluaton is hard to avoid if doing this by transforming the AST
into that second version. But I now use a special AST form where middle
terms occur only once, so there's no excuse).

As for use-case, I mostly use it with equals:

if A = B = C then

I did use to do:

if A <= B <= C then

but I now tend to have a dedicated op for that which is:

if A in B..C then

So chained comparisons /could/ be restricted to equals only. Except some
will want to do A < B <= C and so on.

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.

Charles Lindsey

unread,
Sep 5, 2021, 10:36:07 AMSep 5
to
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.

Note that

A + B + C
has a defined meaning, but
A + C + B
might be different (one or the other might overflow).

> applied?
>
>


--
Charles H. Lindsey ---------At my New Home, still doing my own thing------
Tel: +44 161 488 1845 Web: https://www.clerew.man.ac.uk
Email: c...@clerew.man.ac.uk Snail-mail: Apt 40, SK8 5BF, U.K.
PGP: 2C15F1A9 Fingerprint: 73 6D C2 51 93 A0 01 E7 65 E8 64 7E 14 A4 AB A5

Bart

unread,
Sep 5, 2021, 12:57:26 PMSep 5
to
On 05/09/2021 15:36, Charles Lindsey wrote:
> 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.

Yes, it a little different. In my case it's more like an N-ary operator,
so that this fragment:

real x, y, z
x < y < z

Generates this AST (one composite operator and 3 operands):

i64------- 1 cmpchain: <op_lt_r64 op_lt_r64>
r64------- - 1 name: x
r64------- - 1 name: y
r64------- - 1 name: z

To do the same thing without the feature, you'd need to write:

x < y and y < z

with this more elaborate AST:

i64------- - 1 andl: <andl_i64>
i64------- - - 1 cmp: <lt_r64>
r64------- - - - 1 name: x
r64------- - - - 2 name: y
i64------- - - 2 cmp: <lt_r64>
r64------- - - - 1 name: y
r64------- - - - 2 name: z

With the problem that y (if an expression) could have side-effects that
may be executed twice, but you'd want to avoid evaluating it twice
anyway, so extra effort.


James Harris

unread,
Sep 6, 2021, 5:40:20 AMSep 6
to
On 05/09/2021 15:36, Charles Lindsey wrote:
> 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).

Either overloaded 'number op bool' or decided that comparison operators
result in an integer (as C does) so that it can be the input to another
comparison against a number.

>>
>> means that B is between A and C? Or should a programmer have to write
>>
>>    A < B && B < C
>
> Yes

Noted. Given that viewpoint what do you make of

https://youtu.be/M3GAJ1AIIlA

?

...


> Note that
>
> A + B + C
> has a defined meaning, but
> A + C + B
> might be different (one or the other might overflow).

Good point. Language expressions are not quite the same as mathematical
ones.


--
James Harris

David Brown

unread,
Sep 6, 2021, 5:55:19 AMSep 6
to
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.

James Harris

unread,
Sep 6, 2021, 6:28:12 AMSep 6
to
On 05/09/2021 12:27, Bart wrote:
> 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.

Until seeing https://youtu.be/M3GAJ1AIIlA (which is the same link I
posted in reply to Charles) I never realised that there was a simple and
logical way to interpret operator sequences but it appears I am well
behind the curve. You do that, and so did BCPL in 1967!

But there's a question of relative precedences.

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?

...

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

Now I know what the expression means I find it surprisingly easy to read
- just adding "and" between parts and knowing that all parts must be
true for the composite comparison to be true makes any uses of it easy
to understand.


--
James Harris

Bart

unread,
Sep 6, 2021, 6:41:26 AMSep 6
to
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

David Brown

unread,
Sep 6, 2021, 7:24:50 AMSep 6
to
On 06/09/2021 12:41, Bart wrote:
> 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!]
>

I've been interspersing with a few agreements...

>
>> 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:

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

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

If B is complicated, you can always use a local variable - split things
up to make them clear. (Of course, you want a language that has proper
scoped local variables for that - but you'd want that anyway.)

Bart

unread,
Sep 6, 2021, 7:25:25 AMSep 6
to
On 06/09/2021 11:28, James Harris wrote:
> 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?

Exactly the same as a single comparison operator. All comparisons are
the same precedence (see below), so:

A + B = C = D + E is (A+B) = C = (D+E)

A and B = C = D and E is A and (B=C=D) and E


> 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), !, ~.

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

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


Dmitry A. Kazakov

unread,
Sep 6, 2021, 8:02:51 AMSep 6
to
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.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de

James Harris

unread,
Sep 6, 2021, 8:20:26 AMSep 6
to
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.

>
>
>
>
>> 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), !, ~.

I am not copying C!

I could have chosen to do so, of course. But some of it's operator
precedence levels are in the wrong order. Even DMR acknowledged that.

If one is not going to copy C's why decide to copy any of it? If some
precedences are going to be different then why not just do whatever's best?

...

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

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.

:-(


--
James Harris

David Brown

unread,
Sep 6, 2021, 8:25:27 AMSep 6
to
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.

James Harris

unread,
Sep 6, 2021, 9:22:51 AMSep 6
to
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
>>
>> ?
>>
>
> 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 don't see this as that special a syntax but (at least, potentially) an
expression form which is widely useful and easy to understand. That is,
of course, once one knows the rules(!) but the rules look as though they
could be easy to grasp: For a series of comparison ops in a phrase such as

A op B op C op D

the ops would apply left to right, with inner expressions being
evaluated as they were encountered, and result in True if all
subcomponents were true; if any were found to be false then the rest of
the phrase would not be evaluated. Maybe I'm missing something but that
seems remarkably simple.


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

Noted.

One thing in favour of the chained operators (other than their
simplicity once one knows the rules) is single evaluation. In

A < sub(B) <= C

would evaluate sub(B) once.

I am not decided yet on whether to support this but I do note that it
was present in BCPL in some form in 1967 so it's not a new concept and I
find it surprisingly easy to read - just say "and" between parts! :-)


--
James Harris

Dmitry A. Kazakov

unread,
Sep 6, 2021, 9:39:09 AMSep 6
to
But given a user type you want to be able to have arrays, vectors
matrices indexed by the type. You want standard loops working with it
etc. The usual OOPL method is to manually create a dozen of helper
classes that producing a total mess and still no support of slices,
submatrices etc. I would like some integrated mechanics for this stuff.

Bart

unread,
Sep 6, 2021, 9:46:07 AMSep 6
to
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:
>>> 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.

That's just because that same example was used in previous posts.

For parsing purposes, then = <> < <= >= > are all treated the same.

>>> 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!

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:

sin x + y

means (sin(x)) + y not sin(x+y).)

>

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

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

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:

a + b * c

means (a+b) * c, contrary to school arithmetic rules.



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


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


See above.

>
>
>>
>> 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.
>
> :-(

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

means -(P^). (^ is really syntax, not an operator, but it follows the
same rules.)

Since P here needs to be a pointer, negating it first wouldn't be useful.


Bart

unread,
Sep 6, 2021, 10:06:16 AMSep 6
to
On 06/09/2021 13: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.

I guess my dynamic language has a powerful type system then!

".." is not quite an operator, but a constructor which takes two
integers and yields a range type.

And a range is a proper first class type:

a := 10..20
println a.type # 'range'
println a.lwb # 10
println a.upb # 20
println a.len # 11
println a.bounds # 10..20
println a[10] # 10 (index must be 10..20)
println a.isrange # 1 (true)
println 13 in a # 1 (true)

b := new(list, a)
println b.bounds # 10..20

for i in a do
println sqr i # 100 to 400
od

In my static language however, then ranges are mostly syntax.


James Harris

unread,
Sep 6, 2021, 1:03:48 PMSep 6
to
On 06/09/2021 14:46, Bart wrote:
> 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:

Indeed, exceptions are not good. I wrestled with the
minus-exponentiation combo for a long time before setting on the
arithmetic group having its operators in the following order (high to low).

**
+ - (prefix)
* /
+ - (infix)

Note that I have attempted to be consistent between groups, where there
are corresponding operations. The bitwise group (currently) similarly
has shifts before bitnot. So

! a >> b

means

! (a >> b)

The order corresponds with the minus-exponentiation combo in the
arithmetic group.

Incidentally, in another case of not following C I use ! for bitnot.
That's because it is used to make other composite symbols with the same
meaning of unary negation and I preferred to try to be consistent. For
example,

! bitwise not
& bitwise and
!& bitwise nand

C's ~ didn't look good:

~&

...

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

Certainly! It would be far too confusing for a human to parse if the
other of evaluation changed for different types.

But one can still have bitwise not below bitwise shift, unary minus
below exponentiation, and boolean 'not' below comparisons which yield
False or True.

...


>>> 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?

Yes, the bitwise operations are all in the same group. It has: shifts,
not, and, xor, or in that order.

>
> But I don't believe this is the complete set of precedence levels,
> unless "+" has the same precedence as "*" so that:

What I listed was the families. Plus and times (+ and *) are both in the
'arithmetic' group. And times comes before plus.

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

I have that as

if not A in B

because 'in' comes before 'not'.

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

Well, the basic precedences have been in place for some time but if I
added chained comparisons then they (the chained comparisons) would be
treated as a composite subexpression of the same precedence as any other
comparison.

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


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

Because arithmetic comes before comparison I would parse that as

if (N + 1) <= Limit

...

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

I agree with much of that. Postfix operators come first. But I don't put
all prefix operators so high.

Consider

- a & b ;i.e. negate (a bitand b)
- a ** b ;i.e. negate (a to the power of b)
not a & b ;i.e. boolean not of (a bitand b)



>
> In practice it works intuitively; you mustn't over-think it! So:
>
>   -P^     # ^ is a deref op

Mine does that. Of higher precedence than bitwise ops I have the
'operand' group which includes dereference. So I would have dereference
before unary negation.

The operand group essentially has

( invoke subprogram
[ array or slice
. member selection
& inhibit auto dereference
* dereference

All of the operators in that group are postfix, all have highest
precedence and all, therefore, associate left-to-right.



--
James Harris

James Harris

unread,
Sep 6, 2021, 3:11:00 PMSep 6
to
On 06/09/2021 12:24, David Brown wrote:
> 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...

Very lax. :-)

...

> 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,

I don't see the chaining of comparisons as only for range tests. AISI
where each 'op' is a comparison operator

A op B op C op D

simply means that all component parts have to be true for the whole
phrase to be true. In words,

(A op B) and (B op C) and (C op D)


> but if it is
> used for array slices, for-loops, etc., then you have a good feature.

I don't understand that. How can chaining be used for array slices, for
loops, etc?

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


--
James Harris

Bart

unread,
Sep 6, 2021, 4:52:32 PMSep 6
to
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.)

David Brown

unread,
Sep 6, 2021, 5:24:48 PMSep 6
to
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". Some characters, such as parenthesis, are
thin enough that they provide their own visual space, and sometimes you
/do/ want to tie elements together closely and thus skip the space. The
details of a good spacing system are complicated, but I have no doubts
that the readability of Bart's code would be improved with more spaces.

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.

James Harris

unread,
Sep 7, 2021, 3:19:02 AMSep 7
to
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.


--
James Harris

David Brown

unread,
Sep 7, 2021, 4:09:38 AMSep 7
to
"The TeXbook", by Donald Knuth.

"LaTeX : A Document Preparation System", by Leslie Lamport

These are not the smallest, cheapest or easiest introductions to TeX or
LaTeX if you want to use these systems - there is endless information
available for free online, as well as tutorial books, guides for more
specialist use in typesetting, etc.

But they are written by the authors of TeX and LaTeX respectively, and
give an understanding into the philosophy and technical details of
typesetting and good structured document layout. It is not just about
saying /how/ you make spaces of different sizes and stretchiness, but
saying /why/ you should do so, and when to use them.

James Harris

unread,
Sep 9, 2021, 12:52:49 PMSep 9
to
On 06/09/2021 21:52, Bart wrote:
> 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,

Would they? Couldn't the same be achieved by allowing some of the
unaries to have lower levels - so that the issues you go on mention
wouldn't arise?

>
> 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 ?

Yes, I have bitwise operations before arithmetic operations.

One could make a good case for prohibiting the combination of arithmetic
and bitwise operators because the first works on numbers and the second
works on bit patterns. The two do not match unless you require a mapping
between numbers and putative representations. But /if/ one decides that
they can be combined then one has to decide how they relate.

The worst place to put manipulation of bit patterns, ISTM, is between
arithmetic and comparisons. That's because arithmetic /produces/
numbers, while common comparisons (e.g. less-than) consume those
selfsame numbers. Bit patterns between those two would convert from
numbers to bit patterns then back to numbers. :-(

The only place left, /if/ one is to allow them to be combined, is to
carry out bitwise operations before doing arithmetic, and that's where
I've put them.

>
>
> I really don't like precedences, or having to remember them,

Nor me. But I found that keeping operators in families made their
ordering much easier to remember.

> 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

That's interesting. You've taken a structural approach. I took a
semantic approach. IMO neither is provably best; there are options
rather than perfect solutions. The best possible is probably a
combination of convenience and memorability.

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

Yes. I think that when explaining precedence levels it might be best to
start with the arithmetic operators and work up and down (in terms of
precedence level) from there.

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

I recognise the dilemma. See above for where I ended up.

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

I, too, have operators to fit in such as

Bitwise concatenation
Range specifiers


--
James Harris

James Harris

unread,
Oct 23, 2021, 10:40:03 AMOct 23
to
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
>>
>> ?
>>
>
> 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,

If this were about just checking whether a value was in range I would
say the same.


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

Nor me.

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

Interesting that you should say that. For me, the main appeal of chained
or composite comparisons is that they can make code /easier/ to read.
Yes, there are ranges - with or without inclusivity as in

low <= v <= high
0 <= index < len

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.


--
James Harris

James Harris

unread,
Oct 23, 2021, 10:42:54 AMOct 23
to
On 06/09/2021 11:41, Bart wrote:
> 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

True.

>
> * 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

Two very good points!


--
James Harris

Message has been deleted

Bart

unread,
Oct 26, 2021, 8:15:12 PMOct 26
to
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])

Andy Walker

unread,
Oct 28, 2021, 3:37:09 PMOct 28
to
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. It's neater
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. But again
whether it's worth doing [whether at all, or with restrictions]
is another matter. I doubt it; but it makes a nice little
programming exercise.

--
Andy Walker, Nottingham.
Andy's music pages: www.cuboid.me.uk/andy/Music
Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Boccherini

Bart

unread,
Oct 28, 2021, 8:03:51 PMOct 28
to
Well, this works, for your examples.

But I think that with working on arrays, that's straying from what this
is about; that is more like list operators, where you apply a designated
operator between all elements of the array to get a single scalar result.

I may have mentioned the way I approach this, which is to consider a
chain of related operators as a single n-ary operator.

This can be implemented by transforming A op1 B op2 C into (A op1 B) and
(B op2 C), but I now treat it as a single operation, with a composite
operator and multiple operands.

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.

(But I don't have list ops, which is a different subject.)


> ====== 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 what it actually means is another! I can't relate it what you write
below ...

  It's neater
> and [perhaps] more readable than
>
>   IF a > 0 AND a <= 100 AND b > 0 AND ...

... which I'd anyway write as:

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!


> but I've managed without over several decades of programming and
> can't claim ever to have missed it.

I wouldn't like to go back to whatever I was using in the mid-80s...

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

I've just done it; it took 3 minutes, and some 8 lines of code. So
you're right, it was harder by requiring some extra effort and mode
code, but it probably saves some head-scratching later.


Bart

unread,
Oct 28, 2021, 8:10:00 PMOct 28
to
On 29/10/2021 01:03, Bart wrote:

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

Well, sort of. I thought I better try. It appears to work, but it
converts all the operands to a common, dominant type. So A=B=C could
have different semantics compared with A=B and B=C, as the former could
use all floats; the latter might use int for A=B and floats for B=C.

Dmitry A. Kazakov

unread,
Oct 29, 2021, 2:27:53 AMOct 29
to
On 2021-10-28 21:37, Andy Walker wrote:

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

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)

A declarative language would have to keep expressions apart from
declarations anyway.

Andy Walker

unread,
Oct 29, 2021, 3:29:58 PMOct 29
to
On 29/10/2021 07:27, Dmitry A. Kazakov wrote:
> 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,

Sure. But once you've added operators to do chained
comparisons, it's harder to prevent "a < b > c" than just to
accept it; it's not as though the meaning is unclear. [OTOH,
I suppose it could be a typo, but OTTH I can't imagine using
any of this in my own programming.]

> rather
>    a,c < b

This is OK as maths, but in many computing languages
it's syntactically ambiguous. In Algol, you'd need to
decorate it something like "[]INT (a,b) < c" plus writing
the [newly extended] operator. Again, it all seems like a
lot of trouble to avoid "a < b AND c < b", or, if "b" is a
complicated expression "(INT b = some mess; a < b AND c < b)".
Note that "a < b >= c" is slightly harder to re-cast, tho' it
comes free with the chaining operators.

> while
>    a < b < c < d
> is pretty common to define an ordered set.

Again, this drops out with no extra work once the
chaining operators are written; equally again, it's not
sufficiently common in programming to justify much work
being done for it, and esp not to justify extra syntax.

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

--
Andy Walker, Nottingham.
Andy's music pages: www.cuboid.me.uk/andy/Music
Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Chwatal

Dmitry A. Kazakov

unread,
Oct 29, 2021, 3:44:47 PMOct 29
to
They can be well used for many other purposes starting with cycles

for I in 1..200
for I in (a,b,c,d)

while chained operations have no use at all, except for renaming an
object like b in

a < b < c

Yet it is too specific to serve numerous other cases one could need a
renaming:

b = b + c

I would expect at least that, e.g. in the evil spirit of C:

b += c
a < b <&& c

(:-))

Andy Walker

unread,
Oct 29, 2021, 7:18:30 PMOct 29
to
On 29/10/2021 01:03, Bart wrote:
>> [...] 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 what it actually means is another! I can't relate it what you write below ...
>>   It's neater
>> and [perhaps] more readable than
>>    IF a > 0 AND a <= 100 AND b > 0 AND ...
> ... which I'd anyway write as:
>    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,
but I don't expect any ideas you or I or even Dmitri may have
to propagate any time soon to C or Fortran or Pascal or ....
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!

>>> 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.
> I've just done it; it took 3 minutes, and some 8 lines of code. So
> 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". I made the last bit up, but it's the impression
I often get from patronising user manuals and textbooks.

--
Andy Walker, Nottingham.
Andy's music pages: www.cuboid.me.uk/andy/Music
Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Chwatal

Bart

unread,
Oct 30, 2021, 7:32:56 AMOct 30
to
On 30/10/2021 00:18, Andy Walker wrote:
> On 29/10/2021 01:03, Bart wrote:
>>> [...] 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 what it actually means is another! I can't relate it what you
>> write below ...
>>>                               It's neater
>>> and [perhaps] more readable than
>>>    IF a > 0 AND a <= 100 AND b > 0 AND ...
>> ... which I'd anyway write as:
>>     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.]

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

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

See above. On the subject of C, see also C++.

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

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

I've switched machines so DAK's posts are visible again; but he suggests
syntax like this:

for I in 1..200
for I in (a,b,c,d)

which is exactly what I use (with the addition of 'do'). The first works
in both of my languages; the second only in the higher level one. The
meanings are obvious.

But, probably half of current languages have copied C's brain-dead
for-loop syntax for that first example:

for (i = 1; i <= 200; ++i)

(More typically, 0..199 for such languages as they tend to be 0-based
too, /and/ case-sensitive.)

What is the matter with people?! This is the compiler's job to know how
to implement iteration, not yours!



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

(Really? I feel fortunate now in sharing my pdp11/34 with only 1-2 other
people!)

> 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!

You've illustrated perfectly why I still prefer using my own languages!

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

>>>> 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.
>> I've just done it; it took 3 minutes, and some 8 lines of code. So
>> 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.

That would just be a Note: you can only mix '= < <=' or '= > >='.

(But don't try to explain further or you'd get tied up in knots.)

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

It can be to avoid confusion and to be keep things readable.

Dmitry A. Kazakov

unread,
Oct 30, 2021, 8:22:59 AMOct 30
to
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). When you move "for every x" to the leftmost
position you reduce in to a proper operation "and". The macro rule is:

for every x in (y, ...) do z --- expand --->

--- expand ---> (x do z) and (for every x in (...) do z)

for every x in () do z --- expand ---> True

You could also have "exists" quantification:

for any x in (y, ...) do z --- expand --->

--- expand ---> (x do z) or (for any x in (...) do z)

for any x in () do z --- expand ---> False

> So this is a sort of red herring when talking about the benefits of
> chained compares.

It is actually a macro as well that expands

LESS(a,b,c)

into proper operations a < b and b < c. It is possible to "overload"
LESS with < to keep the syntax same for both. Then you resolve the
conflict since for

1 < 2 < 3

there is no Boolean < int and you decide in favor of LESS. But in

1 < 2 and x

there is no LESS and x and you decide for comparison. But then you could
not have ordered Booleans.

> I've switched machines so DAK's posts are visible again; but he suggests
> syntax like this:

Yes, my own machines hate me too! (:-))

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

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

Bart

unread,
Oct 30, 2021, 12:36:50 PMOct 30
to
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)

which is a list of bools when op is "<". However you might want to take
that further and make it:

x op a and x op b and x op c

to yield a single bool result. But the original has a chain of related
ops. Maybe that one should be equivalent to either:

(0 < a <= 100, 0 < b <= 100, 0 < c <= 100)

Or the same collapsed down to one bool. This suggests there would be a
choice between those possibilities. It also requires that where this is
a mix of scalar and vector terms, the scalar terms should be treated as
a repeated vector of that term.

But then, you will have other kinds of operators where that doesn't make
sense.

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.

Dmitry A. Kazakov

unread,
Oct 30, 2021, 3:50:03 PMOct 30
to
On 2021-10-30 18:36, Bart wrote:
> 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.)

If you mean vectors, they are not comparable. But yes, this is a reason
why am against such things. To me (a,b,c) is an aggregate that can be of
any user-defined type, which then determines the meaning of <.
Everything is overloaded and everything is typed.

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

Yes, if op is *, () is vector, x is scalar. Then

2 * (3,4,5) = (6,8,10)

> A vector- or list-processing language needs careful design.

Just provide user-defined types, user-defined aggregates etc. The
programmer will sort things out. Less you wire in the syntax, easier it
would be for everybody.

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

Yes, for that reason you either use an alternative notation, e.g. | is
frequently used to separate alternatives:

case X is
when 1 | 4 | 40 =>
Do something
when ...
end case;

Or you use quantifiers as Andy did:

forall x in (a, b) exists y in (c, d) such that x*y >= 100

removing free variables x, y:

all(a, b) * any(c, d) >= 100

The last step:

(a and b) * (c or d) >= 100

Again, not that I advocate for this stuff.

Andy Walker

unread,
Oct 30, 2021, 5:10:56 PMOct 30
to
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.

> 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?

That may be an ambiguity in your own mind, but when you
write your program you will have to decide what it means, and
there is no reason why what you decide has to be the same as
what Dmitri and I decide.

> So this is a sort of red herring when talking about the benefits of
> chained compares.

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, I would like to take some credit for this,
but I really can't, and expect Steve Bourne was much more of an
influence!]

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

[...]
> C syntax was already chaotic, it can't get much worse!

K&R C syntax totals about 5 1/2 sides in a manual of
31 sides. Compare the latest standard and weep. Then look
at the C++ standard, and weep some more. Then look at the
debates in "cmp.std.c" and weep more again. For comparison,
the Algol syntax chart in "Algol Bulletin" is one side, tho'
admittedly it doesn't include formats [an Algol botch, that
would surely have been removed had Algol ever been revised
after looking at Unix], another half side.

> 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. Sadly, that has been the
norm ever since it became standard practice for programmers
to write their own languages, loosely based on C or some
other currently popular language, write a paper or a web
page about the result, and sit back and wait for some of
them to thrive [usually for not-very-good reasons].

[...]
> But I couldn't use A68; I'd be constantly fighting the syntax and
> tyope system.

That's because you haven't read the RR!.

> And switching between capitals and lower case all the
> time...

That's up to you. Any full implementation should
allow you the choice between upper-case stropping, reserved-
word stropping, prefix-apostrophe stropping, prefix-period
stropping and [perhaps] lower-case stropping, also a choice
of character sets [all this long before it became normal to
send programs around the world electronically, so that the
"internationalisation" effort became important]. I often
write code snippets for here in reserved-word, as it makes
the code look a bit more friendly. It's straightforward
[-ish] to write preprocessors that convert between regimes.

[...]
>>      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.
> That would just be a Note: you can only mix '= < <=' or '= > >='.

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. But "Notes" aren't what
a User Manual is about, though it can be useful to put
supplementary information into a footnote, comment or example.
The Manual is for the Truth, the Whole Truth and Nothing But
the Truth. If your language allows you to mix some operators
but not others, then the syntax should reflect that [which,
in the present case would make the syntax more complicated].

> (But don't try to explain further or you'd get tied up in knots.)

How patronising!

> It can be to avoid confusion and to be keep things readable.

Readability and confusions of user programs are not
to do with the User Manual but with style and good usage.
By all means put appropriate comments in footnotes, but the
Manual is the place where the actual syntax and semantics
of the language are "officially" defined, not where the
proles are told what the High Priests deign to allow them
to know. Some of the proles may well be better and more
experienced programmers than the High Priests.

--
Andy Walker, Nottingham.
Andy's music pages: www.cuboid.me.uk/andy/Music
Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Byrd

Bart

unread,
Oct 31, 2021, 6:52:43 AMOct 31
to
On 30/10/2021 22:10, Andy Walker wrote:
> 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.

I don't buy that. You will confuse yourself, and anyone who tries to
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. A further step, eg. like DAK's and() macro, would
be needed to combine those results into one:

andv(mapsv(('<'), 0, (a,b,c)))

But these work on two operands at a time. I don't know why people are
trying to compound a mildly interesting extension called 'chained
comparisons', with advanced list processing on the lines of APL.

One represents one tiny detail my language design, the other dwarfs the
whole language.


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

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?

>
> [...]
>> 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.

Examples? I can't think of any Algol features in C, unless you're
talking about very early days.


> [In view of discussions
> with Brian Kernighan,

I guess you didn't bring up type declaration syntax or printf formats!

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

A decade ago I was playing with 4 kinds of dispatch loops inside
interpreters. They were based on:

Function-tables, switches, label-tables and assembly+threaded code.

These are in increasing order of performance. For some reason which I
don't quite get, using a table of label addresses was faster than
switch. In fact, the fastest dispatch method using pure HLL code.

This required the host language to have label address and label variables.

At that time, CPython for Linux, which used gcc, relied on label-tables
to get an extra bit of performance. But CPython for Windows, which was
built with MSVC, didn't have that, because MSVC didn't have that extension.

So CPython on Windows was a bit slower than on Linux, because someone
decided that label pointers weren't worth having.


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

Lots of opportunity to get C fixed. But people who like C aren't
interested; every terrible misfeature is really a blessing!

In the 70s, Fortran also had formats of sorts for printing values. The
lastest Fortran however lets you write:

print *, a, b, c

Meanwhile C still requires you to do:

printf("%lld %f %u\n", a, b, c);

or something along those lines, because it depends on the types of a, b
and c. Some languages are better at moving on!


>>>      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.
>> That would just be a Note: you can only mix '= < <=' or '= > >='.
>
>     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.

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

Dmitry A. Kazakov

unread,
Oct 31, 2021, 7:45:39 AMOct 31
to
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.

Bart

unread,
Oct 31, 2021, 10:32:45 AMOct 31
to
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.)


> A cleaner way would to define a function like
>    All_Same (List, Ignore)  -- Compare list elements ignoring one

This is overkill for just 3 (or 4) elements instead of two.

100 elements, then yes you'd use list processing and other techniques,
since the elements wouldn't be individually enumerated anyway.


> Otherwise, you can do that in Ada, no problem:

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.

My version of it probably added 100 lines of straightforwards code to
the static code compiler (and 60 to the dynamic one). It also works for
arbitrary types and other comparison operators.

You don't need overload features, nor do you have implement the feature
yourself.

Dmitry A. Kazakov

unread,
Oct 31, 2021, 11:43:39 AMOct 31
to
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.

> The problem is that this is all quite complicated, and requires some
> extra skills, especially if you want to work with any types.

If you do that frequently you have the skill, if you do not, then you do
not need it.

> I favour building it into a language. That also requires special skills,
> but that is only required of the implementer.

No, it makes the language unsafe. Because the chances are 99% that a=b=c
is just an error. So I prefer the language rejecting it straight away
without consideration of any types or operations involved.

Bart

unread,
Oct 31, 2021, 12:11:13 PMOct 31
to
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.

The comptype codes 1/2/3 mean Y/U/V, and this tests they are in that
order and for those channels.

Here I only handle 3 channels, Y/U/V, with certain sampling combinations.

---------------------------
function loadscan(fs,hdr)=
initbitstream(fs)

(vsample1,vsample2,vsample3) := hdr.vsample
(hsample1,hsample2,hsample3) := hdr.hsample
(comptype1,comptype2,comptype3) := hdr.comptype

pimage := nil
case hdr.ncomponents
when 1 then
abort("loadmono")
when 3 then
if comptype1<>1 or comptype2<>2 or comptype3<>3 then
abort("comptype?")
fi
if hsample2=vsample2=hsample3=vsample3 and
hsample1<=2 and vsample1<=2 then
pimage := loadcolour(fs,hdr,hsample1,vsample1)
else
println hsample1,vsample1,hsample2,vsample2,hsample3,vsample3
abort("Unknown sampling")
fi
else
abort("ncomp")
esac

return pimage
end

Dmitry A. Kazakov

unread,
Oct 31, 2021, 12:59:41 PMOct 31
to
On 2021-10-31 17:11, Bart wrote:
> 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.

Then you do need separate checks in order to give meaningful error
message on what exactly is wrong. Unless you missed something, because
it is unusual to have that much redundancy without any use.

Andy Walker

unread,
Nov 1, 2021, 7:00:33 PMNov 1
to
On 31/10/2021 10:52, Bart wrote:
[I wrote, re Algol:]
>> [...] 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.
> I don't buy that. You will confuse yourself, and anyone who tries to
> 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.

> Some things I think needs to be defined by the language.

Again, the things that it is normal to define are perfectly
well defined in Algol. It's just easier to use existing syntax on
new operators and functions than it is in most other languages.

> 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"? Is it not your own language, entirely under your
own control and providing what [and only what] you yourself support?

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

>> [...] Each new [C] standard thus
>> far has added something from Algol, and AFAIR nothing that is
>> Algol-like has ever been taken away.
> 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.

[...]
>>> 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.
> Lots of opportunity to get C fixed. But people who like C aren't
> interested; every terrible misfeature is really a blessing!

You can't fix "break" or "a*b"; by the time of 7th Edition,
it was already too late, there were thousands of users and hundreds of
thousands of programs, and too much would have broken if you changed
such fundamental syntax. So almost all changes since have been either
new facilities that could not have been written in correct earlier C,
or a tightening up on things that were previously ill-defined. Again,
this is a difference between private languages and major ones. If you
want a reasonable change in C, the way to achieve that is to implement
it in "gcc" or similar, show that it works without breaking anything,
and put it to the committee. Most grumblies aren't prepared to put in
the hard work and/or don't understand the problems with their ideas.

[...]
>>      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.
> 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?

--
Andy Walker, Nottingham.
Andy's music pages: www.cuboid.me.uk/andy/Music
Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Hummel

Bart

unread,
Nov 1, 2021, 9:38:59 PMNov 1
to
On 01/11/2021 23:00, Andy Walker wrote:
> On 31/10/2021 10:52, Bart wrote:
> [I wrote, re Algol:]
>>> [...] 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.
>> I don't buy that. You will confuse yourself, and anyone who tries to
>> 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/.

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

It reminds me of posts in comp.lang.c where people claim some new
feature is not necessary. because it so 'straightforward' to
half-implement some terrible version of it via impenetrable
meta-programming.


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

It could well be clearer, AFTER you've had to implement it via code 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 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.

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.

>> 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"?

They are implemented as user-functions which are placed in a library
that comes with the language.


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

Then the rules for that removes any confusion about the result:

mapsv(op, A, B): scalar op vector -> vector



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

-------------------------
global function mapsv(op,as,b)=
#Apply op or function between elements of single value as and vector b
c::=makeempty(b)
forall i,x in b do
c[i]:=mapss(op,as,x)
od
return c
end
-------------------------

(Hmm, maybe 's' stands for 'single' not 'vector'!)

mapss is an actual built-in function which is awkward in user-code,
since 'op' can be any built-in binary op, or any user-function taking
two parameter.

The language has poorly developed operator overloading which is little used.

I don't think you can write my mapss() function in Algol68. Example:

a:=12
b:=20

println mapss( (random(1..4)|(+), (-), (*)| (/)), a, b)

Output is 240.

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

OK, but its implementations of those are crude and remain low level.

People mainly use them to write poorer code that is harder to follow.


> You can't fix "break" or "a*b";  by the time of 7th Edition,
> 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.

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

I copied the feature from Python. You'd need to ask Guido what it means!

> 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?

If I write:

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


Bart

unread,
Nov 2, 2021, 3:01:24 PMNov 2
to
On 02/11/2021 01:38, Bart wrote:
> 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.)

I think this is how it works in the 'K' programming language (a
derivative of APL):

0 < a b c

This prodouces a vector. To combine the bools into a single value, is
possibly done like this (I don't have a version to try):

& 0 < a b c

I don't know about precedences (except it works right to left iirc). On
mine, it would not be hard to define 'and' (&) to work as a unary
operator, which is expected to be a list:

and (a, b, c)
and L # where L is a list

Doing this for "<" is trickier:

0 < (a, b, c)

because while this example seems obvious, it could also look like this:

x < L

Here, x could be "C" and L could be "ABCDE", so that "C" < "ABCDE" is
false (comparing two strings). But you might want L to be treated as a
list of one-character strings, so that the result is (0, 0, 0, 1, 1).

I don't know how K sorts this out. Using my mapsv however:

x := "C"
L := "ABCDEF"
print mapsv((<), x, L)

This correctly shows (0,0,0,1,1). So it might be ugly, but you have
better confidence that it does what you want.

The ugliness can be mitigated a little via a macro:

macro lessv(a,b) = mapsv((<),a,b)

print lessv(x, L)

James Harris

unread,
Nov 3, 2021, 11:58:23 AMNov 3
to
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?

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

When you see a chained comparison do you try to read it as a single
expression? Yes, one can if the operators are all in the same direction,
as you indicate, but maybe in general it's better to read the
comparisons as individual operations.

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

and if so, why not just read the former as the latter?

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

It would seem inconsistent to allow

a == b == c

and yet prohibit

a != b != c



--
James Harris

James Harris

unread,
Nov 3, 2021, 1:06:13 PMNov 3
to
On 01/11/2021 23:00, Andy Walker wrote:

...

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

Not sure about that. Defining new operators is probably a very poor
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.


--
James Harris

Bart

unread,
Nov 3, 2021, 1:54:14 PMNov 3
to
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). So that
does form a sort of weak pattern, one that DAK might exploit as:

(x, z) < y

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

Dmitry A. Kazakov

unread,
Nov 3, 2021, 3:56:14 PMNov 3