How to pass a named function as a parameter (scriptblock)
Asked Answered
C

4

5

Let's take the classic first-order functions example:

function Get-MyName { "George" }

function Say-Hi([scriptblock]$to) {
  Write-Host ("Hi "+(& $to))
}

This works just fine:

Say-Hi { "Fred Flintstone" }

this does not:

Say-Hi Get-MyName

because Get-MyName is evaluated, not passed as a value itself. How do I pass Get-MyName as a value?

Correspondence answered 12/4, 2013 at 17:12 Comment(0)
A
10

You have to pass Get-Myname as a scriptblock, because that's how you've defined the variable type.

Say-Hi ${function:Get-MyName}
Aeniah answered 12/4, 2013 at 17:45 Comment(8)
How is this different from Say-Hi {Get-MyName}? Is there a type other than [scriptblock] that would allow passing of named functions?Correspondence
In this case, there is no difference, because the return value is the same as the scriptblock definition of the function. Which one are you wanting to pass, the scriptblock of the function or the return value from the invocation of that scriptblock?Aeniah
I would like to pass the function itself, same as you can do in javascript, or any of the .net languages using delegatesCorrespondence
OK. We just did that. The 'value' of a function is the content of it's script block. In this case the script block is just "George".Aeniah
That's not quite true, suppose Get-MyName took parameters, in your version you would have to know all the parameters that it is invoked with and forward them manually, if you're passing the function directly (as you would in js or c#) its parameters are whatever it is invoked with from inside Say-HiCorrespondence
Then (get-item function:get-myname)Aeniah
Ah, I see, and then I would have to get rid of the [scriptblock] constraint and it should workCorrespondence
I've never tried to pass a function that way, but the object type that returns is [functioninfo]. The scriptblock is just a property of the [functioninfo] object.Aeniah
I
3

If you are ready to sacrifice the [scriptblock] parameter type declaration then there is one more way, arguably the simplest to use and effective. Just remove [scriptblock] from the parameter (or replace it with [object]):

function Get-MyName { "George" }

function Say-Hi($to) {
  Write-Host ("Hi "+(& $to))
}

Say-Hi Get-MyName
Say-Hi { "George" }

So now $to can be a script block or a command name (not just a function but also alias, cmdlet, and script).

The only disadvantage is that the declaration of Say-Hi is not so self describing. And, of course, if you do not own the code and cannot change it then this is not applicable at all.

I wish PowerShell has a special type for this, see this suggestion. In that case function Say-Hi([command]$to) would be ideal.

Intelligence answered 21/6, 2014 at 5:9 Comment(0)
S
3

This might be a better example to illustrate the question, and details of execution scope. @mjolinor's answer appears to work nicely for this use case:

function Get-MyName($name) { $name; throw "Meh" }

function Say-Hi([scriptblock]$to) {
  try {
      Write-Host ("Hi "+(& $to $args)) # pass all other args to scriptblock
  } catch {
      Write-Host "Well hello, $_ exception!"
  }
}

The command and its output:

PS C:\> Say-Hi ${function:Get-MyName} 'George'
Well hello, Meh exception

In particular, I'm using this pattern for wrapping functions that work with a flaky remote SQL Server database connection, which sleep, then retry several times before finally succeeding or throwing a higher exception.

Sectarian answered 27/10, 2016 at 20:2 Comment(0)
P
1

Strictly based on your code, the correct answer is this:

Say-Hi {(Get-MyName)}

This will produce "Hi George"

Peay answered 20/6, 2014 at 19:11 Comment(3)
Thanks! Do you have any insight as to the difference between this syntax and that by @Aeniah above?Correspondence
As @Peay said "strictly based on your code", there is not much difference. But if the function has parameters then calling it with parameters in this way will not be straightforward.Intelligence
@GeorgeMauer The difference is that here is passed a new block in which Get-MyName is called. In mjolinor's example there is a direct reference to the Get-MyName-function passed. (With parameters this variation could be written like Say-Hi { Get-MyName @args } whereas mjolinor's would work without modification.)Trotline

© 2022 - 2024 — McMap. All rights reserved.