Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add XOR operator to GDScript #3849

Open
elvisish opened this issue Jan 23, 2022 · 27 comments · May be fixed by godotengine/godot#76191
Open

Add XOR operator to GDScript #3849

elvisish opened this issue Jan 23, 2022 · 27 comments · May be fixed by godotengine/godot#76191

Comments

@elvisish
Copy link

elvisish commented Jan 23, 2022

Describe the project you are working on

Any game.

Describe the problem or limitation you are having in your project

These don't work as a workaround for XOR:
collision.get("normal").y > 0.99 != get_floor_normal().y > 0.99
collision.get("normal").y > 0.99 ^ get_floor_normal().y > 0.99

I have to invert the logic by returning from the function if both are true, rather than not satisfying the condition if either both not both are true:
collision.get("normal").y > 0.99 and get_floor_normal().y > 0.99: return

Describe the feature / enhancement and how it helps to overcome the problem or limitation

As above.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Maybe the word XOR and ^ extended from just bitwise operations.

If this enhancement will not be used often, can it be worked around with a few lines of script?

As written above, but not the best solution.

Is there a reason why this should be core and not an add-on in the asset library?

It's a GDScript enhancement.

@Calinou
Copy link
Member

Calinou commented Jan 23, 2022

Related to #230 (which was previously rejected).

@aaronfranke
Copy link
Member

These don't work as a workaround for XOR:

Why? What doesn't work?

print(true != true) # true XOR true = false
print(true != false) # true XOR false = true
print(false != true) # false XOR true = true
print(false != false) # false XOR false = false

@dalexeev
Copy link
Member

Why? What doesn't work?

a xor b is not a != b, it's bool(a) != bool(b). But it's less compact and bool doesn't work for all data types. There are other arguments as well, see the discussion in the original proposal.

@zinnschlag
Copy link

bool doesn't work for all data types.

Do you have an example where bool does not work but xor would be expected to? I can't think of one. And if there is, that would be more likely a problem with bool instead of the lack of xor.

@elvisish
Copy link
Author

Why? What doesn't work?

a xor b is not a != b, it's bool(a) != bool(b). But it's less compact and bool doesn't work for all data types. There are other arguments as well, see the discussion in the original proposal.

Exactly, wrapping each condition in bool() is not only less convenient but GDScript is designed to be convenient and less verbose if possible.

@Xrayez
Copy link
Contributor

Xrayez commented Jan 24, 2022

Related to #230 (which was previously rejected).

Here's a list of related discussions so far starting from 2014:

It appears to me that @vnen is not completely against the feature according to those discussions, I think it's just that it's one of those features which has low priority. However, my interpretation may be certainly wrong so we need to get @vnen to either reject or approve this once and for all. Sounds good? Otherwise I'm afraid these XOR proposals will come up from time to time. You have to be aware of Godot's development philosophy.

@aaronfranke
Copy link
Member

Does anyone have an example where != does not work but boolean xor would work? If they're not booleans, what kind of situation are you expecting to occur, are you trying to xor strings or something? xor would take boolean inputs, so it would be equivalent to casting != inputs to bool.

Also note that for integers, Godot already has a bitwise XOR ^ operator. https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html#operators

@dalexeev
Copy link
Member

dalexeev commented Jan 25, 2022

xor would take boolean inputs, so it would be equivalent to casting != inputs to bool

There are currently no bool constructors for most types.

изображение

And it's strange to force cast to bool for "xor" (bool(string) != bool(object)), if for and and or it is not necessary to cast to bool (in 3.x string or object works, in 4.0 alpha 1 it doesn't work, bug?).

Does anyone have an example where != does not work but boolean xor would work?

@elvisish
Copy link
Author

elvisish commented Jan 31, 2022

if !on_ladder and !swimming:
		return input_dir.x * body.global_transform.basis.z + input_dir.y * body.global_transform.basis.x
elif on_ladder or swimming:
	return input_dir.x * head.global_transform.basis.z + input_dir.y * body.global_transform.basis.x
else: #REPEAT FIRST CHECK CAUSE NO XOR
	return input_dir.x * body.global_transform.basis.z + input_dir.y * body.global_transform.basis.x

I have to repeat the first condition as a redundant else, otherwise i could just XOR the first one.

@YuriSizov
Copy link
Contributor

I have to repeat the first condition as a redundant else, otherwise i could just XOR the first one.

Well, this can still be two branches, you can just expand XOR into two checks, no? (A OR B) AND (A <> B) for the second branch would allow you to remove the first.

@elvisish
Copy link
Author

I have to repeat the first condition as a redundant else, otherwise i could just XOR the first one.

Well, this can still be two branches, you can just expand XOR into two checks, no? (A OR B) AND (A <> B) for the second branch would allow you to remove the first.

No, because a non-void function must return a value in all possible paths:
image

@YuriSizov
Copy link
Contributor

YuriSizov commented Jan 31, 2022

You didn't really do what I'd described. You don't need the first case. You need only the second case and your old "else" case to achieve the XOR behavior. When (A OR B) AND (A <> B), go into your second case, otherwise use your old else case.

if (on_ladder or swimming) and (on_ladder != swimming):
	return input_dir.x * head.global_transform.basis.z + input_dir.y * body.global_transform.basis.x
else:
	return input_dir.x * body.global_transform.basis.z + input_dir.y * body.global_transform.basis.x

PS. I assume there is some typo, because the first case you have is for when neither is true which already won't be captured by the second case which is for when either is true. Either way, you have two boolean flags, so the overall point of mine stands, as that's what XOR expands into, whatever logic you actually want inside of the conditional branch.

PPS. Well, like Aaron mentioned, (on_ladder != swimming) would already be sufficient here for XOR, but if you want explicit clarity, there is no harm in (on_ladder or swimming) and (on_ladder != swimming).

@elvisish
Copy link
Author

elvisish commented Jan 31, 2022

You didn't really do what I'd described. You don't need the first case. You need only the second case and your old "else" case to achieve the XOR behavior. When (A OR B) AND (A <> B), go into your second case, otherwise use your old else case.

if (on_ladder or swimming) and (on_ladder != swimming):
	return input_dir.x * head.global_transform.basis.z + input_dir.y * body.global_transform.basis.x
else:
	return input_dir.x * body.global_transform.basis.z + input_dir.y * body.global_transform.basis.x

PS. I assume there is some typo, because the first case you have is for when neither is true which already won't be captured by the second case which is for when either is true. Either way, you have two boolean flags, so the overall point of mine stands, as that's what XOR expands into, whatever logic you actually want inside of the conditional branch.

PPS. Well, like Aaron mentioned, (on_ladder != swimming) would already be sufficient here for XOR, but if you want explicit clarity, there is no harm in (on_ladder or swimming) and (on_ladder != swimming).

Ah okay:

if (!on_ladder and !swimming) and (on_ladder != swimming):
	return input_dir.x * body.global_transform.basis.z + input_dir.y * body.global_transform.basis.x
else:
	return input_dir.x * head.global_transform.basis.z + input_dir.y * body.global_transform.basis.x

That seems to work, I still think XOR would be useful to keep things simpler and less verbose, though.

@YuriSizov
Copy link
Contributor

YuriSizov commented Jan 31, 2022

if (!on_ladder and !swimming) and (on_ladder != swimming):
That seems to work

Does it? That condition doesn't make sense, because when the left part is true, the right part can never be true. So it's always false overall. That part confused me about your initial code snippet as well, because it doesn't check anything related to XOR.

@elvisish
Copy link
Author

if (!on_ladder and !swimming) and (on_ladder != swimming):
That seems to work

Does it? That condition doesn't make sense, because when the left part is true, the right part can never be true. So it's always false overall. That part confused me about your initial code snippet as well, because it doesn't check anything related to XOR.

I did it this way instead:

	if !on_ladder or !swimming:
		return input_dir.x * head.global_transform.basis.z + input_dir.y * body.global_transform.basis.x
	else:	
		return input_dir.x * body.global_transform.basis.z + input_dir.y * body.global_transform.basis.x

Me still want XOR.

@filipworksdev
Copy link

XOR makes very little sense in programming as a separate operation. Some languages have it for convenience mostly.

The easiest way to implement XOR in GDScript would be (!a) != (!b) with the round brackets to avoid order of operation errors. The ! forces a cast to boolean and will work with anything and since you only care if the two values are not the same then doesn't matter that you invert the values.

@elvisish
Copy link
Author

Some languages have it for convenience mostly.

That's the point, to make it more convenient.

@just-like-that
Copy link

Some languages have it for convenience mostly.

That's the point, to make it more convenient.

For me this is all about readability of source code and with better readability comes better maintainability.
Imo this is stronger than convenience.

GDScript supports both an OR and an AND operator although one can be transformed into the other - see
De Morgan Laws .
To support both makes ofc sense performance-wise as well as from the point of logical understanding by the user.

@9gay
Copy link

9gay commented Sep 7, 2022

@elvisish in your code above you treat XOR (exclusive or) as NAND (not and), those are different operators

NAND:

0 nand 0 == 1
0 nand 1 == 1
1 nand 0 == 1
1 nand 1 == 0

NAND can be implemented as !(a and b) or !a or !b (which is what you did above)

XOR:

0 xor 0 == 0
0 xor 1 == 1
1 xor 0 == 1
1 xor 1 == 0

XOR can be implemented as a != b or !(a and b or !(a or b))

@9gay
Copy link

9gay commented Sep 7, 2022

@elvisish also if you want bitwise NAND then you can just use ~(a&b) and for bitwise XOR you already have a^b

anyway, this proposal appears to be about NAND operator: nand and !&& for booleans; ~& xor !& for bitwise

but in that case there also would need to be operators for NOR (not or) and XNOR (not xor): nor, !||, xnor and == for booleans; ~| xor !| and ~^ xor !^ for bitwise

NOR:

0 nor 0 == 1
0 nor 1 == 0
1 nor 0 == 0
1 nor 1 == 0

NOR is !(a or b) for booleans and ~(a|b) for bitwise

XNOR:

0 xnor 0 == 1
0 xnor 1 == 0
1 xnor 0 == 0
1 xnor 1 == 0

XNOR for booleans is already a == b and for bitwise is ~(a^b)

@9gay
Copy link

9gay commented Sep 7, 2022

Why? What doesn't work?

a xor b is not a != b, it's bool(a) != bool(b). But it's less compact and bool doesn't work for all data types. There are other arguments as well, see the discussion in the original proposal.

@dalexeev if you read the code in the first post collision.get("normal").y > 0.99 != get_floor_normal().y > 0.99 you can see that there is no need for bool(collision.get("normal").y > 0.99) and bool(get_floor_normal().y > 0.99) because a > b results in bool, so in this case != is truly XOR and author simply doesn't know what they are asking for - NAND !(a and b)

@dalexeev
Copy link
Member

dalexeev commented Sep 7, 2022

author simply doesn't know what they are asking for - NAND

I still think that xor is needed, although it is rarely needed, because it is not a complex thing, but a basic one. The fact that the example does not fit does not mean that such examples do not exist.

@Zireael07
Copy link

Quite recently I had to hand-implement XOR for cycles detection in my racer. (And I do mean XOR, 0 XOR 0 = 0 and 0 XOR 1 = 1, I wanted to detect the latter case)

@9gay
Copy link

9gay commented Sep 7, 2022

author simply doesn't know what they are asking for - NAND

I still think that xor is needed, although it is rarely needed, because it is not a complex thing, but a basic one. The fact that the example does not fit does not mean that such examples do not exist.

maybe practical examples where != are used with bool() would prove that such operator is really necessary, even when it comes to &&/and and ||/or I don't use expressions between them that don't result in bool, but maybe it's just my habit that comes from statically typed languages - Go, Haskell and Rust

@dalexeev
Copy link
Member

dalexeev commented Sep 7, 2022

don't use expressions between them that don't result in bool, but maybe it's just my habit that comes from statically typed languages - Go, Haskell and Rust

I quite often write == 0, == null, etc. in conditions, as it seems to me that this is more descriptive, but in many standards it is recommended to use short conditions, without explicit casting to bool.

@elvisish
Copy link
Author

elvisish commented Sep 8, 2022

I honestly didn't know there was so much to this, it's been a real education! (At least I know I wasn't looking for XOR!)

@shenkes
Copy link

shenkes commented Sep 16, 2024

Some languages have it for convenience mostly.

That's the point, to make it more convenient.

For me this is all about readability of source code and with better readability comes better maintainability. Imo this is stronger than convenience.

GDScript supports both an OR and an AND operator although one can be transformed into the other - see De Morgan Laws . To support both makes ofc sense performance-wise as well as from the point of logical understanding by the user.

I think the readability is the strongest argument here, despite few talking about it. Godot's styleguide encourages using OR, AND, and even NOT instead of ||, &&, and ! (though I admit that last one feels a bit cursed in many cases).

We should also allow for XOR instead of != and probably ^^ instead of != as well.
For me at least, a XOR b and a ^^ b translate to "either a or b" where a != b translates to "a does not equal b".
These are not the same readability-wise and there is an additional logical step/jump to get to "either a or b" from "a does not equal b"

What I'm currently working on with reflection is a decent showcase of where it does not read well with != where XOR would be more clear and also the opposite.

if(
(
    (object1.my_int + object2.my_int) % size_int != object3.my_int OR
    (object1.reflection != object2.reflection) != object3.reflection
    # other comparisons of object1 combined with object2 to object3
)
    return false
else:
    return true

I'd prefer this for readability. Notice I'm using both XOR and != with booleans because in one case I am thinking "either this or that" and the other I am thinking "this does not equal that".

if(
(
    (object1.my_int + object2.my_int) % size_int != object3.my_int OR
    (object1.reflection XOR object2.reflection) != object3.reflection
    # other comparisons of object1 combined with object2 to object3
)
    return false
else:
    return true

It seems the PR is most of the way through being merged but still wanted to comment as this reasoning didn't seem properly displayed and there had been some push-back in the threads I've read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.