Reason my subroutine won't recurse - function

#!/usr/bin/perl
use strict;
use warnings;
use List::MoreUtils 'uniq';
my %functiontable =();
$functiontable{foo} = \&foo;
sub iterate {
my ($function, $iterations, $argument) = #_;
return $argument unless 0 < $iterations;
return $argument unless $function = $functiontable{$function};
my #functioned = $function->($argument);
my #refunctioned = ();
for my $i (0 .. #functioned - 1) {
push #refunctioned, iterate ($function, ($iterations - 1), $functioned[$i]);
}
return uniq #refunctioned;
}
sub foo {
my ($argument) = #_;
my #list = ($argument, $argument.'.', $argument.',');
return #list;
}
my #results = iterate 'foo', 2, 'the';
print "#results";
This prints the the. the,, i.e. it doesn't iterate (recurse). I would expect it to print the the. the, the.. the., the,. the,,.
(I used Smart::Comments to check whether it enters iterate a second time, and it does, but it doesn't seem to do everything in the function.)
I can't figure out why. Can someone please help me figure out why, or propose a fix?

This line:
return $argument unless $function = $functiontable{$function};
doesn't make sense. In your subroutine iterate, $function is a string and $functiontable{$function} is a reference to a subroutine. I am not sure what the purpose of this is: is it to compare against the stored function? is it to use the function referenced by the name $function?
Assuming the latter it would make more sense to simply pass in a reference to a function when you call iterate:
sub iterate {
my ($function, $iterations, $argument) = #_;
return $argument unless 0 < $iterations;
my #functioned = $function->($argument);
my #refunctioned = ();
for my $i (0 .. #functioned - 1) {
push #refunctioned, iterate ($function, ($iterations - 1), $functioned[$i]);
}
return uniq #refunctioned;
}
my #results = iterate($functiontable{foo}, 2, 'the');
print "#results";
output:
the the. the, the.. the., the,. the,,

The problem is this line.
return $argument unless $function = $functiontable{$function};
The variable $function is being repurposed and overwritten from a string (the function name) to a code reference (the function to be executed). Later, it's passed into iterate which faithfully ignores it.
Two things would improve this code and avoid that sort of problem. First is to not repurpose variables, use two variables.
return $argument unless $function_ref = $functiontable{$function_name};
Now the mistake cannot happen. One strong indicator that you're repurposing a variable is that it changes type, like from a string to a code reference.
Note that I threw out $function entirely because it's too generic in this context. Is that the function's name or the function's reference? Neither one is obvious, so make it obvious.
Finally, iterate can be made more flexible by eliminating the function table entirely. Pass in the code reference directly. If you want a function table, write a wrapper.
sub select_iteration {
my($iteration_type, $iterations, $argument) = #_;
my $iteration_code = $iteration_types{$iteration_type};
return iterate($iteration_code, $iterations, $argument);
}

The first time your subroutine iterate is called it translates the subroutine name in $function from a name to a subroutine reference
So the first time iterate calls itself it is passing the subroutine reference, and the line
return $argument unless $function = $functiontable{$function};
will stringify the reference and attempt to find an element of the hash using a key something like CODE(0x23e0838)
Clearly that element doesn't exist, so your unless fails and $argument is returned immediately without continuing the recursion
Update
I would write something like this
#!/usr/bin/perl
use strict;
use warnings;
use 5.10.0;
my %functions = ( foo => \&foo );
sub iterate {
my ($func, $arg, $depth) = #_;
return $arg unless $depth;
map {iterate($func, $_, $depth - 1); } $functions{$func}->($arg);
}
sub foo {
my ($arg) = #_;
map "$arg$_", '', '.', ',';
}
my #results = iterate('foo', 'the', 2);
say "#results";
output
the the. the, the. the.. the., the, the,. the,,

Related

While Iterator in groovy

I'm trying to create a loop to read, for example, 4200 users from 1000 to 1000 but I can't get it to cut when it reaches the end. I tried it with if, for and I couldn't do it.
I have programmed in JAVA but with Groovy I see that the structure is different.
urlUsers = urlUsers.concat("/1/1000");
List<UserConnectorObject> usersList = null;
while({
gesdenResponse = GesdenUtils.sendHttpRequest(urlUsers, "LOOKUP", null,
request.getMetaData()?.getLogin(), request.getMetaData()?.getPassword());
log.info("Users data in JSON: "+gesdenResponse.getOutput())
usersList = GesdenUtils.fromJSON(gesdenResponse.getOutput(), GesdenConstants.USER_IDENTITY_KEY);
usersList.size() == 10;
log.info("List size in JSON "+usersList.size());
}()) continue
Groovy has lots of loop structures, but it is crucial to separate the regular ones (lang built-ins) and the api functions which take closure as an argument
take closure - no plain way to escape
If you want to iterate from A to B users, you can use, for instance,
(10..20).each { userNo -> // Here you will have all 10 iterations
if ( userNo == 5) {
return
}
}
If something outrageous happens in the loop body and you cannot use return to escape, as loop boddy is a closure (separate function) and this resurn just exits this closure. Next iteration will happen just after.
use regular lang built-in loop structures - make use of break/continue
for (int userNo in 1..10) { // Here you will have only 5 iterations
if (userNo == 5) {
break
}
}
It looks like your closure always return falsy because there is no explicit return, and the last statement evaluated is the call to log.info(String) which returns void.
Use an explicit return or move/delete the log statement.

How to check if a function parameter is a string or array in Perl

I'm trying to write a custom validateParameter function with Perl.
I have the following code which also works:
sub validateParameter {
my ($args, $list) = #_;
if ( ref($list) eq "ARRAY" ) {
foreach my $key (#$list) {
if ( not defined $args->{$key} ) {
die "no $key given!";
}
}
}
#elsif ( check if string ) {
#}
}
I want to call my function the following way:
validateParameter({ hallo => "Welt", test => "Blup"}, ["hallo", "test"]);
But I also want to call my function like this:
validateParameter({ hallo => "Welt", test => "Blup"}, "hallo");
I know that Perl only has the following three data-types (scalars, hashes, arrays). But maybe there is a smart way to check if a variable is a string.
How can I check if the given arg is a string?
Update: I somehow missed the end of the question. Just testing ref($list) eq 'ARRAY' will work most of the time, but to properly allow even overloaded objects, you should just try dereferencing the parameter:
if ( eval { \#$list } ) {
# it was an array
}
else {
# assume it is a string
}
Original answer:
You can check a number of things about a parameter:
if ( ! defined $param ) {
# undefined
}
elsif ( defined Scalar::Util::blessed($param) ) {
# object
}
elsif ( ref $param ) {
# reference (return of ref will give the type)
}
elsif ( length do { no warnings "numeric"; $param & '' } ) {
# number
}
else {
# string
}
But all of that (except perhaps the defined check) kind of defeats the purpose of Perl's automatically converting to your desired type and will limit what can be passed (for instance, a string or dualvar where a number is wanted, or an overloaded object where a string, number, or reference is wanted, or tied variables or magic variables such as $!).
You may want to also just look at what Params::Validate can do.
Don't base behaviour on the "type" of arguments because there really isn't such a thing. You will run into problems if you use type-base polymorphism because Perl values often have more than one type.
For example,
The scalar produced by "123" is stored as as string, but Perl doesn't distinguish it from the scalar produced by 123 which isn't stored as a string.
Scalars can contain both a number and a cached stringification of that number. (e.g. my $i = 123; "".$i;)
Scalars can contain both a number and a string (that isn't a stringification of the number). Common examples of these are $! and !1.
A reference to an object that overloads stringification is also a useful string. (e.g. DateTime->now)
A reference to an array may overload %{} to usable as a hash reference. A reference to an hash may overload #{} to usable as an array reference.
And more.
No, there is no way to check if a scalar is a string, as Perl does implicit type conversions depending on context. If you give a number as the second argument to your function, and you use it in a context that requires a string, it will be automatically converted to a string. So, just check if ref($list) is empty - in such case, $list is not a reference, and therefore it is a string or a number, and you don't need to distinguish between those two.

Strange behaviour from MySQL in Powershell

I'm new to PowerShell and have a specific question about working with MySQL in PowerShell.
I got this function:
Function run-mySQLInsertQuery{
param(
$connection,
[string[]]$insertQuery
)
foreach ($command in $insertQuery){
$MySQLCommand = $connection.CreateCommand()
$MySQLCommand.CommandText = $command
$rowsInserted = $MySQLCommand.ExecuteNonQuery()
if ($rowsInserted) {
return $rowsInserted
} else {
return $false
}
}
}
With this version of the function i get the following Error:
Cause:
"The CommandText property has not been properly initialized."
Errorline:
$rowsInserted = $MySQLCommand.ExecuteNonQuery()
I searched for a solution and edited my function a bit to the following (for testing purpose):
Function run-mySQLInsertQuery{
param(
$connection,
[string[]]$insertQuery
)
$abcd = $insertQuery[1]
foreach ($command in $insertQuery){
$MySQLCommand = $connection.CreateCommand()
$MySQLCommand.CommandText = $abcd
$rowsInserted = $MySQLCommand.ExecuteNonQuery()
}
}
With this code, the function executes the query without a problem. My question now is, why? i cant really see a difference, because in $command should be the exact same query like it is in $abcd. Or am I getting something wrong?
EDIT:
As its asked it the comments, here is how i call the function:
[String[]]$statements = ""
foreach($key in $arrayStatus.Keys){
$item = $arrayStatus[$key]
$insertStatus = "INSERT INTO tx_tphbusinessofferings_domain_model_status (status_id, status) VALUES ('$key', '$item')"
$statements += $insertStatus
}
$Rows = run-mySQLInsertQuery -connection $mySQLconnection -insertQuery $statements
The problem is that you are initializing your array (the one you are passing in) with an empty string:
[String[]]$statements = ""
And then adding elements to it... so your first iteration of the passed array is an empty string, which won't work (it'll set the command text as empty, that's the error you are getting). It works on the second code because you are grabbing the second object of the array (which is your insert statement).
Initialize your array to empty and it should work:
[String[]]$statements = #()
Apart from that, your first script always returns on the first iteration, so it'll only work once (not for every insert you pass). Not sure what do you want to return if you are passing in more than one query, but that's up to your design decisions

Repeatable Parameters in Powershell Function (Preferably Linked Parameter Sets)

I am wondering if it is possible (and if so how) to create repeatable (and hopefully linked) parameters in a PowerShell function. This is how am looking for this to work:
function foo()
{
[CmdletBinding()]
Params(
[Parameter(Mandatory=$true,ParameterSetName="Default")]
[Parameter(Mandatory=$true,ParameterSetName="Set1")]
[Parameter(Mandatory=$true,ParameterSetName="Set2")]
[string]$SomeParam1,
[Parameter(Mandatory=$true,ParameterSetName="Set1")]
[Parameter(Mandatory=$true,ParameterSetName="Set2")]
*some magic here, likely, to make this repeatable*
[string]$SomeRepeatableParam,
[Parameter(Mandatory=$true,ParameterSetName="Set1")]
[string]$SomeLinkedParam1,
[Parameter(Mandatory=$true,ParameterSetName="Set2")]
[string]$SomeLinkedParam2
)
Begin
{
*some code here*
}
Process
{
foreach ($val in $SomeRepeateableParam)
{
*some code here using param and its linked param*
}
}
End
{
*some code here*
}
}
And then call this function like so:
foo -SomeParam "MyParam" -SomeRepeatableParam "MyProperty1" -SomeLinkedParam1 "Tall" -SomeRepeatableParam "MyProperty2" -SomeLinkedParam2 "Wide"
and so on, being able to use the repeatable parameter as many times in a single call as I feel like it.
Can this be done? And if so how?
Thanks for your time.
EDIT: For clarity, I don't mean an array parameter, but a repeatable parameter in which the linked parameter sets can be matched to each instance of the repeatable parameter.
Since PowerShell supports arrays as parameter values, there is generally no need to repeat a parameter.
There is no syntactic way to enforce the pairing (linking) of parameter values the way you intend, with repeating instances of the same parameter name, because parameter names must be unique (and even they didn't have to be unique, that alone wouldn't enforce the desired pairing).
You can, however, use parallel array parameters, and enforce their symmetry inside the function, e.g.:
function foo
{
[CmdletBinding()]
Param(
[string] $SomeParam1,
[string[]] $SomeRepeatableParam,
[string[]] $SomeLinkedParam
)
if ($SomeRepeatableParam.Count -ne $SomeLinkedParam.Count) {
Throw "Please specify paired values for -SomeRepeatableParam and -SomeLinkedParam"
}
for ($i = 0; $i -lt $SomeRepeatableParam.Count; ++$i) {
$SomeRepeatableParam[$i] + ': ' + $SomeLinkedParam[$i]
}
}
You would then call it as follows (note the , to separate the array elements):
foo -SomeParam1 "MyParam" `
-SomeRepeatableParam "MyProperty1", "MyProperty2" `
-SomeLinkedParam "Tall", "Wide"

Perl - De- Referencing a Hash

I am just wondering If I can get some help with dereferencing in Perl?
I have a while loop where I am querying a DB and iterating over what I get back. I then write the data I need into a hash and push the hash into an array. This is all forming part of a JSON string.
However, I can only push the reference to the hash and not the hash itself (I've tried all sorts of things), meaning if the loop goes (e.g.) 3 times, I get the same thing appearing 3 times in the JSON I am trying to PUT.
Here is the code:
my $json = new JSON::XS;
my $json_text = JSON::XS->new->decode (shift->content);
my $sig_num = 0;
my %sig_hash;
<MySQL Stuff -removed for readability>
while($query_handle->fetch())
{
$sig_num++;
$sig_hash{position} = 'below';
$sig_hash{signature_text} = $sig;
$sig_hash{signature_name} = 'Signature '.$sig_num;
$sig_hash{signature_default} = JSON::XS::true;
push (#{$json_text->{data}->{mail}->{signatures}}, \%sig_hash);
}
return $json_text;
Thanks for any help!
The hash ref that you're pushing on to the array is scoped at the outer level (outside the while loop). This means there is only one hash being referenced: you are pushing references to the same hash on to the array multiple times. I assume you want a fresh hash for each iteration of the loop. If so, declare my %sig_hash inside the loop rather than outside.
You can experiment with this script to see the difference. First run it as it is; then move the my %h outside the loop and run it again.
my #data;
for (1..3){
my %h; # New hash for each iteration of the loop.
$h{a} = 10 * $_;
$h{b} = 20 * $_;
push #data, \%h;
}
use Data::Dumper;
print Dumper(\#data);
I suggest you use an autovivified anonymous hash delared, as FMc explains, within the while loop. The code becomes simpler that way, and becomes
my $json = new JSON::XS;
my $json_text = JSON::XS->new->decode(shift->content);
my $sig_num = 0;
while ($query_handle->fetch) {
my $sig_hash;
$sig_hash->{position} = 'below';
$sig_hash->{signature_text} = $sig;
$sig_hash->{signature_name} = "Signature ".++$sig_num;
$sig_hash->{signature_default} = JSON::XS::true;
push #{$json_text->{data}{mail}{signatures}}, $sig_hash;
}
return $json_text;
or if you prefer you can build and push an anonymous hash directly onto the stack without assigning it to a variable
my $json = new JSON::XS;
my $json_text = JSON::XS->new->decode(shift->content);
my $sig_num = 0;
while ($query_handle->fetch) {
push #{$json_text->{data}{mail}{signatures}}, {
position => 'below',
signature_text => $sig,
signature_name => "Signature ".++$sig_num,
signature_default => JSON::XS::true,
};
}
return $json_text;