Why no logical ==?

Why did Fortran not overload == or .eq. as a binary operator between logical items instead of .eqv.? And for that matter why not /= instead of .neqv. ?

Doctor Fortran in “To .EQV. or to .NEQV., that is the question”, or “It’s only LOGICAL” - Doctor Fortran (stevelionel.com)

2 Likes

Not answering the why question, but if using == and /= between booleans is desired, one can use the M_overload module of @urbanjost.

1 Like

In case the full version with things like allowing strings that represent numbers in int, dble, … is
too much, the abridged version:

module logical_ops
   implicit none

   interface operator(==)
      module procedure boolean_equal
   end interface 
   interface operator(/=)
      module procedure boolean_notequal
   end interface 

contains
   logical function boolean_equal(L1, L2)
      logical, intent(in) :: L1, L2
      boolean_equal = L1 .eqv. L2
   end function boolean_equal

   logical function boolean_notequal(L1, L2)
      logical, intent(in) :: L1, L2
      boolean_notequal = L1 .neqv. L2
   end function boolean_notequal

end module logical_ops

program testit
   ! ifort/ifx allow this as an extension, so use "ifx -stand=f90" to see the effect
   use logical_ops
   write (*, *) merge( '== passes', '== fails ', (.true. == .true.) )
   write (*, *) merge( '=/ passes', '=/ fails ', (.true. /= .false.) )
end program testit

Just curious, why use merge() here? Why not the simpler L1 .eqv. L2?

1 Like

No reason other than started from another piece of code and did not think to remove it. Good point. Will change it.

That looks much better! Thanks.

The fact that L1 == L2 is somehow ill-defined in Fortran is utterly deranged. Why can’t this be fixed?

Not deranged at all. The internal value of a logical is not defined by the standard. Two logicals could have different internal values and still be both .TRUE. or .FALSE… There’s a reason .EQV. exists.

1 Like

But who would write a compiler like that? Does such a compiler currently exist? Why not standardize what most (basically all) users would expect? If they are both true then == equates to true, etc.

This is about not breaking old, badly written code, isn’t it?

Re: “Why can’t this be fixed?” well, there is no logical explanation for it!!!

Re: the blog linked upthread, is the following blurb logical?

The way many Fortran programmers would naturally do this is as follows:

IF (LOGVAL1 .EQ. LOGVAL2) …

but the results of this can vary depending on the internal representation.

It will be interesting to come across the processor that has different internal representation for LOGVAL1 and LOGVAL2!

This is the thing that has always bothered me, even before f77. Life would be so much simpler if the standard had long ago defined .false. to be 0 and .true. to be 1, and then allowed those values to be used as integers in arithmetic expressions and as array indexes.

As for compilers having many-to-one mappings from internal values to the two logical values, that is what the C language has always done and will probably always do. As discussed in the Doctor Fortran article above, in C an integer zero is false and any other integer value is true. This was all standard practice before bool was added to the language decades later.

Oddly, the convention with return codes in shells is the opposite. A return code of 0 means success, and a nonzero return code is a failure. In shell scripts, expressions like prog1 && prog2 && prog3 do what you expect them to do, but the underlying return codes are the opposite of the C convention.

If x and y are logical variables, a Fortran compiler could say x == y is .false. if x and y have different KINDs even if they are both true or both false. (Since x == y is nonstandard, the compiler could do anything.) If the natural interpretation of x == y is standardized, some compilers in standard-conforming mode could be forced to change what a code does, and the committee may wish to avoid this.

Since the language allows for overloading the == and /= operator to act on LOGICAL values as intuitively expected, it seems hard to argue it cannot be fixed; and it is a common extension to allow it. It does seem what is currently standardized should be relegated to requiring a compiler switch; especially since the internal bit representation is not defined by the standard. I think that means any old code exploiting the current state would by definition (well, I guess by lack of definition) be non-portable.

Is there an example of what would break if the behavior defined by the example overload were standard?

I think it was CDC and early COS Cray machines that had a “real” logical that only took one bit that saved a lot of memory back in the day. The other puzzling thing to me is why the standard specifies a default REAL, INTEGER, and LOGICAL take the same amount of storage. I assume that is to save some old code from breaking that EQUIVALENCEd some variable of different type, which I think was always considered non-standard and problematic.

I very often find that defining all the logicals to be the smallest kind supported saves significant space when large LOGICAL arrays are used, and often performs better as well. I can imagine that when only a few LOGICAL values are used that some hardware would align and optimize best with variables of a “standard” size but that would be a very unusual reason for the standard to specify the size like that. So another mystery to me is why the standard specified the size instead of leaving it up to the compiler implementations.

The @sblionel article is the best description I remember seeing of why non-standard use of LOGICAL values is not portable; but I do not see a reason the standard cannot specify more intuitive behavior except to save some non-portable old behavior (which would better be supported as an extension instead of being protected by the standard IMO).

Any explanation of good reasons it was codified would be enlightening if anyone can think of them.

One of my most common reasons for overloading == and =/ with something like the example is to use a code that uses the common extension that those work with logicals. I think the main reason so much code does not use .EQV. and .NEQV. properly is that so many compilers have the extension, which seems like a bad state of affairs.

Thank you sblionel. But urbanjost has shown us a module overloading == and /= between logical items by making use of .EQV. and .NEQV. Could that ploy be defeated by a compiler that allows more than one internal representation of .true. or of .false. ? Of course, even if the result would always be the same, == and /= would have their usual precedence, and not the precedence of .EQV. and .NEQV. that sblionel warned us about. Beliavsky’s objection seems to be that if a and b are both logical but of different kinds and a compiler has an extension allowing a==b then
(a==b) .EQV. (a.EQV.b)
might be false. Do any compilers do that?

Two more possible (harmless?) overloads are:

  1. if a and b are of type character ,a+b can be given the same result as a//b except that it would have the precedence of + instead of //

  2. if n is a nonnegative integer and c is of type character, n*c and c*n can be given the same result as repeat(n,c). DATA statements already allow n*c if nand c are both constant.

The following FPP snippet appears over 120 times in individual files in our codebase because == is undefined for logical type in the Fortran standard.

#if     IS_LOGICAL
#define IS_EQUAL .eqv.
#else
#define IS_EQUAL ==
#endif

It’s unfair to expect the standard committee to make backward-incompatible changes to the language. But it is also equally or more unfair to expect modern Fortran programmers to endure so much pain with the lack of such simple generic tools and operations. There should be a middle ground to resolve such minor but annoying incompatibilities.

1 Like

The standard might require the compilers to give the expected result of (A == B) (i.e. .true. iif both A and Bare .true.), exactly the same way it requires it for the .EQV. operator, regardless the internal representations.

Similarly, bit manipulation functions on integers assume a representation model of the integers, regardless the actual internal representation of the integers in memory, which could be entirely different from the model (I’m not even sure that the standard requires that a given integer value has a unique internal representation?).

I tend to prefer the explanations given in the gfortran documentation, about the possible ambiguities if == was used :

1 Like

Is there a detailed proposal anywhere for adding LOGICAL == LOGICAL to the language? In particular, what would the precedence of == be (see the gfortran link posted by @PierU), and what happens if there is already a user-defined operator?

This seems like it would be nice to have, but I worry that the answer to those two questions would just add different warts to the language. At least the current one is easily identified: you use == and the compiler asks if you meant .eqv..

I don’t understand this. Do you then write x IS_EQUAL y instead of x .eqv. y? Why?

My opinion is that the precedence issue detailed on the gfortran page is a good enough reason to not have == and /= as operators for logical types.

If I followed the previous discussions correctly, the issue is not the result, the issue is that the .eqv. and == operators have different precedence. If that precedence is changed, then it could break previous code, including both code that uses language extensions for specific compilers and also standard conforming code. If the programmer was cautious and used redundant parentheses to enforce precedence within expressions, then the code would survive such a change. Otherwise, it might introduce a very difficult to find bug.

Could someone who understands this precedence issue well post an example of an expression that evaluates differently if the operator precedence were changed?