Is there a short circuit 'or' that returns the first 'true' value?
Asked Answered
C

4

5

Scheme has a short-circuiting or that will return the first non-false value:

> (or 10 20 30)
10
> (or #f 20 30)
20
> (or #f)
#f

It does not evaluate its arguments until needed.

Is there something like this already in PowerShell?

Here's an approximation of it:

function or ()
{
    foreach ($arg in $args) {
        $val = & $arg; if ($val) { $val; break }
    }
}

Example:

PS C:\> or { 10 } { 20 } { 30 }
10

Example:

PS C:\> $abc = $null

PS C:\> or { $abc } { 123 }
123

PS C:\> $abc = 456

PS C:\> or { $abc } { 123 }
456
Cyzicus answered 23/2, 2014 at 22:34 Comment(1)
Is this what you mean? #5360645Diabolism
T
9

You could do something like this:

10, $false, 20 | ? { $_ -ne $false } | select -First 1

The result is either the first value from the input list that isn't $false, or $null. Since $null is among the values that PowerShell treats as $false in comparisons, the above should do what you want.

Tirpitz answered 23/2, 2014 at 23:34 Comment(1)
This is by FAR the best answer and most useful.Hilly
M
3

As far as I know, there isn't anything like this built in. I think your function looks pretty good.

It might be more idiomatic to make it take pipelined input:

function or
{
    foreach ($x in $input) {
        $val = & $x; if ($val) { $val; break }
    }
}

Example:

PS > $abc = $null
PS > { $abc },{ 123 } | or
123    
PS > $abc = 456
PS > { $abc },{ 123 } | or
456
Minstrelsy answered 23/2, 2014 at 23:28 Comment(0)
T
3

You're trying to make PowerShell use a Scheme-like syntax by way of your function. Don't do this. Write idiomatic PowerShell. Trying to coerce one language into looking like another language just makes things harder on yourself, introduces lots of room for bugs, and will confuse the %$^%&^*( out of whoever has to maintain your code after you're gone.

PowerShell does appear to short-circuit. Put this code in the ISE and set a breakpoint on the write-output lines in each function, then start the debugger (F5):

function first () {
    write-output "first"
}

function second() {
    write-output "second"
}

$true -or $(first) -or $(second);
$false -or $(first) -or $(second);
$false -or $(second) -or $(first);   

$true evaluates to true (obviously), so it doesn't attempt to process the expression beyond that point. When the next to last line processes, only the breakpoint in first processes. When the last line processes, only the breakpoint in second() is hit.

Tyree answered 24/2, 2014 at 1:22 Comment(2)
Hello alroc. The -or operator does not return the value of the first non-false expression. It always returns either $true or $false.Cyzicus
That goes back to your attempting to write PowerShell that pretends to be Scheme instead of writing idiomatic PowerShell.Tyree
O
1

As long as you give the function a good name, I think creating such a function is perfectly idiomatic Powershell. One tweak I would make to OP's implementation is to make passing in a script block optional:

function Select-FirstValue {
    $args |
        foreach { if ($_ -is [scriptblock]) { & $_ } else { $_ } } |
        where { $_ } |
        select -First 1
}

Then the caller only has to add { } brackets around arguments that could have side-effects or be performance costly.

I'm using such a function as a simple way to provide default values for params from a config file. It looks something like:

function Do-Something {
    param([string]$Arg1)

    $Arg1 = Select-FirstValue $Arg1 { Get-ConfigValue 'arg1' } 'default arg1 val'

    [...]
}

This way, the config file only is only attempted to be read if the user does not pass in the argument.

Ottoottoman answered 15/8, 2015 at 21:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.