Functions - Conjunction Junction What's Your Function?
Functions are snippets of code that you define and give a name that you can call from other places in your code repeatedly. Functions are reusable and help both simplify your code and make your code easier to read.
Parts of a Function
A function consists of a few parts, a name, code block, parameters, and a return.
Name
Naming your function is important. It should be clear what the function does by just looking at the name. While you may not know exactly how the function Get-Popcorn works, the name indicates that if you call it you're going to get popcorn. In Powershell it is standard naming convention to name all functions following the Verb-Noun pattern. While Powershell's standards are to only use approved verbs (You can see all the approved verbs by running the function Get-Verb in a powershell prompt) I find that it doesn't have enough verbs to cover all cases to make your function's function clear. If you're not publishing your script publicly, I default to making your code more clear, rather than sticking to approved verbs.
Self Documenting Code
A quick aside about Self Documenting Code. Self Documenting Code is a programming principle that uses standard naming conventions and structured programming styles to make code more readable to humans. While you can add a bunch of comments to your code to explain what you are intending to do, writing your code in a human readable way eliminates the need for a ton of comments (You should still comment your code, you'll just need less). Many of the tools in an IDE (such as tab auto-completion) will make writing Self Documenting Code easier, and writing Self Documenting Code will make your life a lot easier.
Without comments, the second function is much easier to read and much clearer on its intentions. Learning to write your code in a way that is easier to read early will make many things easier on you in the long run.
Parameters
If you need to pass information into a function, you use a parameter. While a function can access variables outside of the functions code block, this is not a good practice as doing so can introduce unintended consequences in your code. Parameters are defined in the first line of the code block of your function with the param statement.
Parameter Types
When defining parameters you can define what type of variable the function is expecting. If the value you pass to the function is convertible to that type, it will automatically be converted to that type (this is called casting), otherwise it will throw an error and the function wont run (preventing some weird things from happening).
Mandatory Parameters
Some parameters aren't always necessary for a function to run correctly, but some parameters are required. To make sure that a parameter has been passed you can make them mandatory by including the [Parameter(Mandatory] statement in front of the parameter name. If you don't include that parameter when you call the function, Powershell will ask you for it then.
Default Parameters
If there is a value that you want a function to use if a parameter is not explicitly defined, you can set a default value that will be used unless overridden by passing in that parameter.
Return
A function can do a task that doesn't require any data to be used outside the function, but most often a function does some work on some data and then gives a returns a result to be used elsewhere in the code. This is where the return statement comes in. Similar to the break statement in loops, return exits out of a function, but unlike break return will pass data to wherever the function was called. This allows you to assign the results of a function to a variable. Once you hit a return, no code after that point in the function's code block will be run.
You can have multiple returns in a function by using a conditional statement, but as soon as one return is hit, nothing else will be run.
Scope and Reusability
Functions should be self contained and reusable. That means you should be able to copy a function that you've written from one script into another script and have it work without making any changes. This is why parameters and the return statement are used. If you are only making changes to data that exists within the function's scope it is easy to reuse this function in other code. Any function that modifies data outside of its own scope doesn't meet this requirement.
Functions should also perform one task and only one task. This makes it much easier to reuse functions and save time while coding. Large complicated functions may work well in one situation but using them in any other situation will be difficult. If you write your functions to solve only one issue they're much more flexible and usable in more situations.
Tic-Tac-Toe
We previously expanded the Tic Tac Toe example by adding a loop and making it a playable game but we can use a few functions (besides the previously added function to write the game board out) to make the code much easier to read and modify in the future.
#A function to output a two dimensional array as a Tic Tac Toe Board
function Write-TicTacToeBoard
{
param($TicTacToeBoard)
#Loop through each row and generate an output string
foreach($item in $TicTacToeBoard)
{
$line = $item[0] + "|"+ $item[1] + "|" + $item[2]
Write-Host $line
}
}
function CheckFor-Winner
{
param($TicTacToeBoard)
#Check Rows and colums for winner
foreach($i in 0..2)
{
#Check Row $i for winner
if(($TicTacToeBoard[$i][0] -eq $TicTacToeBoard[$i][1] -and $TicTacToeBoard[$i][0] -eq $TicTacToeBoard[$i][2]) -and $TicTacToeBoard[$i][0] -ne "_")
{
return $TicTacToeBoard[$i][0]
}
#Check Column $i for winner
elseif(($TicTacToeBoard[0][$i] -eq $TicTacToeBoard[1][$i] -and $TicTacToeBoard[0][$i] -eq $TicTacToeBoard[2][$i]) -and $TicTacToeBoard[0][$i] -ne "_")
{
return $TicTacToeBoard[0][$i]
}
}
#Check diagonals for winner
if(($TicTacToeBoard[0][0] -eq $TicTacToeBoard[1][1] -and $TicTacToeBoard[0][0] -eq $TicTacToeBoard[2][2]) -and $TicTacToeBoard[0][0] -ne "_")
{
return $TicTacToeBoard[0][0]
}
elseif(($TicTacToeBoard[2][0] -eq $TicTacToeBoard[1][1] -and $TicTacToeBoard[2][0] -eq $TicTacToeBoard[0][2]) -and $TicTacToeBoard[0][0] -ne "_")
{
return $TicTacToeBoard[2][0]
}
#If no winner yet, return $null
return $null
}
function Place-Character
{
param($Character,$TicTacToeBoard)
#Get a Row
$Row = Read-Host "Which row would you like to place an $Character [0,1,2]"
#Get a column
$Column = Read-Host "Which column would you like to place an $Character [0,1,2]"
#Place Character in position
$TicTacToeBoard[$Row][$Column] = $Character
#Return the variable $TicTacToeBoard
return $TicTacToeBoard
}
#Declare a Two Dimensional Array that represents a Tic Tac Toe board
$TicTacToe = @(@('_','_','_'),@('_','_','_'),@('_','_','_'))
#Start a turn iterator
$Turn = 0
#Create an loop to run the game, Can only run 9 turns
while($Turn -lt 9)
{
#Write Out the Board for the user
Write-TicTacToeBoard -TicTacToeBoard $TicTacToe
#Check to see if someone won, No one can win before turn 5
if($turn -gt 4)
{
$WinCheck = CheckFor-Winner -TicTacToeBoard $TicTacToe
#If someone won, break out of the loop
if($WinCheck -ne $null)
{
Write-Host "Congratulations to Team $WinCheck you have won the game!"
break
}
}
#Check which turn it is. Even turns are X, Odd turns are O
if($Turn % 2 -eq 0)
{
#Assign a character for this move
$Character = "X"
}
else
{
#Assign a character for this move
$Character = "O"
}
#Put the character on the board
$TicTacToe = Place-Character -Character $Character -TicTacToeBoard $TicTacToe
#Increment the turn counter
$Turn++
}
#If the game completes turn 9 and there is no winner, declare a draw
if($Turn -eq 9)
{
Write-Host "It is a draw!"
}
The CheckFor-Winner function loops through the game board and checks to see if anyone has won, rather than asking the players if anyone has won. CheckFor-Winner returns the character that has won, or if neither player has won yet, it returns $null
The Place-Character functions takes care of asking where to place the current character. Previously there was duplicated code that did that for both X and O and this condenses it down into one function, which is both more clear in the main loop of the code and makes it easier to modify the process at a later date. Each time the Place-Character function runs it fills in the board and then assigns the correct value to $TicTacToe.