Functional PowerShell with Classes

I promise it’s not an oxymoron

Christopher Kuech
May 9, 2019 · 4 min read

Object-Oriented Programming and Functional Programming paradigms may seem at odds with each other, but they are really just two language paradigms supported by PowerShell. Virtually all programming languages, functional or otherwise, have a means of extensibly binding names to values; Classes, like structs or records, are simply one approach to doing so. As long as we restrict our usage of Classes primarily to binding names and values and avoid heavy object-oriented programming concepts like inheritance, polymorphism, or mutability, we can leverage Classes without complicating our code. Further, by adding immutable type conversion methods, we can elevate our functional code with Classes.

Casting magic

Casting is one of the most powerful features in PowerShell. When you cast a value, you can trigger abstracted initialization and validation code in your application. For example, just casting a string with [xml] will trigger code to parse the string into a complete xml tree. We can leverage classes to implement the same features in our own code.

If you don’t have a constructor, you can cast hashtables to your class type without any modification to your class. Be sure to add validation attributes to fully leverage this pattern. We can also use custom types for the type of our class properties, to trigger even more validation and initialization logic.

Casting also enables cleaner output. Compare the output from an array of Cluster hashtables piped to Format-Table, versus casting those hashtables to [Cluster] before for piping to Format-Table. The properties are always listed in the order they are defined on the class. Be sure to include the hiddenkeyword before any properties that should not be visible when outputting the table.

Image for post
Image for post

If you have a single-argument constructor, casting a value to your class type will pass the value to your single-argument constructor, where you can initialize your class instance.

You can also override the [string] ToString() method on the class to define the conversion logic for converting the object to a string, such as when including the object in string interpolation.

Casting enables safe input from serialization formats. The examples below will fail if the data does not meet our specifications in Cluster.

Validating serialized data

Casting in your Functional code

Functional programs define data structures first, then implement the program as a series of transformations upon the immutable data structures. While it may seem contradictory, classes, thanks to type conversion methods, actually facilitate functional code in PowerShell.

Many people coming from a C# or similar background write PowerShell that resembles C#. If you do this, you are not leveraging functional concepts and would probably benefit from doubling down on object-oriented programming in PowerShell or learning more about functional programming.

If you rely heavily on transforming immutable data using pipelines (|) , Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, etc, you have a more functional PowerShell style and will benefit from using PowerShell classes in a functional way.

Casting, though it uses different syntax, is just a mapping function between two domains. We can map values of an array in a pipeline using ForEach-Object.

In the example below, the Node constructor is run every time we cast to Datum, allowing us to abstract away a fair amount of code. As a result, our pipeline only focusses on declarative data querying and aggregation while our classes focus on data parsing and validation.

Example combining classes with pipelines for implementing separation of concerns in pipelines

Packaging the class for reuse

Unfortunately, classes cannot be exported from modules in the same way as functions or variables; however, there are some workarounds. Assuming your classes are defined in a file ./my-classes.ps1:

  • You can dot-source the file containing the classes: . ./my-classes.ps1. This will execute my-classes.ps1 in your current scope, defining all the classes in your file.
  • You can create a PowerShell module that exports all your user-facing cmdlets and set ScriptsToProcess = "./my-classes.ps1" in your module manifest file, which will similarly run ./my-classes.ps1 in your environment.

Whichever approach you take, keep in mind that the PowerShell type system cannot resolve types if they come from two separate places. Even though you have two identical classes with the same names and all the same properties, if they are loaded from two separate locations, you might find yourself facing confusing type issues.

The best way to avoid type resolution issues is to never expose your classes to users. Rather than expect your user to import the class type, instead export a function from your module that abstracts away the need for directly accessing the class. For example, for Cluster, we would export a function New-Cluster that supports user-friendly parameter sets and returns a Cluster.

Further Reading

Image for post
Image for post

Follow us on Twitter 🐦 and Facebook 👥 and join our Facebook Group 💬.

To join our community Slack 🗣️ and read our weekly Faun topics 🗞️, click here⬇

Image for post
Image for post

FAUN

The Must-Read Publication for Creative Developers & DevOps Enthusiasts

Sign up for FAUN

By FAUN

Medium’s largest and most followed independent DevOps publication. Join thousands of aspiring developers and DevOps enthusiasts Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Christopher Kuech

Written by

DevOps, SRE, Automation, PowerShell @Microsoft

FAUN

FAUN

The Must-Read Publication for Creative Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.

Christopher Kuech

Written by

DevOps, SRE, Automation, PowerShell @Microsoft

FAUN

FAUN

The Must-Read Publication for Creative Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store