Everything is iterable?

This topic contains 7 replies, has 4 voices, and was last updated by Profile photo of Paal Braathen Paal Braathen 3 months, 1 week ago.

Viewing 8 posts - 1 through 8 (of 8 total)
  • Author
    Posts
  • #42857
    Profile photo of Paal Braathen
    Paal Braathen
    Participant

    The help page describing the foreach statement: https://technet.microsoft.com/en-us/library/hh847816.aspx

    On this page you can see that the foreach is "a language construct for stepping through (iterating) a series of values in a collection of items.". This is false. This is possible:

    foreach ($Element in 1) {
        Write-Host $Element
    }

    Why is this possible?

    Is there no distinction between a iterable item and a non-iterable item in powershell?

    The about_ForEach is also a very confusing read since it mixes the foreach statement/keyword with the foreach-object cmdlet (which is aliased "foreach"). These aren't the same. Is this some kind mixup of legacy docs?

    • This topic was modified 3 months, 1 week ago by Profile photo of Paal Braathen Paal Braathen.
    • This topic was modified 3 months, 1 week ago by Profile photo of Paal Braathen Paal Braathen.
    #42879
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    PowerShell's pretty aggressive about doing type conversions to help you out. If you pass something to a foreach that isn't iterable, it's probably just converting it to a single-element array behind the scenes. The main reason for this is probably to support commands that may return 0, 1 or more elements, such as this:

    foreach ($item in Get-ChildItem)
    {

    }

    On the other hand, there are some types that are iterable in .NET that won't automatically trigger PowerShell's foreach (strings and dictionaries). These get treated as single objects in PowerShell unless you explicitly call their GetEnumerator() methods.

    #42892
    Profile photo of Don Jones
    Don Jones
    Keymaster

    And with regard to the documents, the scripting construct "foreach," the "ForEach-Object" cmdlet, and the "foreach" alias have been around since v1.0, so it isn't a mixup in the docs. It's just the way it is. PowerShell figures out the difference between the alias and the construct based on context. I agree that it's confusing to have all those identical keywords doing different things, but that's what it is. I believe it's even an article in "The Big Book of PowerShell Gotchas," on our eBooks menu, because it stumps a lot of newcomers.

    And to reinforce what Dave wrote, the documentation is not false. But, in your example, because ForEach expects a collection/array in the second position, PowerShell converts "1" to a single-object collection, which is enumerable. This happens, as Dave points out, a lot. For example, if you run:

    Get-Service -ComputerName One

    The -ComputerName parameter only accepts a string[], which is a collection. Knowing that tossing an error for only providing one computer name would be annoying, the shell converts "One" into an array of one item. You can see this if you use Trace-Command to see what's happening in the pipeline as the command is processed by the shell. As a programming language, PowerShell is very loosely typed and loosely structured, which is common for scripting languages. It doesn't exhibit the same strict behaviors as a language like C#.

    #43004
    Profile photo of Paal Braathen
    Paal Braathen
    Participant

    About foreach keyword vs foreach-object:
    Yes, they are clearly separated because of the foreach keyword expecting parentheses after the keyword.

    But shouldn't the about_ForEach doc be only about the keyword? The ForEach-Object is documented in it's own help pages. They are not the same thing (event though they are very similar).

    About the input and converting scalars to single item collections:
    I find the documentation to be false or at least misleading since it clearly says that the foreach keyword iterates collections. When some kind of conversion magic happens it should at least be mentioned.

    The argument that this is i nice thing since e.g. Get-ChildItem might return a single item is valid. It would be awkward to handle this possibility if the foreach keyword couldn't iterate single items. But: My personal opinion is that the fix is wrong. IMO the foreach keyword shouldn't be able to loop single items. The Get-ChildItem should rather always return a collection. In stead of returning null, a single item or a collection of multiple items it should've return an empty collection, an collection of one item or a collection of multiple items.

    The think the bottom line is that I find the language to be too loose. It should be much, much stricter. There is too much implicit stuff going on under the hood and in many cases this leads to unwanted and/or unpredictable behavior in scripts.

    Ohwell. I guess I'm done ranting now 🙂 How these cmdlets return their values will probably never change now and I'll just have to accept the behavior.

    #43053
    Profile photo of Don Jones
    Don Jones
    Keymaster

    I don't tend to worry about "should" something be the way it is or not. I'm not on the product team, so I just deal with what they produce ;).

    You're arguing against some of the fundamental ways that PowerShell works. I think it's probably a little late for Microsoft to go back and change it. Most users seem pretty satisfied with it. I appreciate your personal opinion, but it's outside my ability to address it. I can't implement the changes you're suggesting, I can only explain why things are the way they are. For me, engaging in a debate over them isn't a productive use of my time, since regardless of the debate's outcome, I can't change any of it.

    The language is loose by design. There are stricter languages, like C#, which might be more appropriate for you if that's your preference. In fact, it is the existence of C# that helped make PowerShell more loosely typed – people who want a more formal programming language already had one.

    PowerShell's looseness is both a strength and a weakness – just as C#'s tightness is a strength and a weakness. Everything is a tradeoff, and so you have to look at why the tradeoffs were made, and what audience and purpose they were intended to serve. There's no One True Answer that's always right in every situation. Perhaps your situation would be better served by C# or Visual Basic, both of which can access the PowerShell engine and commands.

    #43115
    Profile photo of Justin King
    Justin King
    Participant

    I think it's worth remembering the roots of the language, too. PowerShell's intent from day 1 was to make an easily interchangeable set of cmdlets for system configuration/scripting (monad manifesto and all that). Being overly strict/literal basically just "gets in the way" of the original intent of use: IT Admins who need to get "something" and then easily apply "something else" to it... essentially scripting on steroids. It has evolved to the point where people are writing games and programs as well as with Dev-Ops practices starting to hit infrastructure things continue to blur, but it's roots are fundamentally different than some more "traditional" languages that come from a programming root.

    This former "click-next guru" certainly appreciates the leniency 🙂

    #43117
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Justin, you bring up a really good point. Paal, you may want to read https://www.gitbook.com/book/devopscollective/the-monad-manifesto-annotated/details for some of the original history on the shell. It's interesting to me, at least.

    #43540
    Profile photo of Paal Braathen
    Paal Braathen
    Participant

    Thanks for the manifesto link. I'll check it out 🙂

    Yes, I agree that debating "I think this and that should've been different" when you can't even do anything about it isn't productive. What could be a productive discussion on the other hand is how to deal with these "flaws", "inconveniences" or .

    I think an example with Get-ChildItem illustrates the issue quite well. E.g. how would you get the number of objects returned from Get-ChildItem when the return type isn't a single, strictly defined type?

Viewing 8 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic.