Functional Programming in PowerShell

Functional Programming in PowerShellClean, concise PowerShell using functional abstractionsChristopher KuechBlockedUnblockFollowFollowingJun 6PowerShell isn’t a purely functional language, but PowerShell very elegantly integrates some functional concepts into its semantics.

By leveraging Functional Programming concepts to PowerShell, you will be able to spend more time writing clean idiomatic PowerShell and less time reverse engineering your scripts.

Improve the flow of your programs by applying Functional Programming patternsFunctional vs ImperativeFunctional Programming is often contrasted to Imperative Programming.

Functional Programming encourages maintaining logic in functions that the interpreter evaluates, while Imperative Programming encourages maintaining logic as a sequence of statements that the interpreter evaluates.

The difference between these two paradigms results in differing patterns, benefits, and performance considerations.

PatternsFunctional Programming style encourages several patterns that can be implemented in PowerShell, such as —Pure functions, where the function is solely dependent on its input and therefore always has the same output.

For example, square is a pure function, as the output is solely in terms of $x.

comparison of impure functions vs pure functionsDeclarative style, that maximizes the use of expressions and minimizes the use of statements.

For example, this imperative if / else block can be simplified to a single declarative expression.

comparison of imperative code vs declarative codeRecursion, where a function calls itself instead of relying on for or foreach.

Recursion can simplify code when handling recursive data structures — data structures with properties of the same type as themselves — such as in the case of a tree traversal function, where the function can be applied to the root node of the tree, the left node property of the node, and the right node property of the node equivalently.

comparison of iterative code vs recursive codeImmutability, where data is never modified and is instead duplicated with the modification made to the duplicate.

Immutability helps avoid shared state in different parts of your script and therefore minimize debugging.

Value types (numbers, strings, booleans) are always immutable, but arrays and hashtables are mutable.

You can treat mutable types as immutable types by adhering to the copy on write convention, where you only clone the data and modify it for write operations, but read operations occur without cloning.

implementation of functions for immutably reading and writing from a hashtableHigher-order functions, where functions receive a function as a parameter or return a function.

Higher-order functions keep code simple to write and debug.

These are discussed in detail later.

Data-first design, where you first consider what data you have and what data you want, then apply higher-order functions to transform your data into the form you desire.

In contrast, object-oriented design focuses on the functions first (method interfaces) and defines the data (classes) later.

Monads, where side-effects like state modification and errors are encoded in the return type of a function to keep the function pure.

These are discussed in detail later.

BenefitsAdhering to functional patterns will provide some strong benefits —Stateless— if you keep your logic in pure functions and maintain immutability, you will avoid side-effects (like modifying the state of variables or using temporary files) in your code.

Side effects are much harder to unit test and debug than pure functions and immutable code.

If your code does not contain side effects, it will also be idempotent, so you will not face unwelcome surprises when executing your code multiple times.

Side effects are much harder to unit test and debug than pure functions and immutable codeClean abstractions—Higher-order function abstractions allow for better separation of concerns, as you separate the “what” from the “how”.

Implementing your logic modularly and generically allows you to write simple test cases for your generic implementation while actually calling more complex implementations.

PerformancePeople often avoid functional style because of performance concerns over adding otherwise unnecessary function calls; however, considering PowerShell scripts often contain network calls and other much longer operations, the performance hit is negligible.

Further, PowerShell’s native pipeline processing functions that support functional patterns (ForEach-Object, Where-Object, etc.

) are more optimized than equivalent custom PowerShell implementations.

First-Class FunctionsPowerShell supports first-class functions, which means you can assign functions to variables, pass them as parameters, and ultimately use functions as you would objects or primitive values.

ScriptBlocks are PowerShell’s first-class functions.

First-class functions are essential to functional style because they facilitate quick function definitions.

Defining ScriptBlocksA ScriptBlock consists of an optional Param definition and a series of statements, all wrapped in curly braces.

Because ScriptBlocks are first-class functions, you can assign the expression to a variable.

Addition function implemented as a scriptblockYou may find this syntax a little verbose when compared to defining a function.

You can equivalently access the ScriptBlock from the function with the $function:<function-name> syntax.

Deriving a scriptblock from a functionYou can turn any script into a ScriptBlock and serialize the ScriptBlock back to a script.

Creating a scriptblock from a script fileInvoking ScriptBlocksYou can invoke your ScriptBlock with write access to variables in your current scope using .

or without write access to your current scope using &.

Comparison of the two ways to invoke a scriptblockWhen defining a ScriptBlock, you may want to reference variables in the parent scope.

Unfortunately, if you invoke the ScriptBlock somewhere without access to that scope, or if the value of the variable unexpectedly changes, your script will error.

In this case, you can capture the current value of the variable in a closure to ensure it always references the expected value.

Capturing values from the parent scope with a closureHigher-Order FunctionsA higher-order function is a function that either receives a function as a parameter or returns a function.

PowerShell supports a variety of higher-order functions, usually implemented using PowerShell filter functions.

Using these higher-order functions can greatly simplify your code and even improve the performance of your code on large datasets.

Filter functionsIdiomatic PowerShell encourages the use of filter functions.

filter functions are a convenient way of creating a function that accepts pipeline input and consists solely of a process block.

Example of a filter functionfilter functions and process blocks are more optimized than manually looping through an array, so use them whenever possible.

MapForEach-Object is the PowerShell implementation of the Map higher-order function, which takes a mapping function and transforms each item in a list using the mapping function.

A filter invoked directly in a pipeline is also a Map higher-order function; note that a filter is not a filter higher-order function.

filter functions are most often used as scriptblock-literals when callingForEach-Object .

This scenario is so common that ForEach-Object has a single-character alias: %.

Comparison of filter vs ForEach-ObjectOften, you will simply want to map all elements to one of their properties.

ForEach-Object allows you to provide the property name instead of a filter scriptblock.

Simplifying mapping functions to a single getter propertyFilterA filter higher-order function is a function that takes as argument a “predicate” — a function that returns true or false for a given item.

The predicate is applied to each item in the input list to determine if the value should be added to the output list.

Where-Object is PowerShell’s Filter implementation.

Where-Object applies a filter function to each value in the pipeline, writing the value to output if the filter function returns a truthy value.

It is so useful that it has a single character alias: ?.

Example of Where-Object — PowerShell’s “filter” higher-order functionGroup-ObjectGroup-Object is a higher-order PowerShell function that uses its passed-in filter function to generate an aggregation key for the object.

It aggregates elements in the pipeline into group objects by the aggregation key.

A group object contains a Name property containing the aggregation key, a Group property containing all the objects with that key, and a Count property describing the size of the Group property array.

Example of Group-ObjectSort-ObjectSort-Object is a higher-order PowerShell function that uses its passed-in filter function to generate a sorting key for the object.

It sorts the array by the sorting key.

Like the other native higher-order PowerShell functions, Sort-Object can accept a property in place of an equivalent scriptblock.

Example of Sort-ObjectCompressThe Compress higher-order function, (a.

k.

a.

reduce, fold, …) takes as argument a reducer function and a list and returns a list.

The reducer function takes two arguments — an accumulator and a list item — and returns a new accumulator value, that is immediately passed along with the next item in the list to the reducer function.

Example of Compress-Object from “functional” moduleCompress is probably the most difficult higher-order function to conceptualize, but it is the most important list processing higher-order function because all other list processing higher-order functions can be implemented using Compress.

Any higher-order list processing function can be implemented with Compress-ObjectAll the previous functions we have discussed use PowerShell filter functions, which are single-parameter functions that receive their single parameter as $_.

Compress is different and requires a two-parameter function, with parameters bound as $aand $b.

If your function accesses values other than $a and $b, you often need to explicitly declare the params with Param($a, $b) to tell the higher-order function to provide access to the parent scope.

Implicit arguments vs explicit arguments when invoking Compress-ObjectCompress-Object is not a native PowerShell function.

It is published in the functional PowerShell Gallery module, along with other PowerShell functions for facilitating Functional Programming style in PowerShell.

You can find more information about the module on GitHub.

Function CompositionFunction composition allows you to wrap multiple chained function calls into a single function.

For example, if we are adhering to a data-first Functional Programming style, we will often find ourselves with many sequentially applied mapping functions to convert our data to other forms without side effects.

For convenience, we could wrap these functions in a single function.

A naive attempt at function compositionThis implementation is not ideal because while we are applying sequential steps (aToB, bToC, cToD), we are calling them in reverse order (cToD, bToC, aToB) and balancing the parenthesis to compensate.

We can avoid this by using a higher-order compose function, such as the Merge-ScriptBlock function from functional.

Using Merge-ScriptBlock from “functional” module to compose functionsMerge-ScriptBlock will give you a reusable function, but if you do not need a reusable function and simply need to compose functions once within an expression, you can apply a similar pattern with ForEach-Object.

Composing functions ad-hoc with pipelinesAs we learned earlier, Imperative Programming pattern consists of a sequence of steps, which has some drawbacks.

This pipeline pattern allows you to implement a sequence of pipelined steps without having any side effects or confusing flow — the main drawbacks of Imperative Programming.

It’s easy to see why this pattern is so prevalent in PowerShell.

CallbacksWhile most PowerShell commands are synchronous, meaning the PowerShell interpreter waits for the previous command to complete before starting the next command, an asynchronous action is an action that is invoked without waiting for the action to complete before proceeding to the next step.

In PowerShell, asynchronous actions are Jobs: scriptblocks that run in a separate process.

Jobs are typically created using the Start-Job higher-order function that takes as a parameter the function to execute, then executes that function asynchronously, returning a Job object for controlling the lifecycle of the function execution.

Creating a Job asynchronouslyIf a function is executed asynchronously, we will not know when the function completes.

So what happens if we want to execute another command after the function completes?.We can use a callback function to execute some logic after our original action completes.

We can abstract this into a higher-order function.

Create a job with callbackWhile this pattern is common in some other languages, PowerShell allows you to resynchronize Jobs, so it’s much more idiomatic to simply interact with the Job.

Synchronizing jobsStill, using callbacks can help you avoid “writing everything twice”; for example, if you want to run the same callback after every action.

A practical example of using a callback in PowerShellMinimizing Side-EffectsWith a functional paradigm, we try to avoid side-effects like throwing an error or modifying a local variable.

PowerShell allows us to isolate imperative code with scriptblocks and handle errors functionally with a monad.

Containing the ProblemSometimes you cannot initialize a variable without using mutable or local variables.

In that case, you can isolate the initialization code by defining it in ascriptblock and invoking the scriptblock with & to ensure the scriptblock only has read access to the parent scope.

Containing imperative code in functional codeMonadsOne of the most common program side-effects is throwing an error.

For example, while the division function seems like a pure function, it actually can have side-effects (i.

e.

throwing an exception) if the denominator is 0.

A seemingly pure function can still have unexpected side effectsWe want to write defensive PowerShell code and throw errors as early as possible if we detect that our program may reach a bad state; as such, we always set $ErrorActionPreference = "Stop" at the top of our scripts to direct the interpreter to throw any error the interpreter encounters.

Often, we want to handle an error and proceed conditionally based on if an error occurs or depending on some property of the error.

Traditionally, we use try / catch / finally to enable this conditional control flow.

Imperative error handlingtry / catch / finally is a sequence of control-flow statements and therefore imperative.

As an alternative, PowerShell allows you to return a monad from your function instead of throwing an error.

A monad is a method of maintaining state in purely functional code by passing state around as an object.

This specific monad is the Nullable[T] monad — the monad’s value will either be $null or of type T.

(Note that the [T] is the PowerShell syntax for indicating that Nullable is a parametric type with parameter T).

A $null value of the monad indicates that the function encountered an error, while a monad value of type T indicates that the function did not encounter an error.

A monad is a method of maintaining state in purely functional code by passing state around as an object.

In PowerShell, the default ErrorActionPreference is Continue, so if a function encounters a non-terminating error, it should return the Nullable monad and write the error to the error stream, but continue to the next statement.

As mentioned above, we typically apply defensive PowerShell principles and set ErrorActionPreference to Stop, which directs our functions to throw an error and not continue to the next statement, not continuing to the return statement and therefore not throwing an error.

This is good default behavior, but if we want to catch the error with functional code, we can direct the cmdlet to return the Nullable monad by appending -ErrorAction SilentlyContinue to the parameters.

We can enable this behavior in our own functions by converting our functions into cmdlets with the CmdletBinding attribute.

Imperative error handling with try/catch vs Functional error handling with a monadBear in mind that not all errors can be handled with ErrorAction settings and that there are still occasional cases where you will find you need try / catch to handle the error (at least for now).

Next StepsPlay with “functional”Start applying Functional Programming style to your code!.Install the functional PowerShell module to get all the higher-order functions described above that are not installed with PowerShell.

Ad-hoc querying with higher-order functionsTake advantage of Export-Csv and Import-Csv to store information about your systems and ad-hoc query them with the higher-order functions for practice whenever possible.

PowerShell is invaluable for collecting and serializing data about systems — if you can start querying and aggregating this data with higher-order functions, you will be able to prescribe more compelling data-driven solutions to your issues.

More conceptsYou can also begin applying other advanced functional PowerShell concepts like type checking and custom type conversion functions.

.

. More details

Leave a Reply