Voici mes arguments pour expliquer pourquoi la programmation fonctionnelle peut et doit être utilisée pour la science informatique. Les avantages sont vastes et les inconvénients disparaissent rapidement. Dans mon esprit, il n'y a qu'un seul problème:
Con : manque de prise en charge de la langue en C / C ++ / Fortran
Au moins en C ++, ce problème est en train de disparaître - car C ++ 14/17 a ajouté de puissantes fonctionnalités pour prendre en charge la programmation fonctionnelle. Vous devrez peut-être écrire vous-même un code de bibliothèque / support, mais le langage sera votre ami. A titre d'exemple, voici une bibliothèque (warning: plug) qui fait des tableaux multidimensionnels immuables en C ++: https://github.com/jzrake/ndarray-v2 .
En outre, voici un lien vers un bon livre sur la programmation fonctionnelle en C ++, bien qu’il ne soit pas axé sur les applications scientifiques.
Voici mon résumé de ce que je crois être les pros:
Avantages :
- La justesse
- Compréhensibilité
- Performance
En termes d' exactitude , les programmes fonctionnels sont manifestement bien posés : ils vous obligent à définir correctement l'état minimal de vos variables physiques et la fonction qui fait avancer cet état dans le temps:
int main()
{
auto state = initial_condition();
while (should_continue(state))
{
state = advance(state);
side_effects(state);
}
return 0;
}
La résolution d'une équation aux dérivées partielles est idéale pour la programmation fonctionnelle. vous ne faites qu'appliquer une pure fonction ( advance
) à la solution actuelle pour générer la suivante.
D'après mon expérience, les logiciels de simulation physique sont généralement grevés par une gestion médiocre de l' état . Habituellement, chaque étape de l'algorithme opère sur une partie d'un état partagé (globalement global). Cela rend difficile, voire impossible, d'assurer le bon ordre des opérations, laissant le logiciel vulnérable aux bugs pouvant se manifester par des erreurs de segmentation, ou pire, des termes d'erreur qui ne font pas planter votre code, mais compromettaient en silence l'intégrité de sa science. sortie. Tenter de gérer un état partagé dans une simulation physique inhibe également le multi-threading - un problème pour le futur, car les supercalculateurs évoluent vers un nombre de cœurs plus élevé et la mise à l'échelle avec MPI dépasse souvent les tâches d'environ 100 000 tâches. En revanche, la programmation fonctionnelle rend le parallélisme de la mémoire partagée trivial, du fait de son immuabilité.
Les performances sont également améliorées dans la programmation fonctionnelle grâce à l'évaluation paresseuse des algorithmes (en C ++, cela signifie générer plusieurs types au moment de la compilation - souvent un pour chaque application d'une fonction). Mais cela réduit la surcharge des accès et des allocations de mémoire, ainsi que l’élimination de la répartition virtuelle, ce qui permet au compilateur d’optimiser tout un algorithme en affichant immédiatement tous les objets fonction qui le composent. En pratique, vous allez expérimenter différents arrangements des points d'évaluation (où le résultat de l'algorithme est mis en cache dans une mémoire tampon) pour optimiser l'utilisation de la CPU par rapport aux allocations de mémoire. Ceci est plutôt facile en raison de la localité élevée (voir l'exemple ci-dessous) des étapes de l'algorithme par rapport à ce que vous verrez généralement dans un module ou un code basé sur une classe.
Les programmes fonctionnels sont plus faciles à comprendre dans la mesure où ils banalisent l’état physique. Cela ne veut pas dire que leur syntaxe est facilement compréhensible par tous vos collègues! Les auteurs doivent veiller à utiliser des fonctions bien nommées et les chercheurs en général doivent s'habituer à voir les algorithmes exprimés de manière fonctionnelle plutôt que procédurale. J'admets que l'absence de structures de contrôle peut déranger certaines personnes, mais je ne pense pas que cela devrait nous empêcher d'aller de l'avant, capables de faire de la science de meilleure qualité sur des ordinateurs.
Vous trouverez ci-dessous un exemple de advance
fonction, adaptée d’un code de volume fini utilisant le ndarray-v2
package. Notez les to_shared
opérateurs - ce sont les points d’évaluation auxquels je faisais allusion tout à l’heure.
auto advance(const solution_state_t& state)
{
auto dt = determine_time_step_size(state);
auto du = state.u
| divide(state.vertices | volume_from_vertices)
| nd::map(recover_primitive)
| extrapolate_boundary_on_axis(0)
| nd::to_shared()
| compute_intercell_flux(0)
| nd::to_shared()
| nd::difference_on_axis(0)
| nd::multiply(-dt * mara::make_area(1.0));
return solution_state_t {
state.time + dt,
state.iteration + 1,
state.vertices,
state.u + du | nd::to_shared() };
}