Pure Functions
Pure functions are pretty similar to mathematical functions. They are the reason that Haskell is called a pure functional programming language.
Pure functions
I compare in the table pure and impure functions.
Pure functions have a crucial drawback. They can not communicate with the outside world. Because functions for input and output, functions for building a state, or functions for creating random numbers can not be pure. According to Simon Peyton Jones, the only effect that a pure function can have is to warm up the room. Haskell solves this dead-end by embedding impure, imperative subsystems in the pure functional language. These imperative subsystems are called monads. I will write more about monads in a few seconds.
What is the story of purity in C++? This story is based – similar to Immutable Data – on the programmer’s discipline. I will present a function, a meta-function, and a constexpr function in the following program. All three are pure functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// pureFunctions.cpp #include <iostream> int powFunc(int m, int n){ if (n == 0) return 1; return m * powFunc(m, n-1); } template<int m, int n> struct PowMeta{ static int const value = m * PowMeta<m,n-1>::value; }; template<int m> struct PowMeta<m,0>{ static int const value = 1; }; constexpr int powConst(int m, int n){ int r = 1; for(int k=1; k<=n; ++k) r*= m; return r; } int main(){ std::cout << powFunc(2,10) << std::endl; // 1024 std::cout << PowMeta<2,10>::value << std::endl; // 1024 std::cout << powConst(2,10) << std::endl; // 1024 } |
Although the three functions give the same result, they are entirely different. powFunc (lines 5 – 8) is a classical function. It will be executed at the program’s run time and can get non-constant arguments. On the contrary, PowMeta (lines 10 – 18) is a meta-function executed at the program’s compile time. Therefore, PowMeta needs constant expressions as arguments. The constexpr function powConst (lines 20 – 24) can run at compile and run times. To be executed at the compile time, powConst needs constant expressions as arguments according to the meta-function PowMeta.
And now to something completely different. I will introduce this in the next section, monads. I will refer to this first definition of monads in later posts, and I will show you examples of monads in future C++: std::optional in C++17, the ranges library from Eric Nieber in C++20, and the extended futures in C++20. But now, I’m talking about the future concept in C++. Or, to say it in the words of Bartosz Milewski: I See a Monad in Your Future. I strongly encourage you the read the post or watch his video.
Monads
Haskell, as a pure functional language, has only pure functions. The key to these pure functions is that they will always give the same result when given the same arguments. Thanks to this property, a Haskell function can not have side effects. This property is also called referencial transparency. Therefore, Haskell has a conceptual issue. The world is full of calculations that have side effects. These are calculations that can fail, return an unknown number of results, or are dependent on the environment. To solve this conceptual issue, Haskell uses monads and embeds them in the pure functional language.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
The classical monads encapsulate one side effect:
- I/O monad: Calculations that deal with input and output.
- Maybe monad: Calculations that may return a result.
- Error monad: Calculations that can fail.
- List monad: Calculations that can have an arbitrary number of results.
- State monad: Calculations that build a state.
- Reader monad: Calculations that read from the environment.
The concept of the monad is from the category theory. Category theory is a part of mathematics that deals with objects and mapping between objects. Monads are abstract data types (type classes), transforming simple types into enriched ones. Values of these enriched types are called monadic values. Once in a monad, a value can only be transformed by a function composition in another monadic value.
This composition respects the particular structure of a monad. Therefore, the error monad will interrupt its calculation if an error occurs or the state monad builds its state.
To make this happen, a monad consists of three components:
- Type constructor: The type constructor defines how the simple data type becomes a monadic data type.
- Functions:
- Identity function: Introduces the simple value into the monad.
- bind operator Defines how a function is applied to a monadic value to get a new one.
- Rules for the functions:
- The identity function has to be the left and the correct identity element.
- The composition of functions has to be associative.
For the error monad to become an instance of the type class Monad, the error monad has to support the identity function and the bind operator. Both functions define how the error monad deals with an error in the calculation. If you use an error monad, the error is handled in the background.
A monad consists of two control flows. The explicit control for calculating the result and the implicit control flow for dealing with the specific side effect.
A few months ago, after I published this post in German, a reader said: Hey, the definition of a monad is quite simple. “A monad is just a monoid in the category of endofunctors.” I hope you get it.
What’s next?
Pure functional languages have no mutable data. Therefore, they use recursion instead of loops. So you know what the next post will be about.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!