Concisely passing functions as parameters - powershell

This function accepts two values and a function, and calls that function with the two values:
function abc ($a, $func, $b) { &$func $a $b }
Define a function to pass:
function bcd ($x, $y) { $x + $y }
Pass bcd to abc:
abc 10 bcd 20
The result is 30.
It appears that the bcd function object itself isn't being passed, but the string "bcd". Then abc invokes bcd by its name.
Is this considered an acceptable way to pass a function to another function? Most examples I've seen suggest passing a function in a script block which will be invoked in the receiving function. However that approach is more verbose than the above method.

You're correct that it works, as long as the scope doesn't change enough to invalidate the naming. So it's a bit fragile for truly general code; I wouldn't recommend it even in your profile script, for example. (Speaking from some painful experience with insufficiently general profile functions here.)
However, consider this sample, which is even shorter and more robust:
function abc($a, $func, $b) { &$func $a $b }
abc 10 { param($a, $b); $a+$b } 20
(Prints out 30.) You can do whatever you'd normally want with that param block, including validation.
abc 10 {param([parameter(Mandatory=$true)]$a, [parameter(Mandatory=$true)]$b); $a+$b} 20
Alternatively, predefine the function like this:
$bcd = { param($a, $b); $a+$b }
And continue as usual:
abc 10 $bcd 20

Unless there's some reason the passed function call needs to be isolated to it's own scope you can simplify that by just passing a script block and invoking it in the function's local scope:
function abc($a, $func, $b) {.$func}
abc 10 {$a+$b} 20
30

Related

Append PowerShell function args to program invoked by the function

I'm wondering how I can append PowerShell function arguments to a program command?
I want something like this:
function foo($x, $y, $z) {
docker run $x $y $z
}
So that calling PS>foo alone would be the equivelant to PS>docker run, and PS>foo a b c to PS>docker run a b c.
This seems like a question that must have an answer here somewhere, but I am unable to find it. I'm not sure whether I'm just phrasing my searches poorly. Apologies in advance if so.
Thanks!
Grab the argument values from $PSBoundParameters.Values:
function foo($x, $y, $z) {
docker run $PSBoundParameters.Value
}
As pointed out in the comments, $PSBoundParameters doesn't guarantee insertion order, an alternative approach would be to take a single argument of arrays with the ValueFromRemainingArguments parameter flag set:
function foo {
param(
[Parameter(Mandatory=$false,ValueFromRemainingArgumemnts)]
[string[]]$dockerArgs
)
$dockerArgs = #('run';$dockerArgs)
& docker $dockerArgs
}

perl a user supplied sub, always returns the text, not the eval

I am a perl newb.
Unfortunately, eval doesn't work the way I'm expecting
My mock example:
my $filter = 'return false;';
my $filterFunc = sub{ eval{ $filter } } ;
while (my $entry = readNextEntry()){
print "Eval:".$filterFunc->( $entry )."\n";
}
When I run this, I get the literal "return false;" being returned from every pass (rather than the function getting evaluated). I've tried a couple of variations, but I'm not hitting the magic combination.
A note on security implications:
As I am the user, I don't have security concerns about what the passed in code does.
My intent is to pull in multiple sources and filter out stuff based on parameters, since I don't know what parameters are going to be useful, I thought I could pass in some text, eval the text into an anonymous function, and run the function for each record (a filter function). As I am the user, I don't have security concerns about what the passed in code does.
You need to do string eval, not block eval.
my $filterFunc = sub{ eval{ $filter } } ;
The eval BLOCK is like a try/catch mechanism. It does not evaluate arbitrary code. It catches errors in the code inside the block. What you want is string eval.
my $filterFunc = sub{ eval $filter };
Here's an example implementation of what I think you are trying to do:
$ perl -E 'my $filter = sub { my $f = shift; eval $ARGV[0]; }; for ( 1 .. 10 ) { say $_ if $filter->($_) }' '$f % 2'
1
3
5
7
9
However, there is no false in Perl. That's not a keyword. Unless you have sub false somewhere, this might give you a syntax error, depending on if you have use strict or not.
You should read up on eval.
If all you want is a $filterFunc that returns something false, use 0 instead. Note that the literal string "return false;" is true in Perl.

Global variable changed in function not effective

I just tried this code:
$number = 2
Function Convert-Foo {
$number = 3
}
Convert-Foo
$number
I expected that function Convert-Foo would change $number to 3, but it is still 2.
Why isn't the global variable $number changed to 3 by the function?
No, I'm afraid PowerShell isn't designed that way. You have to think in scopes, for more information on this topic please read the PowerShell help about scopes or type Get-Help about_scopes in your PowerShell ISE/Console.
The short answer is that if you want to change a variable that is in the global scope, you should address the global scope:
$number = 2
Function Convert-Foo {
$global:number = 3
}
Convert-Foo
$number
All variables created inside a Function are not visible outside of the function, unless you explicitly defined them as Script or Global. It's best practice to save the result of a function in another variable, so you can use it in the script scope:
$number = 5
Function Convert-Foo {
# do manipulations in the function
# and return the new value
$number * 10
}
$result = Convert-Foo
# Now you can use the value outside the function:
"The result of the function is '$result'"

Data types for parameters of subroutines / functions?

In Perl, can one specifiy data types for the parameters of subroutines? E.g. when using a dualvar in a numeric context like exit:
use constant NOTIFY_DIE_MAIL_SEND_FAILED => dualvar 3, 'NOTIFY_DIE_MAIL_SEND_FAILED';
exit NOTIFY_DIE_MAIL_SEND_FAILED;
How does Perl in that case know, that exit expects a numeric parameter? I didn't see a way to define data types for the parameters of subroutines like you do it in Java? (where I could understand how the data type is known as it is explicitely defined)
The whole point of the dualvar is that it behaves as a number or text depending on what you want. In cases where that's not obvious (to you more importantly than to perl) then make it clear.
exit 0 + NOTIFY_DIE_MAIL_SEND_FAILED;
As for explicitly typing parameters, that's not something built in. Perl is a much more dynamic language than Java so it's not common to check/force the type of every parameter or variable. In particular, a perl sub can accept different numbers of parameters and even different structures.
If you want to validate parameters (for an external API for example) try something like Params::Validate
In addition, Moose and Moo allow a certain level of attribute typing and even coercion.
In Perl, scalars are both numeric and stringy at the same time. It is not the variables themselves that distinguish between strings and numbers, but the operators you work with. While the addition + only uses a number, the concatenation . only uses strings.
In more strongly typing languages, e.g. Java, the addition operator doubles as addition and concatenation operator, because it can access type information.
"1" + 2 + 3 is still sick in Java, whereas Perl can cleanly distinguish between "1" + 2 + 3 == 6 and "1" . 2 . 3 eq "123".
You can force numeric or stringy context of a variable by adding 0 or concatenating the empty string:
sub foo {
my ($var) = #_;
$var += 0; # $var is numeric
$var .= ""; # $var is stringy now
}
Perl is quite different from Java in that - Perl is dynamically typed language, because it does not requires its variables to be typed at compile time..
Whereas, Java is statically typed (as you know already)
Perl determines the type of the variable depending upon the context it is used..
There can be only two context: -
List Context
Scalar Context
And the context is defined by the operator or function that is used..
For EG:-
# Define a list
#arr = qw/rohit jain/;
# Define a scalar
$num = 2
# Here perl will evaluate #arr in scalar context and take its length..
# so, below code will evaluate to : - value = 2 / 2
$value = #arr / $num;
# Here since it is used with a foreach loop, #arr will be taken as in list context
foreach (#arr) {
say $_;
}
# Above foreach loop will output: - `rohit` \n `jain` to the console..
You can force the type by:
use Scalar::Util qw(dualvar);
use constant NOTIFY_DIE_MAIL_SEND_FAILED => dualvar 3, 'NOTIFY_DIE_MAIL_SEND_FAILED';
say NOTIFY_DIE_MAIL_SEND_FAILED;
say int(NOTIFY_DIE_MAIL_SEND_FAILED);
output:
NOTIFY_DIE_MAIL_SEND_FAILED
3
How does Perl in that case know, that exit expects a numeric parameter?
exit expect a number as is part of its specification and its behaviour is kind of undefined if you pass it a non-integer value (i.e. you should not do it.
Now, in this particular case, how does dualvar manages to return either value type depending of the context?
I don't know how Scalar::Util's dualvar is implemented but you can write something similar with overload instead.
You certainly can modify the behaviour for a blessed object:
#!/usr/bin/env perl
use strict;
use warnings;
{package Dualvar;
use overload
fallback => 1,
'0+' => sub { $_[0]->{INT_VAL} },
'""' => sub { $_[0]->{STR_VAL} };
sub new {
my $class = shift;
my $self = { INT_VAL => shift, STR_VAL => shift };
bless($self,$class);
}
1;
}
my $x = Dualvar->new(31,'Therty-One');
print $x . " + One = ",$x + 1,"\n"; # Therty-One + One = 32
From the docs, it seems that overload actually changes the behaviour within the declaration scope so you should be able to change the behaviour of some common operators locally for any operand.
If exit does use one of those overloadable operations to evaluate its parameter into a integer then this solution would do.
I didn't see a way to define data types for the parameters of subroutines like you do it in Java?
As already said by others... this is not the case in Perl, at least not at compilation time, except for subroutine prototypes but these don't offer much type granularity (like int vs strings or different object classes).
Richard has mentioned some run-time alternatives you may use. I personally would recommend Moose if you don't mind the performance penalty.
What Rohit Jain said is correct. A function that wants input to follow certain rules simply has to explicitly check that the input is valid.
For example
sub foo
{
my ($param1,$param2) = shift;
$param1 =~ /^\d+$/ or die "Parameter 1 must be a positive integer.";
$param2 =~ /^(bar|baz)$/ or die "Parameter 2 must be either 'bar' or 'baz'";
...
}
This may seem like a pain, but:
The extra flexibility gained generally outweighs the work involved in doing this.
Simply having the correct data type is often not enough to ensure that you valid input, so you end up doing a lot this anyway even in a language like Java.

Why does this line of Perl contain only a variable by itself?

I like perl the more I am getting into it but I had a question about a line I saw in a subroutine in a module I am looking through.
my $var = 1;
....
....
....
....
$var;
What throws me is just seeing that $var all by itself on a line. Is that just a roundabout way of returning 1 ?
Many thanks!
Jane
In perl the value of a block is the value of the last expression in the block. That is just a shorthand for return $var.
EDIT: Purists point out that that blocks in general do not return values (like they do in Scala, for example) so you can't write:
my $x = if (cond) { 7 } else { 8 }; # wrong!
The implicit return value of a subroutine, eval or do FILE is the last expression evaluated. That last expression can be inside a block, though:
sub f {
my $cond = shift;
if ($cond) { 7 } else { 8 } # successfully returns 7 or 8 from f()
}
There is the superficial appearance of the if/else blocks returning a value, even though, strictly speaking, they don't.
Quoting the last line of perldoc -f return:
In the absence of an explicit return, a subroutine, eval, or do FILE automatically returns the value of the last expression evaluated.