Explaining Rationale for Function Output Behavior

This topic contains 8 replies, has 5 voices, and was last updated by Profile photo of tocano tocano 7 months, 3 weeks ago.

  • Author
    Posts
  • #54829
    Profile photo of tocano
    tocano
    Participant

    Ok, so I discovered (the hard way) how functions sort of queue up any output from within the function and then return all of it as part of the return object.

    function foo () {
      echo "SettingVar"
      $var = "varValue"
      echo "VarSet"
      return $var
    }
    
    > foo
    SettingVar
    varValue
    VarSet
    

    I initially found this to be idiotic. Why wouldn't it behave the same as the rest of the code? Why wouldn't it behave the same as most other languages that I've ever worked with? It made no sense to me.

    This makes it much more difficult to take a block of code I suddenly find myself needing to reuse and just throw a function declaration and some brackets around it and reuse it. Suddenly the code has to be refactored to capture all output (or direct to something like Write-Host instead of standard output – which results in its own challenges) in order to avoid ruining the returning object.

    But I tried to be open minded. Ok, maybe if they were trying to pseudo-recreate VBScript mechanics (function means return value;subroutine means no return) and this behavior was being used in a 'subroutine' to have it return all output at the end, then maybe I could understand it. But since they don't distinguish between the two and, in fact, eliminated the distinction – only having functions (as far as I'm aware), that doesn't apply here.

    I've tried thinking of various ways in which this would be a technical requirement – something structural that prevents these from behaving as just another section in the main line of the code run – after all, it already shares variable scope with the rest of the code in a script, why would it then seem to "jump out" to execute as almost a separate run of code? But with my limited knowledge of the internals of Powershell, I couldn't come up with anything.

    I don't understand it.

    So I would appreciate it if anyone could help me understand why this is the behavior – is it a technical requirement? – and whether there is any possibility of this changing at any point – so that a function (or – to preserve legacy behavior – even create a new thing like a 'runblock' or 'execblock') would run within the current execution stream, output returns at exec time, not queued up until end of exec, and returning an obj only returns that obj.

    Thank you

  • #54830
    Profile photo of Dan Potter
    Dan Potter
    Participant

    huh?

    PS H:\> get-help about_functions
    TOPIC
    about_Functions

    SHORT DESCRIPTION
    Describes how to create and use functions in Windows PowerShell.

    LONG DESCRIPTION
    A function is a list of Windows PowerShell statements that has a name
    that you assign. When you run a function, you type the function name.
    The statements in the list run as if you had typed them at the command
    prompt.

    Functions can be as simple as:

    function Get-PowerShellProcess {Get-Process PowerShell}

    or as complex as a cmdlet or an application program.

    Like cmdlets, functions can have parameters. The parameters can be named,
    positional, switch, or dynamic parameters. Function parameters can be read
    from the command line or from the pipeline.

    Functions can return values that can be displayed, assigned to variables,
    or passed to other functions or cmdlets.

    The function's statement list can contain different types of statement
    lists with the keywords Begin, Process, and End. These statement lists
    handle input from the pipeline differently.

    A filter is a special kind of function that uses the Filter keyword.

    Functions can also act like cmdlets. You can create a function that works
    just like a cmdlet without using C# programming. For more information,
    see about_Functions_Advanced (http://go.microsoft.com/fwlink/?LinkID=144511).

  • #54833
    Profile photo of tocano
    tocano
    Participant

    Are you saying you don't understand my issue?

    Run this in ISE:

    function Get-ItemIndexes ($haystack,$needle){ 
    
        echo "Called Get-ItemIndexes()"
    
        $returnObj = @()
    
        $count = 1
        foreach ($nextItem in $haystack) {
    
            echo "Item $($count): $nextItem"
    
            if ($nextItem -eq $needle) {
    
                echo "Found needle! Adding to return obj"
                $returnObj += $count
            }
            
            $count++
        }
    
        echo "Finished processing haystack. Returning $($returnObj.count) indexes found"
        return $returnObj
    }
    
    
    $array = @("a","b","c","b")
    
    echo "Will call Get-ItemIndexes()"
    
    $indexes = Get-ItemIndexes -haystack $array -needle "b"
    
    echo "Get-ItemIndexes() completed"
    

    Note the lack of output from inside the function. Now look at the contents of $indexes.

    This apparently isn't new/unknown (just to me until now):
    https://technet.microsoft.com/en-us/library/hh847760.aspx

    http://stackoverflow.com/questions/10286164/function-return-value-in-powershell

    • #54837
      Profile photo of Craig Duff
      Craig Duff
      Participant

      I can take a stab. PowerShell is designed to be friendly to admins, and attempts to abstract away some programming stuff like Return. Return isn't really needed in a PowerShell function. Anything that goes out of stdout will go out of the function. That's to be friendly to non-programmers.

      Most people will say that you shouldn't write powershell function with messages that display by default. The idea is that they shouldn't display anything unless there is an error. Functions can be written to support verbose output, and that is where most people I've read suggest to put those type of messages. There is more than stdout and stderr in Powershell. There are streams for warnings and verbose messages.

      This would be a more powershellish way to do that:

      function Get-ItemIndexes{ 
          [cmdletbinding()]
      
          Param ($haystack,$needle) 
      
          Write-Verbose "Called Get-ItemIndexes()"
      
          $returnObj = @()
      
          $count = 1
          foreach ($nextItem in $haystack) {
      
              Write-Verbose "Item $($count): $nextItem"
      
              if ($nextItem -eq $needle) {
      
                  Write-Verbose "Found needle! Adding to return obj"
                  $returnObj += $count
              }
              
              $count++
          }
      
          Write-Verbose "Finished processing haystack. Returning $($returnObj.count) indexes found"
          return $returnObj
      }
      
      
      $array = @("a","b","c","b")
      
      echo "Will call Get-ItemIndexes()"
      
      $indexes = Get-ItemIndexes -haystack $array -needle "b" -Verbose
      • This reply was modified 7 months, 3 weeks ago by Profile photo of Craig Duff Craig Duff.
    • #54847
      Profile photo of Dan Potter
      Dan Potter
      Participant

      I understand your question but was confused as to why you would do that at all. I have hundreds of functions I've written since 2007 and none of them have write-host,echo,write-verbose etc. in them.

      The way you've constructed it makes $indexes unusable, you want to return objects so you can reuse the output somewhere else in your script.

      function get-p{get-process}
      (get-p | select -First 1).processname

    • #54862
      Profile photo of tocano
      tocano
      Participant

      I appreciate the feedback. I understand the purpose of functions and in most any other language, that made-up Get-ItemIndexes would only return a single object containing the indexes found. I also understand echo/Write-Output/Write-Host and their differences. And while some may never use Write-Output in their scripts, I do a great deal. It significantly helps in larger functions to be able to keep flow in log files of the output.

      My major issue is that, beyond being counterintuitive vs most other scripting langs, when I take code that I wish to reuse, I am forced to refactor it instead of essentially tossing it, as is, into a function block. I have to either remove echo/Write-Output statements, change them to Write-Host/Write-Verbose (not to mention when I output objects for logging purposes), and alter the way the script itself is ran (-Verbose) in order to have everything function as expected. Plus, if I go this method, I have to make sure that my functions are prepared to utilize Write-Verbose.

      Craig Duff: "PowerShell is designed to be friendly to admins, and attempts to abstract away some programming stuff like Return. Return isn't really needed in a PowerShell function. Anything that goes out of stdout will go out of the function. That's to be friendly to non-programmers."

      See, I don't think it *IS* friendly to admins. Anyone that has used any other scripting lang, including other MS Windows lang like VBScript, would find this very counter-intuitive.

      Dan Potters: saying 'huh?' and just listing the docs on functions made it seem like you didn't know what I was talking about. And good for you. I'm glad this odd implementation of functions doesn't affect you. For those of us that use output for logging purposes frequently, it does. But if you don't use any output in your functions, then if PS functions were to be modified to behave more like functions in other languages, it shouldn't significantly affect you. 🙂 I wrote the function (that would work perfectly well in most any other scripting language), specifically to demonstrate that it functions very unintuitively in PS.

      It felt, as it appears Don Jones confirmed, like this implementation assumed a great deal about how functions would/should be used – which seems like a mistake.

      I guess I'll work through and replace all echo/Write-Output with Write-Verbose, and just deal with it.

      Thanks for the feedback.

  • #54839
    Profile photo of Don Jones
    Don Jones
    Keymaster

    I think there's two things going on.

    One, "return" is really just an internal alias to Write-Output. It doesn't "return and exit" as you would expect from all other programming languages, and was a bad choice on the PowerShell team's part. They've owned up to this. In a Class construct, "return" behaves as you'd expect.

    Two, you're perhaps not entirely embracing where PowerShell is coming from. Functions aren't meant to emit a whole bunch of accumulated objects; they're expected to emit one, at a time, to the pipeline – so that the next command in the pipe can begin processing objects immediately. There are notable exceptions – like Sort-Object – which do block the pipeline and accumulate objects, due to the nature of what they're doing. But in most cases, commands in the pipeline can run "kind of in parallel" and process one object at a time as they pass down the line.

    PowerShell's main ethos is as a shell, not a scripting language per se. The creation of functions – a kind of command – is mainly to enable composable pipelines. Thus the different approach. "Learn PowerShell Toolmaking in a Month of Lunches" kind of expounds on that ethos, and outlines the difference between a "tool" (command) and "controller script" in PowerShell's perspective. Functions (commands) are meant to be fairly tightly scoped, atomic units of work that have a high expectation for reuse across business processes. A "controller script" is a less structured/formal procedural script which employs those commands to a specific use, implementing a business process of some kind.

    "Echo" is similarly an alias (e.g., "Get-Alias echo" and see what you get). There's also a strong difference between Write-Output and Write-Host, with only the former employing the pipeline, and the latter being restricted to screen display. Again, this is kind of a big part of PowerShell's worldview that unfortunately isn't well spelled-out in a lot of the docs (see, "Learn Windows PowerShell in a Month of Lunches,") but playing along with PowerShell's "way of doing things" is a lot easier and more productive than treating it like, say, VBScript.

  • #54840
    Profile photo of Don Jones
    Don Jones
    Keymaster

    (By the way, I find that a lot of people with a strong shell or scripting background from another OS or language run into exactly the same problems; PowerShell _looks_ a lot more familiar than it is, and that facade obscures what it actually is doing and how it wants to live in the world. It's worth spending some time learning it's worldview, which is what I tried to do in the two books I referenced in the previous post.)

    And happy to clarify/expand, if it helps. Just ask.

  • #54849
    Profile photo of Jonathan Warnken
    Jonathan Warnken
    Participant

    You may also want to review this discussion

You must be logged in to reply to this topic.