4

I've written this (which works):

function ForAll {
    BEGIN {
        $allTrue = $true
    }
    PROCESS {
        if ($_ -lt 1) { $allTrue = $false }  
    }
    END {
        $allTrue
    }
}

$aList = (0..4)
$bList = (1..4)

$aList | ForAll # returns false
$bList | ForAll # returns true

But what I want to do is replace the ($_ -lt 1) with a function called something like $predicate that I pass into the ForAll function. I can't seem to get this to work. Any ideas?

Bohdan Szymanik
  • 141
  • 1
  • 7

3 Answers3

3

Use [scriptblock], imho it is much easier than using functions here. That's the task why scriptblocks were invented.

function ForAll([scriptblock]$predicate) {

    BEGIN {
        $allTrue = $true
    }
    PROCESS {
        if (!(& $predicate $_)) { $allTrue = $false }  
    }
    END {
        $allTrue
    }
}

$aList = (0..4)
$bList = (1..4)

$aList | ForAll {$args[0] -le -10 } # returns false
$bList | ForAll {$args[0] -le 10 } # returns true

$args[0] denotes the first argument passed to the scriptblock - in this case it is $_.

stej
  • 28,745
  • 11
  • 71
  • 104
  • Stej, I notice in this that the [scriptblock] isn't actually required if $_ is substituted for $args[0]. I was getting myself quite confused the other night trying to get this to work. In the end the shortest version I've got is just a slight change on yours. function ForAll($predicate) { ...} $aList = (0..4) $bList = (1..4) $aList | ForAll {$_ -gt 0 } # returns false $bList | ForAll {$_ -gt 0 } # returns true $bList | ForAll {$_ -gt 0 -and $_ -lt 5 } # returns true – Bohdan Szymanik Jan 23 '10 at 04:50
  • Yes, it's definitelly true. You may use the automatic variable - it looks the same as foreach-object cmdlet. However, I had in some more complicated scripts problems with $_, so I hesitate to use $_. – stej Jan 24 '10 at 20:59
3

If you're on PowerShell 2.0, you can use param with a scriptblock to declare parameters e.g.:

function ForAll {
    param(
        [Parameter(Position=0,Mandatory=$true)]
        [scriptblock]
        $Expression,

        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [psobject]
        $InputObject
    ) 

    Begin { $allTrue = $true } 

    Process { 
        foreach ($obj in $InputObject) {
            if (!(&$Expression $obj)) { $allTrue = $false }
        }
    } 

    End { $allTrue } 
} 

$aList = (0..4) 
$bList = (1..4) 

ForAll {param($val) $val -gt 0} (0..4) # returns false 

$aList | ForAll {param($val) $val -gt 0} # returns false 
$bList | ForAll {param($val) $val -gt 0} # returns true 
$aList | ForAll {param($val) $val -ge 0 -and $val -le 4} # returns true
Keith Hill
  • 194,368
  • 42
  • 353
  • 369
  • 1
    I've always wanted to see correct processing of parameter that can be from pipeline and as argument as well. I'll remember the trick with foreach. Is there any reason why not to pipe it? $InputObject| % { if (!(&$Expression $_)) { $allTrue = $false } } } – stej Jan 19 '10 at 20:09
  • Nope - that would work just as well. When I write functions I tend to fall back into my C# (non-pipelined) ways for loops. :-) Small pipelines are easy enough to read but when they go on for line after line, I find explicit loops easier to read when I come back to the script. – Keith Hill Jan 19 '10 at 20:25
  • I don't understand, why do you need the foreach? i thought process was meant to take in individual values from the pipeline? Is the $InputObject bound to just a single item inside process? (because of the attribute) if that's the case why use begin, process, end at all? – Jake May 13 '11 at 16:37
  • True but every now and then someone will try to send a collection down the pipe e.g. `,$aList | ForAll { ... }`. The foreach walks collections sent in and for scalar values, the loop executes just once. – Keith Hill May 13 '11 at 17:54
0

For efficient processing we would use a filter instead of a function and we would "break" as soon as we found the spurious element. This also stops all upstream process too:

filter ForAll([scriptblock] $predicate) {
    PROCESS {
        if ( @($_ | Where-Object $predicate).Length -eq 0 ) {
            $false;
            break;
        }
    }
    END {
        $true;
    }
}

Use it idiomatically:

(0..4) | ForAll { $_ -gt 0 }     # returns false
(1..4) | ForAll { $_ -gt 0 }     # returns true
Tahir Hassan
  • 5,715
  • 6
  • 45
  • 65