Commit 9368f3ab authored by CHAMONT David's avatar CHAMONT David
Browse files

Ajout du fonctionnel et du concurrent pour OnDemand

parent d8bd2862
......@@ -10,5 +10,6 @@ gcm.cache
tmp*
core.*
a.out
.fake
\ No newline at end of file
# About this course notebooks
The use of notebooks is not completely mandatory : you can read them on the [public project](https://gitlab.in2p3.fr/chamont/modernscientificcpp), and copy/paste the C++ extracts to your prefered editor. Else, if you want to play with the notebooks,
please look at the notebook section in file `installation.md`.
The use of notebooks is not completely mandatory : you can read them on the [public project](https://gitlab.in2p3.fr/chamont/modernscientificcpp), and copy/paste the C++ extracts to your prefered editor. Else, if you want to play with the notebooks, please look at the notebook section in file `installation.md`.
## Two kind of notebooks
......@@ -13,8 +12,12 @@ For few features which are not supported by the xeus-cling kernel (user defined
## Notebooks vs git versioning
One issue when working with notebooks : when using a notebook, you generally modifies it (at least the output of the code cells). If the teacher ask you to `git pull` a new version of it, you may face merging confilcts.
One issue when working with notebooks : when using a notebook, you generally modifies it (at least the output of the code cells). If the teacher ask you to `git pull` a new version of it, you may face tedious merging conflicts.
The usual dirty workaround applies : before pulling, either move globally your modified notebooks to a private git branch, or move/copy them to a backup directory.
The usual dirty workarounds applies : before pulling, either move globally your modified notebooks to a private git branch, or move/copy them to a backup directory.
If you prefer to solve the conflicts, `git diff` will not help much. The right command is rather `nbdiff` , which is not available by default (till now) in the linux distributions. You will need to install the `nbdime` package first, for example with the command `conda install -y -c conda-forge nbdime` .
\ No newline at end of file
If you prefer to solve the conflicts, `git diff` will not help much. The right command is rather `nbdiff` , which is not available by default (till now) in the linux distributions. You will need to install the `nbdime` package first, for example with the command `conda install -y -c conda-forge nbdime`.
TO BE INVESTIGATED:
- https://jupytext.readthedocs.io/en/latest/
- https://mybinder.org/
......@@ -3,7 +3,7 @@
Practice the **new C++ syntax** thanks to the notebooks of this folder.
Also, try to apply those novelties to your real own codes.
* Jour 1 (1h30) : [Modernize your code](1-ModernizeYourCode/en.0-index.ipynb)
* Jour 2 (1h30) : [Improved references and pointers](2-ReferencesAndPointers/en.0-index.ipynb)
* Jour 3 (1h30) : [Always more templates](3-Generic/en.0-index.ipynb)
* Jour 4 (1h30) : [Functional first steps](4-Functional/en.0-index.ipynb)
\ No newline at end of file
* Jour 1 (1h30) : [Modernize your code](1-ModernizeYourCode/README.ipynb)
* Jour 2 (1h30) : [Improved references and pointers](2-ReferencesAndPointers/README.ipynb)
* Jour 3 (1h30) : [Always more templates](3-Generic/README.ipynb)
* Jour 4 (1h30) : [Functional first steps](4-Functional/README.ipynb)
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Adopt a functional style in old C ++\n",
"\n",
"> For FP in C++, the importance of templates isn’t (mainly) in the creation of container classes such as vectors, but in the fact that it allowed creation of STL algorithms (...) Most of these algorithms let you pass custom functions to customize the algorithms’ behavior (...) The capability to pass functions as arguments to another function, and to have functions that return new functions, made even the first standardized version of C++ an FP language. *Ivan Cukic*"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"## A vision of functional programming\n",
"\n",
"Get closer to **mathematical logic**:\n",
"* a **variable** always means/contains the same thing,\n",
"* given the same arguments, a **function** always returns the same result,\n",
"* **high-order functions** can manipulate functions,\n",
"* a **type algebra** allows types to be recombined.\n",
"\n",
"Compensate for the involved efficiencies:\n",
"* **lazy evaluation**,\n",
"* **immutable data structures**,\n",
"* ..."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Hypothetical benefits of functional programming\n",
"\n",
"* **Facilitates program proofs** ... on only the pure sub-part of a real application (excluding reading and creating files, displays, interaction with the database, etc.).\n",
"* **Makes the code more readable?** Not so obvious in C ++, whose syntax serves so many paradigms..."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"## Practical benefits of functional programming\n",
"\n",
"* **Strengthen existing good practices** aimed at fighting spaghetti code (elimination of global variables).\n",
"* **Reduce unintentional state changes** (\"OO makes code understandable by encapsulating moving parts. FP makes code understandable by minimizing moving parts.\" *Michael Feathers*).\n",
"* **Promote the distinction between data structures and algorithms**, already more and more practiced in scientific computing, against a naive object approach.\n",
"* **Facilitate parallelization** (\"No shared states, no problem\")."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## A classic code example"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"#include <fstream>\n",
"#include <vector>\n",
"#include <string>"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"source": [
"void count_lines_in_files\n",
" ( std::vector<std::string> & fnames, std::vector<int> & nb_lines )\n",
" {\n",
" std::vector<std::string>::iterator fileitr ;\n",
" for ( fileitr = fnames.begin() ; fileitr != fnames.end() ; ++fileitr )\n",
" {\n",
" int line_count = 0 ;\n",
" char c = 0 ;\n",
" std::ifstream in(fileitr->c_str()) ;\n",
" while (in.get(c))\n",
" {\n",
" if (c == '\\n')\n",
" { line_count++ ; }\n",
" }\n",
" nb_lines.push_back(line_count) ;\n",
" }\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"## What we can already do with ancient C ++\n",
"\n",
"* Immutable data => use **`const`**\n",
"* Functions made **pure** =>\n",
" * constant input variables,\n",
" * a single output (possibly composite),\n",
" * no side effects, never use of \"context\" !\n",
"* Eliminate or hide as much as possible `if`,` for` or `while` => use the **standard library algorithms**.\n",
"\n",
"> Thinking functionally means thinking about the input data and the transformations you need to chain in order to get the desired output. *Ivan Cukic*"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"## Modified example"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"#include <fstream>\n",
"#include <vector>\n",
"#include <string>\n",
"#include <algorithm>"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"int count_lines( std::string const & filename )\n",
" {\n",
" std::ifstream in(filename.c_str()) ;\n",
" typedef std::istreambuf_iterator<char> ifiterator ;\n",
" return std::count(ifiterator(in), ifiterator(), '\\n') ;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"std::vector<int> count_lines_in_files( std::vector<std::string> const & files )\n",
" {\n",
" std::vector<int> nblines(files.size()) ;\n",
" std::transform(files.begin(), files.end(), nblines.begin(), count_lines) ;\n",
" return nblines ;\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Questions ?"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"© *CNRS 2019* \n",
"*This document was created by David Chamont, proofread and improved by Hadrien Grasland, and translated by Olga Abramkina. It is available under the [License Creative Commons - Attribution - No commercial use - Shared under the conditions 4.0 International](http://creativecommons.org/licenses/by-nc-sa/4.0/)*"
]
}
],
"metadata": {
"celltoolbar": "Diaporama",
"kernelspec": {
"display_name": "C++17",
"language": "C++17",
"name": "xcpp17"
},
"language_info": {
"codemirror_mode": "text/x-c++src",
"file_extension": ".cpp",
"mimetype": "text/x-c++src",
"name": "c++",
"version": "17"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
<
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Lambda functions\n",
"\n",
"> C++11, C++14, and C++17 introduced quite a few features that make writing programs in the functional style much easier. The additional features are mostly syntactic sugar, but important syntactic sugar, in the form of the `auto` keyword and lambdas. *Ivan Cukic*"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Lambda functions: anonymous and defined on the fly\n",
"\n",
"Many standard library algorithms, such as `std::for_each`, take a function as final argument. Starting with C++11, rather than defining such function separately, one can define it directly where it is used, by replacing its name with `[]`. These unnamed functions are called **anonymous functions** or **lambda functions**."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"#include <algorithm>\n",
"#include <vector>\n",
"#include <iostream>"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 4 9 16 25 \n"
]
}
],
"source": [
"std::vector<int> v {1,2,3,4,5} ;\n",
"std::for_each(v.begin(),v.end(), []( int i ){ std::cout<<i*i<<' ' ; } ) ;\n",
"std::cout<<std::endl ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## To modify elements, pass the argument by reference\n",
"\n",
"If your lambda must modify the received element, you have to (naturally) declare this element as a reference:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2 4 6 8 10 \n"
]
}
],
"source": [
"std::vector<int> v {1,2,3,4,5} ;\n",
"\n",
"std::for_each(v.begin(),v.end(),[]( int & i )\n",
" { i = 2*i ; }) ;\n",
"\n",
"std::for_each(v.begin(),v.end(),[]( int i )\n",
" { std::cout<<i<<' ' ; }) ;\n",
"std::cout<<std::endl ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Return a result\n",
"\n",
"If your lambda function needs to return a result, it's about as easy. The compiler can guess the return type of your lambda by inspecting the instruction `return`. In the following example, we combine `std::sort` with a lambda so to reverse the ordering."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5 4 3 2 1 "
]
}
],
"source": [
"std::vector<int> v {1,2,3,4,5} ;\n",
"\n",
"std::sort(v.begin(), v.end(), []( int lhs, int rhs )\n",
" { return (lhs>rhs) ; }) ;\n",
"\n",
"std::for_each(v.begin(), v.end(), []( int i )\n",
" { std::cout<<i<<' ' ; }) ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"To improve the readability of the code, or to help the compiler in certain ambiguous cases, we can explain the return type of a lambda. This is called **trailing return type declaration**."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5 4 3 2 1 "
]
}
],
"source": [
"std::vector<int> v {1,2,3,4,5} ;\n",
"\n",
"std::sort(v.begin(), v.end(), []( int lhs, int rhs ) -> bool\n",
" { return (lhs>rhs) ; }) ;\n",
"\n",
"std::for_each(v.begin(), v.end(), []( int i ) -> void\n",
" { std::cout<<i<<' ' ; }) ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"These early examples already show practical uses of lambdas. But their killing feature is their ability to capture local variables."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Local variables capture\n",
"\n",
"A lambda function, when introduced with empty brackets \"[]\", can only access to its own arguments and global variables, like any other function. Yet, one can insert between the brackets a list of context variables to be captured, by value or by reference:\n",
"* `[x,y,&j]` : `x` and `y` by value, `j` by reference ;\n",
"* `[]` : nothing captured ;\n",
"* `[&]` : all variables by reference ;\n",
"* `[=]` : all variables by value ;\n",
"* `[=,&j]` : all variables by value, except `j` by reference ;\n",
"* `[&,j]` : all variables by reference, except `j` by value."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"30\n"
]
}
],
"source": [
"std::vector<int> v {1,2,3,4,5} ;\n",
" \n",
"int multiplier = 2 ;\n",
"std::for_each(v.begin(), v.end(), [multiplier]( int & i )\n",
" { i = multiplier*i ; }) ;\n",
" \n",
"int accumulator = 0 ;\n",
"std::for_each(v.begin(), v.end(), [&accumulator]( int i )\n",
" { accumulator += i ; }) ;\n",
" \n",
"std::cout<<accumulator<<std::endl ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"A lambda is equivalent to some function-object, which capture the variables as members, and reuse them in the implementation of `operator()`:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"source": [
"class Multiplier\n",
" {\n",
" public :\n",
" Multiplier( int m ) : m_m{m} {}\n",
" void operator()( int & i ) { i = m_m*i ; }\n",
" private :\n",
" int m_m ;\n",
" } ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**BEWARE**: when capturing by reference, as with any reference, the behavior is undefined if the original variable disappears before the lambda function is used. And from a *functional* point of view, such a kind of side-effect context modification **is rather impure**!"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In the case where we want to capture a large object without duplicating it and without making it modifiable, we can regret that C ++ does not allow to capture a variable as a **constant reference**."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Storing and reusing lambdas"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"A lambda function is a \"first class object\", and can be stored in a variable, to be reused later as any normal function. The type of the lambda is implementation-dependent. The usual practice is to declare above variable `auto`."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}