Pure Functions in Software Development

A quick dive into pure functions and how we can control them to have an excellent testable architecture.

Photo by Ant Rozetsky on Unsplash

Whenever we think about pure vs. impure, we strive to get something as close to 100% pureness as possible.

Does software development have the same preference for purity?

What is a pure function in software development?

Pure function – Wikipedia

If we are to look it up on Wikipedia, we will notice that these pure functions have two properties:

  1. the return value of the function it’s always the same when called with the same arguments
  2. the function application does not have side effects

Hmm… let’s try to understand it better with some practical examples:

int sum(int a, int b) {
return a + b;
}

Does sum respect the two properties of pure functions?

Sure, for the same a and b values, we will always get the sum of them, and also, whatever it does inside, it doesn’t mutate/change the state of the app/module.

Now let’s try with a counter example:

DateTime giveMeTheCurrentDate() {
return DateTime();
}

Let’s go through the same process? Does it violate any of the two properties of pure functions?

It does for the first one: each time we will call it, the result will be different since as much as we would love to, we cannot freeze the time 😭

And now the 1 million dollar question? If we cannot control it and have a deterministic outcome, how can we write a unit test or a test of any sort for it and have the insurance that everything works as expected?

Time to bring into our arsenal two new weapons:

  • dependency inversion principle
  • controlling the world

To put it simply, the dependency inversion principle states that we shouldn’t rely on concrete types but rather on abstractions.

Let’s see how this will look like in Flutter (abstract class is equivalent with interface in other languages if it looks weird at first sight)

abstract class IDateManager {
DateTime retrieveDate();
}
class ConcreteDateManager implements IDateManager {
@override
DateTime retrieveDate() {
return DateTime.now();
}
}
class DateService {
DateTime giveMeTheCurrentDate(IDateManager manager) {
return manager.retrieveDate();
}
}

If we want to get the date with the concrete implementation, which is not a pure function we can call it like this:

final currentDate = DateService().giveMeTheCurrentDate(ConcreteDateManager());

How will a pure function look like in this scenario? Let’s create another class for our unit tests that is deterministic:

class DummyDateManager implements IDateManager {
@override
DateTime retrieveDate() {
return DateTime.fromMicrosecondsSinceEpoch(1642167154);
}
}

Now, if we call the DateService with DummyDateManager, it will always produce the same result Friday, 14 January 2022, 13:32:34

Controlling our dependencies and environment empowers us to quickly write unit test cases and easily reuse parts of the codebase.

After all, when you write a piece of code, you are its creator; why shouldn’t you be able to control the time?

The second tool from our arsenal was controlling the world: our world. Without going into much specifics at the moment (because it will be covered in a later story), the idea is that all external nondeterministic dependencies like time, storage, network, device sensors can be instantiated by concrete implementation inside an Environment structure or a service locator that’s available all across the app/module. It can look something like this:

void setupEnvironment() {
serviceLocator.registerFactory<IDateManager>(() => ConcreteDateManager());
}

And now, for using it inside DateService, we can use the service locator (ditching the abstract parameter manager):

class DateService {
DateTime giveMeTheCurrentDate() {
return serviceLocator<IDateManager>().retrieveDate();
}
}

Thank you for taking the time to read all the above, and I hope that everything makes much more sense and if you have any comments, please don’t hesitate to write them up. Also, please don’t hesitate to spread the knowledge to other fellow developers.


Pure Functions in Software Development was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.

A quick dive into pure functions and how we can control them to have an excellent testable architecture.

Photo by Ant Rozetsky on Unsplash

Whenever we think about pure vs. impure, we strive to get something as close to 100% pureness as possible.

Does software development have the same preference for purity?

What is a pure function in software development?

Pure function – Wikipedia

If we are to look it up on Wikipedia, we will notice that these pure functions have two properties:

  1. the return value of the function it’s always the same when called with the same arguments
  2. the function application does not have side effects

Hmm… let’s try to understand it better with some practical examples:

int sum(int a, int b) {
return a + b;
}

Does sum respect the two properties of pure functions?

Sure, for the same a and b values, we will always get the sum of them, and also, whatever it does inside, it doesn’t mutate/change the state of the app/module.

Now let’s try with a counter example:

DateTime giveMeTheCurrentDate() {
return DateTime();
}

Let’s go through the same process? Does it violate any of the two properties of pure functions?

It does for the first one: each time we will call it, the result will be different since as much as we would love to, we cannot freeze the time 😭

And now the 1 million dollar question? If we cannot control it and have a deterministic outcome, how can we write a unit test or a test of any sort for it and have the insurance that everything works as expected?

Time to bring into our arsenal two new weapons:

  • dependency inversion principle
  • controlling the world

To put it simply, the dependency inversion principle states that we shouldn’t rely on concrete types but rather on abstractions.

Let’s see how this will look like in Flutter (abstract class is equivalent with interface in other languages if it looks weird at first sight)

abstract class IDateManager {
DateTime retrieveDate();
}
class ConcreteDateManager implements IDateManager {
@override
DateTime retrieveDate() {
return DateTime.now();
}
}
class DateService {
DateTime giveMeTheCurrentDate(IDateManager manager) {
return manager.retrieveDate();
}
}

If we want to get the date with the concrete implementation, which is not a pure function we can call it like this:

final currentDate = DateService().giveMeTheCurrentDate(ConcreteDateManager());

How will a pure function look like in this scenario? Let’s create another class for our unit tests that is deterministic:

class DummyDateManager implements IDateManager {
@override
DateTime retrieveDate() {
return DateTime.fromMicrosecondsSinceEpoch(1642167154);
}
}

Now, if we call the DateService with DummyDateManager, it will always produce the same result Friday, 14 January 2022, 13:32:34

Controlling our dependencies and environment empowers us to quickly write unit test cases and easily reuse parts of the codebase.

After all, when you write a piece of code, you are its creator; why shouldn’t you be able to control the time?

The second tool from our arsenal was controlling the world: our world. Without going into much specifics at the moment (because it will be covered in a later story), the idea is that all external nondeterministic dependencies like time, storage, network, device sensors can be instantiated by concrete implementation inside an Environment structure or a service locator that's available all across the app/module. It can look something like this:

void setupEnvironment() {
serviceLocator.registerFactory<IDateManager>(() => ConcreteDateManager());
}

And now, for using it inside DateService, we can use the service locator (ditching the abstract parameter manager):

class DateService {
DateTime giveMeTheCurrentDate() {
return serviceLocator<IDateManager>().retrieveDate();
}
}

Thank you for taking the time to read all the above, and I hope that everything makes much more sense and if you have any comments, please don’t hesitate to write them up. Also, please don’t hesitate to spread the knowledge to other fellow developers.


Pure Functions in Software Development was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


Print Share Comment Cite Upload Translate
APA
Catalin Patrascu | Sciencx (2024-03-28T11:17:45+00:00) » Pure Functions in Software Development. Retrieved from https://www.scien.cx/2022/01/18/pure-functions-in-software-development/.
MLA
" » Pure Functions in Software Development." Catalin Patrascu | Sciencx - Tuesday January 18, 2022, https://www.scien.cx/2022/01/18/pure-functions-in-software-development/
HARVARD
Catalin Patrascu | Sciencx Tuesday January 18, 2022 » Pure Functions in Software Development., viewed 2024-03-28T11:17:45+00:00,<https://www.scien.cx/2022/01/18/pure-functions-in-software-development/>
VANCOUVER
Catalin Patrascu | Sciencx - » Pure Functions in Software Development. [Internet]. [Accessed 2024-03-28T11:17:45+00:00]. Available from: https://www.scien.cx/2022/01/18/pure-functions-in-software-development/
CHICAGO
" » Pure Functions in Software Development." Catalin Patrascu | Sciencx - Accessed 2024-03-28T11:17:45+00:00. https://www.scien.cx/2022/01/18/pure-functions-in-software-development/
IEEE
" » Pure Functions in Software Development." Catalin Patrascu | Sciencx [Online]. Available: https://www.scien.cx/2022/01/18/pure-functions-in-software-development/. [Accessed: 2024-03-28T11:17:45+00:00]
rf:citation
» Pure Functions in Software Development | Catalin Patrascu | Sciencx | https://www.scien.cx/2022/01/18/pure-functions-in-software-development/ | 2024-03-28T11:17:45+00:00
https://github.com/addpipe/simple-recorderjs-demo