Commit 8b087a22 authored by CHAMONT David's avatar CHAMONT David
Browse files

After PRACE

parent b3a679ea
......@@ -536,11 +536,11 @@
"\n",
"using Liter = StrongTypedef<double,struct LiterTag> ;\n",
"\n",
"// A COMPLETER\n",
"// TO BE COMPLETED\n",
"\n",
"using Meter = StrongTypedef<double,struct MeterTag> ;\n",
"\n",
"// A COMPLETER\n",
"// TO BE COMPLETED\n",
"\n",
"int main()\n",
" {\n",
......
......@@ -21,7 +21,7 @@
"source": [
"## Downgraded mathematic functions\n",
"\n",
"Let's take this example : we want to compute the total energy `e` of an electron whose linear momentum `p` is known. We can deduce it from the inequality `e^2 = m^2*c^4 + p^2*c^2`, with `c` the speed of light and `m` the mass of the electron."
"Let's take this example : we want to compute the total energy `e` of an electron whose linear momentum `p` is known. We can deduce it from the equality `e^2 = m^2*c^4 + p^2*c^2`, with `c` the speed of light and `m` the mass of the electron."
]
},
{
......@@ -377,7 +377,7 @@
}
},
"source": [
"Actually, Eigen does not accept to multiply a scalar with avector if all the numbers are not expressed in the same types : **strong typing is not compatible with popular linera algebra libraries**."
"Actually, Eigen does not accept to multiply a scalar with avector if all the numbers are not expressed in the same types : **strong typing is not compatible with popular linear algebra libraries**."
]
},
{
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Typage Fort et Systeme International d'Unités en C++"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"En dépit de l'approche objet, pour choisir ou définir le type d'un objet, on a encore tendance à se focaliser sur son contenu plutôt que sur son comportement, **en particulier lorsqu'il s'agit de petits nombres scalaires**. Un nombre réel est un nombre réel : en C++, on en fait un `float` ou un `double`, point-barre. Un nombre entier est un `int`, ou exceptionnellement un `short` ou un `long` si on chipote, point-barre. On en arrive ainsi à ajouter des mètres avec des secondes, ou à confondre un indice de tableau avec un identifiant, sans que le compilateur ne lève un sourcil."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pourtant, C++, particulièrement aujourd'hui, possède toutes les fonctionnalités permettant d'aller plus loin, et de construire\n",
"des **types porteurs de sens**, réduisant les risques de mauvaise manipulation. Voyons comment."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## Typage fort & unités de physique"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `typedef` ou `using` : des alias pour faire semblant\n",
"\n",
"La définition d'un alias de type permet d'expliciter l'intention, et d'améliorer la lisibilité du code. Mais du point du compilateur, il s'agit toujours d'un seul et même type, et tous les mélanges restent permis."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%file tmp_using.cpp\n",
"\n",
"#include <iostream>\n",
"\n",
"using Meter = double ;\n",
"using Second = double ;\n",
"\n",
"int main()\n",
" {\n",
" Meter m { 1000 } ;\n",
" Second s { 60 } ;\n",
" \n",
" std::cout<<m<<\"+\"<<m<<\"=\"<<(m+m)<<std::endl ;\n",
" std::cout<<s<<\"+\"<<s<<\"=\"<<(s+s)<<std::endl ;\n",
" std::cout<<m<<\"+\"<<s<<\"=\"<<(m+s)<<std::endl ;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!rm -f tmp_using.exe && g++ -o tmp_using.exe tmp_using.cpp"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!./tmp_using.exe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Alias \"fort\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ce dont on pourrait rêver, c'est que le langage nous fournisse directement une sorte de `strong typedef`, ou `opaque typedef`, qui ne serait pas seulement un alias, mais un type distinct du point de vue du compilateur. Mais aux dernières nouvelles (2016), Bjarne Stroustrup n'est plus très favorable à l'idée."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"On peut aussi créer chaque type complètement à la main. Cela devient vite épuisant."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Reste les templates..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%file tmp_template.cpp\n",
"\n",
"#include <iostream>\n",
"\n",
"template< typename UnderlyingType, typename TagType >\n",
"class StrongTypedef\n",
" {\n",
" public :\n",
" explicit StrongTypedef( UnderlyingType value ) : value_{value} {}\n",
" UnderlyingType value() const { return value_ ; }\n",
" private :\n",
" UnderlyingType value_ ;\n",
" } ;\n",
"\n",
"template< typename UnderlyingType, typename TagType >\n",
"std::ostream & operator<<( std::ostream & os, const StrongTypedef<UnderlyingType,TagType> & obj )\n",
" { return (os<<obj.value()) ; }\n",
"\n",
"using Meter = StrongTypedef<double, struct MeterTag> ;\n",
"using Second = StrongTypedef<double, struct SecondTag> ;\n",
"\n",
"int main()\n",
" {\n",
" Meter m { 1000 } ;\n",
" Second s { 60 } ;\n",
" \n",
" std::cout<<m<<\"+\"<<m<<\"=\"<<(m+m)<<std::endl ;\n",
" std::cout<<s<<\"+\"<<s<<\"=\"<<(s+s)<<std::endl ;\n",
" std::cout<<m<<\"+\"<<s<<\"=\"<<(m+s)<<std::endl ;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!rm -f tmp_template.exe && g++ -o tmp_template.exe tmp_template.cpp"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Le compilateur ne confond plus les mètres et les secondes... mais les nouveaux types ont perdus tous leurs opérateurs. Il reste beaucoup de travail pour restaurer ceux qui sont valides, et seulement ceux là."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Factoriser et recombiner les opérateurs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"L'héritage peut nous aider à factoriser la définition de chaque opérateur, et à ne l'utiliser que dans les types ou cela fait sens."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%file tmp_inheritance.cpp\n",
"\n",
"#include <iostream>\n",
"\n",
"template< typename UnderlyingType, typename TagType >\n",
"class StrongTypedef\n",
" {\n",
" public :\n",
" explicit StrongTypedef( UnderlyingType value ) : value_{value} {}\n",
" UnderlyingType value() const { return value_ ; }\n",
" private :\n",
" UnderlyingType value_ ;\n",
" } ;\n",
"\n",
"template< typename UnderlyingType, typename TagType >\n",
"std::ostream & operator<<( std::ostream & os, const StrongTypedef<UnderlyingType,TagType> & obj )\n",
" { return (os<<obj.value()) ; }\n",
"\n",
"template <class StrongTypedef>\n",
"struct addition\n",
" {\n",
" friend StrongTypedef operator+( const StrongTypedef & lhs, const StrongTypedef & rhs )\n",
" { return StrongTypedef(lhs.value()+rhs.value()) ; }\n",
" } ;\n",
"\n",
"struct Meter : StrongTypedef<double, Meter>, addition<Meter>\n",
" { using StrongTypedef::StrongTypedef ; } ;\n",
"\n",
"struct Second : StrongTypedef<double, Second>, addition<Second>\n",
" { using StrongTypedef::StrongTypedef ; } ;\n",
"\n",
"int main()\n",
" {\n",
" Meter m { 1000 } ;\n",
" Second s { 60 } ;\n",
" \n",
" std::cout<<m<<\"+\"<<m<<\"=\"<<(m+m)<<std::endl ;\n",
" std::cout<<s<<\"+\"<<s<<\"=\"<<(s+s)<<std::endl ;\n",
" std::cout<<m<<\"+\"<<s<<\"=\"<<(m+s)<<std::endl ;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!rm -f tmp_inheritance.exe && g++ -o tmp_inheritance.exe tmp_inheritance.cpp"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!./tmp_inheritance.exe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Des bibliothèques à la rescousse\n",
"\n",
"Au delà de l'exemple des unités, le typage fort peut rendre d'immenses services pour donner un sens à vos valeurs numériques, et réduire les erreurs. Différentes bibliothèques vous proposent de l'aide pour les écrire rapidement :\n",
"* [type_safe, par Jonathan Muller](https://github.com/foonathan/type_safe)\n",
"* [NamedType, par Jonathan Boccara](https://github.com/joboccara/NamedType)\n",
"* [opaque-typedef, par Kyle Markley](https://sourceforge.net/projects/opaque-typedef/files/)\n",
"* [BOOST_STRONG_TYPEDEF](http://www.boost.org/doc/libs/1_61_0/libs/serialization/doc/strong_typedef.html)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## Multiples des unités & suffixes personnalisés\n",
"\n",
"Le typage fort nous offre des outils pour différencier les grandeurs physiques que l'on manipule, et donner des unités à nos variables, plutôt que d'utiliser de simples types prédéfinis tels que `int` ou `double`.\n",
"\n",
"Cependant, lorsque l'on considère des unités qui sont des multiples les uns des autres, telles que le mètre et le kilomètre, on sent bien qu'elles sont interopérables, à un multiple près, et que plutôt que de coder des types différents pour tous les multiples, il faut introduire un autre mécanisme."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Constantes\n",
"\n",
"Une solution simple consiste à définir des constantes à multiplier par les valeurs littérales :"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%file tmp_constants.cpp\n",
"\n",
"#include <iostream>\n",
"\n",
"template< typename UnderlyingType, typename TagType >\n",
"class StrongTypedef\n",
" {\n",
" public :\n",
" explicit StrongTypedef( UnderlyingType value ) : value_{value} {}\n",
" UnderlyingType value() const { return value_ ; }\n",
" private :\n",
" UnderlyingType value_ ;\n",
" } ;\n",
" \n",
"template< typename UT, typename TT1, typename TT2 >\n",
"UT operator/( const StrongTypedef<UT,TT1> & lhs, const StrongTypedef<UT,TT2> & rhs )\n",
" { return (lhs.value()/rhs.value()) ; }\n",
"\n",
"using Liter = StrongTypedef<double,struct LiterTag> ;\n",
"const double L = 1.0 ;\n",
"const double HL = 100.0 ;\n",
"const double CL = 0.01 ;\n",
"\n",
"using Meter = StrongTypedef<double,struct MeterTag> ;\n",
"const double M = 1.0 ;\n",
"const double KM = 1000.0 ;\n",
"const double MM = 0.001 ;\n",
"\n",
"int main()\n",
" {\n",
" Liter fuel { 2.38*L } ;\n",
" Meter distance { 28*KM } ;\n",
" std::cout<<(fuel/distance*100*KM)<<\" l/100km\"<<std::endl;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!rm -f tmp_constants.exe && g++ -o tmp_constants.exe tmp_constants.cpp"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!./tmp_constants.exe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"La limite de cette approche est que la cohérence entre les unités et les multiples utilisés n'est pas vérifiée par le compilateur : on peut multiplier la constante `L` avec une valeur littérale puis en faire un objet de type `Meter`. Essayez d'inverser `L` et `KM` dans le `main()` ci-dessus : cela compile sans crier gare. On peut faire également une erreur de parenthésage, qui fera que la constante sera séparée par erreur de sa valeur associée."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Note : dans l'exemple ci-dessus, on peut également remarquer que toute grandeur physique peut être divisée par une grandeur d'une autre nature, et nosu fournissons un opérateur de division en ce sens, mais à ce stade nous ne savons pas quel est le type du résultat, donc nous nous contentons de renvoyée une valeur du type sous-jacent. Nosu verrons plus tard comment résoudre élégamment cette difficulté.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Suffixes personnalisés\n",
"\n",
"En C++ moderne, on peut éviter les interversions de types, les erreurs de parenthesages, et permettre un prétraitement à la compilation. Ceci à l'aide d'un mélange de typage fort, d'expressions constantes (`constexpr`) et de suffixes personnalisés (\"user-defined litterals\")."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%file tmp_literals.cpp\n",
"\n",
"#include <iostream>\n",
"\n",
"template< typename UnderlyingType, typename TagType >\n",
"class StrongTypedef\n",
" {\n",
" public :\n",
" explicit constexpr StrongTypedef( UnderlyingType value ) : value_{value} {}\n",
" constexpr UnderlyingType value() const { return value_ ; }\n",
" private :\n",
" UnderlyingType value_ ;\n",
" } ;\n",
" \n",
"template< typename UT, typename TT1, typename TT2 >\n",
"constexpr auto operator/( const StrongTypedef<UT,TT1> & lhs, const StrongTypedef<UT,TT2> & rhs )\n",
" { return (lhs.value()/rhs.value()) ; }\n",
"\n",
"using Liter = StrongTypedef<double,struct LiterTag> ;\n",
"\n",
"constexpr Liter operator \"\"_L ( long double value )\n",
" { return Liter(1.*value) ; }\n",
"constexpr Liter operator \"\"_HL ( long double value )\n",
" { return Liter(100.*value) ; }\n",
"constexpr Liter operator \"\"_CL ( long double value )\n",
" { return Liter(0.01*value) ; }\n",
"\n",
"using Meter = StrongTypedef<double,struct MeterTag> ;\n",
"\n",
"constexpr Meter operator \"\"_M ( long double value )\n",
" { return Meter(1.*value) ; }\n",
"constexpr Meter operator \"\"_KM ( long double value )\n",
" { return Meter(1000.*value) ; }\n",
"constexpr Meter operator \"\"_MM ( long double value )\n",
" { return Meter(0.001*value) ; }\n",
"\n",
"int main()\n",
" {\n",
" constexpr auto fuel { 2.38_L } ;\n",
" constexpr auto distance { 28.0_KM } ;\n",
" std::cout<<(fuel/distance*(100.0_KM).value())<<\" l/100km\"<<std::endl;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!rm -f tmp_literals.exe && g++ -o tmp_literals.exe tmp_literals.cpp"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!./tmp_literals.exe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## Analyse dimensionnelle"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Alias forts** et **suffixes personnalisés** nous ont permis de définir des types spécifiques pour les différentes unités physiques de base, ainsi que leurs multiples, mais nous avons constaté rapidement qu'il nous manquait les unités dérivées, qui sont formées par les puissances, les produits ou les quotients des unités de base et sont potentiellement illimitées en nombre."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Une nouvelle classe pour toutes les unités\n",
"\n",
"Plutôt que d'écrire une infinité de classe pour les unités de base et leurs dérivées, on va plutôt s'appuyer sur une classe générique unique, qui en plus des habituels types paramétrés, va s'enrichir de paramètres entiers décrivant la puissance de chacune des 7 unités de base :\n",
"1. Masse, en kilogramme,\n",
"2. Temps, en seconde,\n",
"3. Longueur, en mètre,\n",
"4. Température, en kelvin,\n",
"5. Intensité électrique, en ampère,\n",
"6. Quantité de matière, en mole,\n",
"7. Intensité lumineuse, en candela."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ci-dessous, un exemple simplifié avec un paramètre pour la dimension `temps` et un paramètre pour la dimension `longueur`, ainsi qu'un opérateur `/` générique permettant de calculer par exemple une vitesse ou une accélération."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%file tmp_si.cpp\n",
"\n",
"#include <iostream>\n",
"\n",
"template< typename UnderlyingType, int s, int m >\n",
"class SiUnit\n",
" {\n",
" public :\n",
" explicit constexpr SiUnit( UnderlyingType value ) : value_{value} {}\n",
" constexpr UnderlyingType value() const { return value_ ; }\n",
" private :\n",
" UnderlyingType value_ ;\n",
" } ;\n",
"\n",
"template< typename UnderlyingType, int s, int m >\n",
"std::ostream & operator<<( std::ostream & os, const SiUnit<UnderlyingType,s,m> & obj )\n",
" { return (os<<obj.value()) ; }\n",
"\n",
"template< typename UT, int s1, int m1, int s2, int m2 >\n",
"constexpr auto operator*( SiUnit<UT,s1,m1> lhs, SiUnit<UT,s2,m2> rhs )\n",
" { return SiUnit<UT,s1+s2,m1+m2>(lhs.value()*rhs.value()) ; }\n",
"\n",
"template< typename UT, int s1, int m1, int s2, int m2 >\n",
"constexpr auto operator/( SiUnit<UT,s1,m1> lhs, SiUnit<UT,s2,m2> rhs )\n",
" { return SiUnit<UT,s1-s2,m1-m2>(lhs.value()/rhs.value()) ; }\n",
"\n",
"using Time = SiUnit<double,1,0> ;\n",
"constexpr Time operator \"\"_Se ( long double value )\n",
" { return Time(value) ; }\n",
"constexpr Time operator \"\"_Mi ( long double value )\n",
" { return Time(value*60) ; }\n",
"constexpr Time operator \"\"_H ( long double value )\n",
" { return Time(value*60*60) ; }\n",
"\n",
"using Length = SiUnit<double,0,1> ;\n",
"constexpr Length operator \"\"_M ( long double value )\n",
" { return Length(value) ; }\n",
"constexpr Length operator \"\"_KM ( long double value )\n",
" { return Length(value*1000) ; }\n",
"\n",
"using Speed = SiUnit<double,-1,1> ;\n",
" \n",
"int main()\n",
" {\n",
" constexpr auto l { 28.0_KM } ;\n",
" constexpr auto d { 39.0_Mi } ;\n",
" constexpr Speed vmax = 50._KM/1._H ;\n",
" constexpr Speed v = l/d ;\n",
" std::cout<<\"Max speed : \"<<vmax<<\" m/s\"<<std::endl ;\n",
" std::cout<<\"Mean speed : \"<<v<<\" m/s\"<<std::endl ;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!rm -f tmp_si.exe && g++ -o tmp_si.exe tmp_si.cpp"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!./tmp_si.exe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Derniers raffinements\n",
"\n",
"Dans notre exemple précédent, on peut regretter la lourdeur de l'expression `50._KM/1._H`, là ou l'on voudrait simplement dire `50_KM/H`. L'utilisation de `/` n'étant pas autorisée dans les suffixes, on ne peut pas créer de suffixe `_KM/H`, mais on peut revenir à la création de simples constantes pour rendre l'écriture valide. Par ailleurs, on peut se passer des `.` en surchargeant également la variante `uns