O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Thinking in Functions

111 visualizações

Publicada em

A talk from CppEurope 2019 about functional programming in C++.

It talks about lambdas, immutability, operations with functions (partial application, currying, functional composition), shows an example and ends with a procedure for refactoring legacy code.

Publicada em: Software
  • Seja o primeiro a comentar

  • Seja a primeira pessoa a gostar disto

Thinking in Functions

  1. 1. Thinking In Functions Alex Bolboacă,  @alexboly,  alex.bolboaca@mozaicworks.com February 2019
  2. 2. Why this talk
  3. 3. The Journey
  4. 4. A few years back
  5. 5. Last year… CppEurope Video available: https://www.youtube.com/watch?v=7L6oryyzZXc
  6. 6. Then… Book deal
  7. 7. About This Talk • A practical approach to use functional programming in C++ • Focused on essentials and the mindset • Not purist • No maths • Not touching on performance • My Goal: You should find at least one technique you can use in your code
  8. 8. Core Ideas
  9. 9. Two Fundamental Ideas • Immutability • Functions are data
  10. 10. Why Immutability?
  11. 11. Why Immutability? The more obstacles one places in the translation between code and behavior, the more mistakes (aka bugs) we can make and the least effective we are as programmers.
  12. 12. Example of Mutable Code How many implementation options exist? int add(int& first, int& second){ ... }
  13. 13. Option 1 int add(int& first, int& second){ return first + second; }
  14. 14. Option 2 int add(int& first, int& second){ return first += second; }
  15. 15. Option 3 int add(int& first, int& second){ return second += first; }
  16. 16. What’s the Semantics? int add(int first, int second) int add(int first, int& second) int add(int& first, int second) int add(int& first, int& second) int add(int first, int* second) int add(int& first, int* second) int add(int* first, int* second) int add(int* first, int second) int add(int* first, int& second)
  17. 17. Immutable Code // If standalone function int add(const int& first, const int& second) // If part of a class int add(const int& first, const int& second) const How many implementation options?
  18. 18. Immutable Code // If standalone function int add(const int& first, const int& second){ return first + second; } // If part of a class int add(const int& first, const int& second) const{ return first + second; }
  19. 19. Why Immutability? Immutability simplifies our translation effort, allowing your brain to focus on adding more behavior.
  20. 20. Functions Are Data
  21. 21. Not a New Idea In C++ • Function pointers • Functors
  22. 22. Let’s Represent Functions As Data // Lambda variable auto add = [](int first, int second){ return first + second; }; More details: http://en.cppreference.com/w/cpp/language/lambda
  23. 23. Careful: Lambdas can be mutable // Mutable auto increment = [](int& value){return ++value}; int aValue = 42; int incremented = increment(42); assert(incremented == 43); assert(aValue == 43)
  24. 24. Careful: Immutable Lambda • Function call operator is const • Parameters use their own specifier // Immutable auto incrementImmutable = [](const int& value){ return value + 1; }; int aValue = 42; int incremented = increment(42); assert(incremented == 43); assert(aValue == 42)
  25. 25. Type Inference // Does not compile auto add(auto first, auto second){ return first + second; } // Works for any two values that have operator+ defined auto add = [](const auto& first, const auto& second){ return first + second; };
  26. 26. Value Capture Lambdas can capture values from the context. TEST_CASE(”type inference with lambdas”){ int first = 5; auto add = [=](const auto& second){ return first + second; }; CHECK_EQ(42, add(37)); CHECK_EQ(42.5, add(37.5)); }
  27. 27. Code Time Code Time
  28. 28. Consequences Functions are data. We can do operations on data. We can do operations on functions!
  29. 29. Operations on Functions
  30. 30. What is an Operation on Functions? Any operation that takes one (or more functions) and returns a new function. • Partial Application • Functional Composition • Currying • Higher level functions
  31. 31. Partial Application A function with n-1 parameters is obtained from a function with n parameters by binding one parameter to a value. using namespace std::placeholders; auto divide = [](const auto& first, const auto& second){ return first/second; }; int specialValue = 10; auto divideSpecialValue = bind(divide, specialValue, _1); // Equivalent with // auto divideSpecialValue = [](const auto& second) { // return 10 / second // };
  32. 32. Functional Composition Create a function h(x) from two functions f(y) and g(z), such that for any value of x, h(x) = f(g(x)). C++ doesn’t yet have a functional composition operator, so we have to write our own function: template<typename F, typename G> auto compose(F f, G g){ return [f, g](auto x){ return f(g(x)); }; }
  33. 33. Example auto increment = [](const auto& value){ return value + 1; }; auto incrementTwice = compose(increment, increment); /* Equivalent with: auto incrementTwice = [&](const auto& value){ return increment(increment(value)); }; */ CHECK_EQ(3, incrementTwice(1));
  34. 34. Currying Decompose a function with N arguments into N functions with one argument auto curriedAdd = [](const auto& first){ return [first](const auto& second){ return first + second; }; };
  35. 35. Currying in C++ Unfortunately, there’s no operator that supports currying. In pure functional languages, curry is done by default, and linked with partial application. For example: auto divide = [](const auto& first, const auto& second){ return first/second; }; divide(5) => a new function that takes second as parameter, equ
  36. 36. Interesting Fact Curry. Haskell Curry
  37. 37. Pass Functions as Parameters TEST_CASE(”pass functions as parameters”){ vector<int> values{42, 1, 55, 23}; vector<int> expected{1, 23, 42, 55}; auto compare = [](const auto& first, const auto& second){ return (first < second); }; sort(values.begin(), values.end(), compare); CHECK_EQ(expected, values); }
  38. 38. Short list of higher level functions Find them in or • find_if • transform • reduce / accumulate • count_if • all_of / any_of / none_of • … See examples on https://github.com/MozaicWorks/functionalCpp
  39. 39. Code Time Code Time
  40. 40. Conclusions We can create functions from other functions through partial application, composition, currying. We can understand better the functions we write due to immutability.
  41. 41. Applying Functional Programming
  42. 42. Problem: TicTacToe Result Given a TicTacToe board that’s either empty or already has moves, print out the result of the game if the game ended or that the game is still in progress.
  43. 43. Approach 1. Clearly define the input; give examples 2. Clearly define the output; give examples 3. Identify a chain of functional transformations you can apply on the input data to turn it into the output data
  44. 44. Clearly Define the Output • Game not started • Game in progress • X won • O won • Draw
  45. 45. Clearly Define the Input Empty Board: _ _ _ _ _ _ _ _ _ X won by line: X X X O O _ _ _ _ etc.
  46. 46. Turns out that … X wins if • any line is filled with X OR • any column is filled with X OR • the main diagonal is filled with X OR • the secondary diagonal is filled with X
  47. 47. Transformations board -> collection(all lines, all columns, all diagonals) -> if any(collection, filledWithX) -> X won where filledWithX(line|column|diagonal L) = all(token on L equals 'X')
  48. 48. Remove duplication with functional operators • Functions with the same parameter? Partial application! Or classes! • Functions with the same structure? Higher level functions! • Chained calls? Function composition!
  49. 49. Code Time Code Time
  50. 50. What I’m Exploring Next
  51. 51. Refactoring code through Immutability Premise: • Any program can be written as Input -> Pure Functions -> State change (either UI or storage) • This property of the code applies on multiple levels, even on a single function
  52. 52. Refactoring Method • Take a complex piece of code: • extract a small piece of it as another method • extract everything mutable as parameter • until the method is immutable • check by making everything const and compiling • then decompose the long immutable function into smaller immutable functions • then recombine the functions into classes, based on the parameters they use
  53. 53. We End Up With Functional OOP • Data structures with set / get or public data • “Processor” objects that receive data and return other data • “Run and forget” objects: initialize, execute operations, throw it away • Separate, pluggable, input / output objects
  54. 54. Hypothesis Unconfirmed Hypothesis: this type of refactoring is safe even without tests. (Or, we can easily generate tests for the immutable function with property-based testing)
  55. 55. Conclusion Immutability and functions as data can simplify programming I invite you to try them out. Pick one and try it.
  56. 56. Learn more Functional programming in C++: https: //www.slideshare.net/alexboly/functional-programming-in-c-88970494 Hidden loops: https://www.slideshare.net/alexboly/hidden-loops Removing structural duplication: https://www.slideshare.net/alexboly/removing-structuralduplication
  57. 57. Learn more at Mozaic Works workshops https://mozaicworks.com/training/c-plus-plus/
  58. 58. Pre-order my book! Hands-on Functional Programming In C++ - http://bit.ly/FProgCpp
  59. 59. Q&A Q&A