Add XOR (Exclusive OR) operator support for Bool type

The ^ (XOR - Exclusive OR) operator is currently not supported for Bool type.
It is commonly used in other languages such as C and Java.

I propose adding support via an extension.

So, Instead of having to write something like this:

if switch1 != (switch2 != (switch3 != switch4)) {
	isOn = true
} else {
	isOn = false       
}

Adding support - We could then write it like this:

if switch1 ^ switch2 ^ switch3 ^ switch4 {
	isOn = true
} else {
	isOn = false
}

P.S. If excepted, I would like to add it myself to the language if an expert contributor would guide me through the processes. Thanks.

2 Likes

like this?

infix func ^ (lhs: Bool, rhs: Bool) -> Bool {
    return lhs != rhs
}
7 Likes

It isn’t an oversight but a deliberate decision.

Bool also doesn’t have & and |, and as you know, && and || behave somewhat differently in terms of precedence, associativity, and short circuiting. Additionally, in Swift, the precedence of operators (and particularly of bitwise operators) has been rationalized and differ from other C-family languages.

Swift’s design also reflects the decision that Bool does not represent a bit; it represents only a logical true or false value. (By contrast, the integer types model both the integers and a sequence of bits.)

To remain consistent with this model, it would not do to add merely ^ for Bool. Either we would have to add & and | also (i.e., overturn the decision that Bool doesn’t represent a single bit, a choice that has been deliberately avoided), or we would want to create ^^ just for Bool. But if we had ^^ behave consistently with && and ||, then it would just be more or less another spelling for !=.

As you show, it’s possible to use != for this operation. Swift deliberately allows users to make their own extensions, so you can always use it for yourself, but I don’t think a ^ or ^^ would be a good fit for addition to the standard library for the reasons above.

15 Likes

It should definitely be called ^^ and it should absolutely be the same as !=. The issue isn’t that you can’t do it, it’s that != is a very confusing to see for people who aren’t used to it.

If the idea is to minimise the number of boolean operators, we don’t need both AND and OR. Come to think of it, we could just make do with NAND, no need even for NOT...

1 Like

The thing about ^^ is that it appears to imply that the operation can short circuit (for people with some C-family experience), but xor never short circuits; you always have to evaluate both arguments.

@Jonathan-Hoch Can you give a non-synthetic example of how you want to use this operator? The only real issue--for me--with != is that it doesn't have associativity defined, so you can't chain them without parentheses. But chaining more than two or three booleans together like this is relatively rare, so it would be helpful to see the use cases that are motivating your request.

@xwu. I understand your points.

  1. Bitwise operators precedence are rationalised and differ from other languages
  2. Bool does not represent a single bit.

In a few projectes I came across needing Bool to act as a bit and used an extension.

extension Bool {
	static func ^(lhs: Bool, rhs: Bool) -> Bool {
		return lhs != rhs
	}
}

I was surprised it wasn't "built in" as I was use to it in other languages.

On the one hand it makes sense for Swift to have Bool be only a logical true or false value as there are no "primitive types" in Swift. However, in other languages Bool-boolean is a primitive type and does support Bitwise operations. - This takes me back to the basics of CS. Boolean to me is the most basic bit 1-0 flipper.

To overturn Swift’s design that Bool does not represent a bit - Is "way beyond my pay grade". :slightly_smiling_face:

I leave the descision for you guys here. Thanks.

This isn't quite right; to get xor, you only want to check the parity (true if there are an odd number of trues, false if even). If you already have them in a collection you can just use reduce(false, !=); the trick is when you don't have a collection.

3 Likes

So the repeating of the operator indicates, in C, that there is short circuiting? How important is this C convention for modern Swift though?

And as you say, XOR can never short circuit, so even people familiar with the convention wouldn't expect ^^ to short circuit, meaning it wouldn't cause any bugs, right?

If that is correct, then I would say that it's worth more to give modern Swift users an XOR that looks like an XOR, than to adhere to a C convention.

1 Like

Not just in C, but in C family languages, && and || short circuit and & and | do not. Swift is, in that respect at least, a member of the C family and the same behavior applies.

If & and | were defined for Swift, users would be justified in expecting that they do not short circuit. It would follow that since a logical XOR cannot short circuit, it would not be spelled ^^. However, it also cannot be spelled ^ because that operator is already defined with the expected precedence relative to & and | and not to && and ||.

I’m not sure to what you refer here by “looks like a XOR” other than a spelling that isn’t !=. To be clear, it is not as though users don’t know about or misunderstand != on Bool, and it both looks and behaves the same way in many languages (which is to say, it looks like != and behaves as a logical XOR in many languages). There is no evidence of user misunderstanding as to its behavior, and if it were not present in Swift it would be added to the language with the exact semantics it has.

It’s pretty clear to me that having two logical XOR operators would be problematic. Either another operator behaves identically to != in every respect for Bool values, which would then be confusing and unprecedented, or it does not because it would have different associativity and/or precedence, which would also be confusing and unprecedented.

If there is a concrete use case that isn’t well served by Swift’s current operators, we should look at what the use case is and devise another solution. Otherwise, I think it’s fair to guide users to deploy Swift’s existing operators efficaciously.

1 Like

FWIW, in my own codeÂą, I would probably write this as:

isOn = [switch1, switch2, switch3, switch4].reduce(false, !=)

I personally find this clearer, especially if the switches are actually expressions. YMMV.

Âą at least outside of a performance-critical context; in such a context I would have to see how well the optimizer does with the expression (and maybe avoid it anyway because of debug build performance concerns).

4 Likes

Oh I totally misunderstood it.

isOn should be true if there's an odd number of trues, right? (Conversely, true if there is not an even number of trues)

Couldn't that be expressed as just a counting operation?

isOn = !switches
    .lazy
    .filter(\.self)
    .count
    .isMultiple(of: 2)

This is where a count(where:) would come in really useful, to remove the need for .lazy.filter(). An isOdd and isEven function would be cool too. If you add extensions for those, you get this down to:

isOn = switches.count(where: \.self).isOdd

Yes I mean that != doesn't look like XOR. I mean it isn't an XOR operator, it is a very general operator that is available for tons of types and means "not equal". If you look in the docs it's not even explained, and it is placed separately from && and || which are explained. The only mention of != is in the documentation for == where it says:

/// Equality is the inverse of inequality. For any values `a` and `b`,
/// `a == b` implies that `a != b` is `false`.

So I don't think it is fair to say we already have XOR. We just have an unrelated operator that happens to have the same truth table as XOR.

It might be a well known workaround among C-developers, but shouldn't Swift aim to be accessible for new developers too? At the very least the docs and the official Swift book should reflect that != is viewed as XOR I think.

Maybe I'm the only one, but I certainly didn't know of the != trick when I came to iOS development. I did come to use != sometimes when it fit the behaviour I wanted, and only several years later I realised that it was actually the same as, and an acknowledged synonym of XOR.

Maybe this is because I don't have a C background, but I think such developers will be increasingly common.

But I am not sure that it's a strong argument to say that you can get by without it. You can get by without Bool.toggle() and Int.isMultiple(of:) too, and a lot of other convenient methods recently introduced.

1 Like

It's very reasonable to file a bug to document the != more completely. For Bool, it should absolutely point out that it is how you spell an exclusive disjunction.

It's not a trick or a workaround, and it has nothing to do with C. This is just Boolean algebra.

There is a distinction between convenience methods (which have a set of criteria to meet for inclusion into the standard library) and presenting duplicate APIs for the same operation.

It's undoubtedly a workaround, since != exist independently of boolean algebra, it is not limited to Bool. I hope you can agree that it is merely a lucky accident that it has the same truth table? Just like == can be used as XNOR. Who would argue that Swift has an XNOR operator?

The question is only if it's so commonly accepted that you can expect people to know it.

No! It's not a lucky accident. It's math.

4 Likes

If we're quoting them, we should at least compare their metric to the current (^) discussion.

For isMultiple(of:):

For toggle:

If it's not an accident, it would mean that != was somehow designed to be used as XOR, and later generalised to work for all equatable types.

Of course it's not an accident that it works every time without fail, I'm talking about the design of the language.

I'd say we have at least readability, consistency and correctness. But I'm not necessarily saying we have to introduce it, I just don't think all the arguments against it are very strong.

Again, it's not a matter of programming language design. If a language has a Boolean type and a notion of equivalence, then this is how it works, because math. It's like asking if 1 + 1 == 2 is by design. Anyway, I think we've sufficiently explored this here.

I feel that you are misunderstanding me on purpose. I'm not saying it's an accident in Swift, I mean in general.