ca9163d9
ca9163d9

Reputation: 29209

Monad in PowerShell?

I have the following code

function Get($url) {
    $x = Invoke-RestMethod $url 
    # ....
    $result # return a json which some values (id, name, color) and a list
}

Get "http://...." |
% {
    $id = $_.id
    $url = "${$_.url}/xxx"
    Get $ur  |
    % {
        $name = $_.name
        $url = $_.url
        Get $url | 
        % {
            $color = $_.color
            $url = $_.url

            Get $url | % {
                # other layer omitted
            }
        }
    }
}

The code looks bad. It seems the embedded layers should be resolved by monad. Is there a way to do it in PowerShell?

The following shows a pseudo F# code.

myMonad {
    let! (id1, _,     _,      urls1, _    ) = Get ["http://..."]
    let! (_,   name2, _,      urls2, _    ) = Get urls1
    let! (_,   _,     color3, urls3, names) = Get urls2
    // ....
    printfn "%d %s %s %A" id1, name2, color3, names
}

Upvotes: 2

Views: 656

Answers (2)

mklement0
mklement0

Reputation: 439727

PowerShell is clearly not a function language, though it has some functional traits and its many constructs and dynamic nature allow you to emulate functional features:

The code below defines a recurse-and-aggregate function named recurse that can invoke your Get function as follows in order to aggregate properties Id, Name, and Color from recursive calls to Get via each returned object's .URL property:

recurse -Function  Get                 <# target function #> `
        -Argument  http://example.org  <# input URL #> `
        -RecurseOn URL                 <# what result property to recurse on #> `
        -PropertyNames Id, Name, Color <# what properties to aggregate successively, 
                                          in each iteration #>

recurse source code with sample code:

# A fairly generic recurse-and-aggregate function that aggregates properties from
# result objects from recursive calls to a given function based on a single result property.
function recurse($Function, $Arguments, $RecurseOn, $PropertyNames, $outProperties = [ordered] @{ }) {

  # Call the target function with the specified arguments.
  $result = & $Function $Arguments

  # Split into the names of the current and the remaining properties to aggregate.
  $propName, $remainingPropNames = $PropertyNames

  # Add the value of the current property of interest to the output hashtable, if present.
  if ($null -ne $result.$propName) {
    if ($outProperties.Contains($propName)) { # not the first value
      if ($outProperties.$propName -is [array]) { # already an array -> "extend" the array.
        $outProperties.$propName += $result.$propName
      }
      else { # not an array yet -> convert to array, with the previous value and the new one as the elements. 
        $outProperties.$propName = $outProperties.$propName, $result.$propName
      }
    }
    else { # first value -> assign as-is
      $outProperties.$propName = $result.$propName
    }
  }

  if ($remainingPropNames) {
    # Recurse on the value(s) of the property specfied with -RecurseOn
    foreach ($newArgument in $result.$RecurseOn) {
      $null = recurse -Function $function -Argument $newArgument -RecurseOn $RecurseOn -PropertyNames $remainingPropNames -outProperties $outProperties
    }
  }

  # Return the aggregated properties.
  $outProperties

}

# Sample input function:
# It makes a REST call to the given URL and returns the
# result object.
function Get($url) {
  Write-Host "Get $url"
  $id = 1
  $name = ''
  $color = ''

  if ($url -eq 'http://example.org') { 
      $urls = 'http://example.org/1', 'http://example.org/2'
      $id = 1
  }
  elseif ($url -match 'http://example.org/\d$') { 
      $urls = "$url/a", "$url/b" 
      $name = 'test'
  }
  elseif ($url -match 'http://example.org/\d/a') {
      $urls = '...'
      $name = 'test'
      $color = "[color of $url] #1", "[color of $url] #2"
  }
  elseif ($url -match 'http://example.org/\d/b') {
      $urls = '...'
      $name = 'test'
      $color = "[color of $url] #1", "[color of $url] #2"
  }

  [pscustomobject] @{
      URL = $urls
      Id  = $id
      Name = $name
      Color = $color
  }
}

# Call the recurse-and-aggregate function, passing it the name of the Get() 
# function, an input URL, what result property to recurse on, and a list of properties to aggregate.
# The output is an ordered hashtable containing the aggregated property values.
recurse -Function  Get                 <# target function #> `
        -Argument  http://example.org  <# input URL #> `
        -RecurseOn URL                 <# what result property to recurse on #> `
        -PropertyNames Id, Name, Color <# what properties to aggregate successively, 
                                          in each iteration #>

The above yields:

Name                           Value
----                           -----
Id                             1
Name                           {test, test}
Color                          {[color of http://example.org/1/a] #1, [color of http://example.org/1/a] #2, [color of http://example.org/1/b] #1, [color of htt…

Upvotes: 1

js2010
js2010

Reputation: 27516

Well, you could insert F# code into Powershell with Add-Type. See Example 7: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type?view=powershell-5.1

Upvotes: 0

Related Questions