Much to my suprise, Powershell holds up as a decent, testable language, with a few idiosyncrasies.
So far, I’ve been able to refactor Powershell to make use of its pervasive stream handling, and there are some useful tricks that can make Powershell look like other functional-style languages you know and love ( ruby, scala, LINQ, etc).
- There are some nifty aliases:
? { test } | % { process; result } | % { process_result; finish}? is an alias for ‘where-object’
% in an alias of for-each. - More syntactic strangeness
@{}creates an empty hashmap, but
@[]
wraps things into a list context. You’ll notice that when the following expression doesn’t work like you’d expect:
(gci -path $path -filter "*.exe").Count
this does:
@[(gci -path $path -filter "*.exe")].Count
- There is no built-in inject/foldl/aggregate function:
function Fold-Left { [cmdletbinding()] param ( [parameter(mandatory, position = 1)]$inital, [parameter(mandatory, position = 2)][scriptblock]$action ) begin { $sum = $inital} process {$sum = & $action $sum} end { write-output $sum } }We can use it to reduce intermediate variables. This will return a map containing symbol counts:
$counts = a,a,a,a,b,c,d,e | Fold-Left @{} { param($counts) $counts.$_ = $counts.$_ + 1} #$counts @{ "a" => 4; "b" => 1; .... } - For-each collects the return values of the for-each block.
(1,2,3 | % { $_ + 1}) -eq @[2,3,4]However, if you want to modify items in place, it can be error-prone to remember the return value:
@{"ting" = "hi"}, @{"thing" = "lo"} | % { $_.tong = $_.ting + "there"; $_} #note the return value - Tap is a useful function, here it is
function Tap { [cmdletbinding()] param([parameter(position=1)][scriptblock]$action) process {&action; write-output $_} } @{"ting" = "hi"}, @{"thing" = "lo"} | Tap { $_.tong = $_.ting + "there"} #note the return value is now ignored.This is a kestrel, for introducing side-effects. If your style is towards immutability you might want to use it differently.
For instance, it’s great for logging:"command1", "command2" | Tap { Write-Host $_ } | Invoke-Expression $_And for turning it off, use…
function Dont() { [cmdletbinding()] param([parameter(position=1)][scriptblock]$action) process {write-output $_} } "command1", "command2" | Dont { Write-Host $_ } | Invoke-Expression $_