Thoughts on Functional Programming

Context

So there I was, writing a function in PowerShell for work. We work a lot in Active Directory and one of the most painful tasks is copying group memberships from one user to another. Sometimes we want to copy them all across, sometimes just a few, or sometimes none at all!

I had written a UI wrapper using WPF in PowerShell so I had the UI ready to go, I just needed the function. The logic itself is fairly simple:

  1. User (me) clicks the button to start the action
  2. User (again, me) Selects the source user
  3. User (you guessed it, me) Selects the target user
  4. Pull groups from source user
  5. Pull groups from target user
  6. Filter out groups that target user is already a part of
  7. User (haha) selects which of the remaining groups to copy over
  8. Selected groups are assigned to target user

8 steps; Might seem like a bit of work, but consider that the UI utility already had the ability to perform steps 1, 2, 3, 4 and 5.

So here's where the mind started thinking...

The Birth of a Function

We have a function:

function Copy-UserGroups {	...  }

What exactly should this function be responsible for? Is this function even correct?

Should we call this function when the button is clicked? Should clicking the button first get the two users, then call this function with the two users as parameters? Should they be all purely functional? It was quite a dilemma.

On the one hand, the function sounds like it takes a source user, and simply copies all of the groups over to a figurative target user. Not exactly what we want... Alright so let's rename it, how about...

function Add-ADUserGroups {  ...  }

We could imagine this function taking a list of groups, a target user, and simply adding them to the user's profile. Alright, but then we have to make additional functions to support this. We now need a list of groups to add, so how about...

function Get-FilteredGroupsFromSourceAndTarget {	...  }

it's a bit of a mouthful, probably gets the point across. Don't forget documentation you think to yourself! Alright so how about something simpler:

function Select-FilteredUserADGroups {
    <# this function does blah blah blah #>
	...
}

Sure, I could imagine this function taking a source and target user, getting their groups, filtering them, asking the user to filter it further.. then finally returning this list as a result. But it feels dirty; it feels like the function is carrying all of the weight! Getting the groups, filtering them, prompting the user to filter them further its just, too much! Alright, so lets split it again..

function Select-UniqueUserADGroups {  ...  } <# Take a source and target user #>
function Select-UserApprovedGroups {  ...  } <# Have the user filter a list manually #>
...
function Add-ADUserGroups {  ...  }

The Struggle

Alright, so in total we have to make 3 functions on top of the existing functions already offered in the UI. Whats the big deal? The big deal is, this could easily be done in PowerShell with around 4 or 5 major lines of code, in fact, here it is now:

function I-Am-Very-Short-Sad-Face {
	param(
		[System.Object] $SourceUser,
		[System.Object] $TargetUser,
	)

	$UniqueInitialGroups = Get-ADGroup -LDAPFilter "(&(member=$($SourceUser.DistinguishedName))(!member=$TargetUser))"
	$UserFilteredGroups = ..Imagine the user filters it with the magic popup code..
	
	foreach ($GroupName in $UserFilteredGroups) {
		Add-ADGroupMember ..params..
	}
}

So now the question pivots, do I bother making all of those functions? Do I just make one single clean function that does the entire task in one fell swoop?

If we make the other functions, we might save ourselves some effort later should we ever need their discrete abilities. If we stick to a single function, our code may be cleaner, less cluttered, more easily read or maintained even.

At the end of the day, its up to personal preference. Heck its PowerShell, you could do it all in one line if you tried hard enough. I believe the answer depends on the context and use-case. In a UI environment such as this, where various actions occur in a sequence, you likely do not want all of your functionality tangled in a web such that they all rely on each other in some way.

Don't get me wrong, this can be fantastic for code re-use, but unless all of your functions are pure, there's bound to be a bug eventually. Caused by some singular function experiencing an edge case that cascades down into the deeper behavior of your UI action. A single function? well, nothing can go wrong there so long as you don't change anything without testing.

With many functions, especially ones that are re-used a lot, we need to test each branch the logic could take.

With a single function, well, we just have to test the one button.