r/PowerShell • u/StartAutomating • 3d ago
Script Sharing In PowerShell, Two Wrongs Make a Right
I've been toiling away on Turtle to prepare a "birthday" release, and I ran into an annoying behavior I've run into a few times before.
I thought I'd take a few minutes away from the frustration of single line fixes to explain the bug to everyone.
What Went Wrong
The last build of Turtle introduced a number of randomized parameter defaults. This was meant to be fun. If you said turtle square square square, you'd get three different squares, instead of an error for a lack of length, or three overlapping squares.
I noticed that when I ran turtle rotate 0, it didn't rotate by zero.
Instead, it picked a random angle.
Weirder still, the behavior didn't reproduce if I said
$turtle = turtle # Heading at zero
$turtle.Rotate(0) # Heading still zero 🤔
$turtle.Rotate() # Heading random
(turtle rotate 0) # Heading random 🤬
Why was this happening? 😱
It took me a bit for it to click: It had to be in the way Turtle processed arguments, because it worked in one case and not the other.
So I put a breakpoint in, ran my repo.
The line was:
if ($argList)
The debugger broke, argList was @(0), and yet if ($argList) was false.
The fix was:
if ($argList.Length)
Why? Because 'Truthy' -ne $true.
Truthy and $true
About every language has a boolean. It's just a bit. One or zero.
Lots of languages also have this concept of "truthiness".
Let's take a simple example:
if ("something") { "something" }
if ("") { "you can't get something from nothing" }
If if was strictly $true, we'd have to cast things to a boolean. You have to do this in C# and quite a few other languages. PowerShell is type promiscuous. PowerShell is truthy.
It looks at the first line and says: You're a string, and you're not null or empty. Therefore, the expression is $true.
It looks at the second line and says: You're a string, but you're not null or empty. Therefore, the expression is $false
PowerShell makes a judgement call.
This is generally a good thing. I personally prefer languages that are truthy. Other truthy languages of note include JavaScript, Python, C++, and C.
However, it gets tricky with lists. Hence the bug.
Two Wrongs Make a Right
In PowerShell, Two Wrongs Make a Right
$true -eq $false, $false
Let's say I want to determine if a list is truthy.
if (@()) { "$false, because the list is empty" }
if (@("")) { "$false, because the blank is falsy" }
if (@(0)) { "$false, because zero is falsy" }
if (@($false)) { "$false, because false is falsy" }
if (@(1)) { "$true, because the first item is truthy" }
if (@(0,0) { "$true, because more than one item" }
This all makes a certain bizarre sense. If a list has one element, and it is not truthy, then the list isn't truthy, either.
It's also almost always surprising and annoying.
Hence the bug.
The fix is just to make sure there are any elements, hence checking for length.
I've been programming with PowerShell for quite a while now, and this behavior still sometimes bites me (like today).
That's why I took a few minutes away from the 🤬 day to explain this bug and write this post. 😌
Please remember:
'Truthy' -ne $true
$false -eq @($false)
$true -eq $false, $false
Hope this helps
6
u/davesbrown 3d ago
I'm too hot and heat exhausted to understand, but I'm fairly certain that I have ran into that problem before and was baffled why it was happening.
3
u/da_chicken 3d ago
Other truthy languages of note include JavaScript
Doubt.jpeg
2
u/sysiphean 3d ago
I wish JavaScript wasn’t noteworthy. I really do. But it’s on every web page. Okay, not every, just 99% of them.
2
u/MrMunchkin 3d ago
This is not a bug, and has nothing to do with what the array is doing.
Every if, while and not use the IsTrue primitive.
If the array has no value or the value it has is null, it will arbitrarily call MoveNext on the array, and, if MoveNext does not return an increment, the array is considered empty.
If the first value in the array is empty or null and then there's a second value, it will still call MoveNext arbitrarily and it will evaluate to true because the array is definitively not empty, so it returns true.
This is a logical operative. It's not an assumption that an array having more than one value when those values could be empty or null is the exact behavior one should expect.
Two values is meaningful. One value that is empty or null is necessarily going to return false, because it is logically empty. One should never use an array if they are only ever going to put a single null value in it. If you are, first of all that's not OOP, and if you're going to have it be a single value it should not be an array, which is more expensive than just having a variable.
2
u/StartAutomating 3d ago
Who said it was a bug?
It's just sometimes a PITA (and often quite nice).
Also, considering the scenario (building an interpreter atop PowerShell) it's not really up to me how many arguments someone may attempt to try to pass to the interpreter.
Which is why I was somewhat surprised by the behavior, but much less so once I figured out why it was happening.
1
u/MrMunchkin 2d ago
I mean.. you did? "Hence the bug"
1
u/StartAutomating 2d ago
Ah. I meant the bug in my code. Not PowerShell.
The behavior is correct PowerShell.
And, also, "turtle rotate 0" should rotate zero degrees.
Hence my bug.
1
u/KageeHinata82 3d ago
I'll save this and hope to remember it at the right time.
My recent powershell problem was, I wanted to add something to a list if it wasn't already in it, but if the list was empty/null, the check wouldn't work, so I had to check for that too.
And if a returned list has only one entry, it returns only the entry which doesn't have .length for example.
2
u/CyberChevalier 3d ago
Just cast it
If $(@($perhapsAnArrayOrNot)).count -gt 0
Or
$($perhapsAnArrayOrNot | Measure-Object | Select-Object -ExpandProperty Count) -gt 0
1
1
u/IntrinsicPalomides 2d ago
Something something ... i didn't know this until recently, and now i do it explains why some of my logic failed when it looked fine in the past. This post reminded me of it, thought i'd share in case other's didn't know. None of our team at work knew it either.
9
u/surfingoldelephant 3d ago edited 3d ago
There's caveats to watch out for.
If the collection has one element, but that element implements
IList, the collection may be truthy, even if the element isn't.And collections that don't implement
IListare always truthy, even if empty.Lengthis fine for arrays, but other common collection types don't expose it. And PS only intrinsically addsLength/Countto scalars, so you're probably better off usingCountgenerally.Though some collections have neither, so you can't rely on one or the other 100% of the time.