A pure function is one that:
- Will always give the same result given the same arguments
- Doesn't have any observable side effects (e.g. state changes)
Suppose we are writing some code to handle user login, where we want to check that the supplied username and password are correct and prevent the user logging in if there are too many failed attempts. In an imperative style our code might look like this:
bool UserLogin(string username, string password)
{
var user = _database.FindUser(username);
if (user == null)
{
return false;
}
if (user.FailedAttempts > 3)
{
return false;
}
// Password hashing omitted for brevity
if (user.Password != password)
{
_database.RecordFailedLoginAttempt(username);
}
return true;
}
Its pretty clear that this is not a pure function:
- This function won't always give the same result for a given
username
and password
combination as the result also depends on the user record stored in the database.
- The function can change the state of the database, i.e. it has side effects.
Also note that in order to unit test this function we need to mock out two database calls, FindUser
and RecordFailedLoginAttempt
.
If we were to refactor this code into a more functional style we might end up with something a bit like this:
bool UserLogin(string username, string password)
{
var user = _database.FindUser(username);
var result = UserLoginPure(user, password);
if (result == Result.FailedAttempt)
{
_database.RecordFailedLoginAttempt(username);
}
return result == Result.Success;
}
Result UserLoginPure(User user, string pasword)
{
if (user == null)
{
return Result.UserNotFound;
}
if (user.FailedAttempts > 3)
{
return Result.LoginAttemptsExceeded;
}
if (user.Password != password)
{
return Result.FailedAttempt;
}
return Result.Success;
}
Note that although the UserLogin
function is still not pure, the UserLoginPure
function is now a pure function and as a result the core user authentication logic can be unit tested without needing to mock any external dependencies. This is because interaction with the database is handled higher up the call stack.