Ecco i miei argomenti sul perché la programmazione funzionale può e dovrebbe essere utilizzata per la scienza computazionale. I vantaggi sono enormi e gli svantaggi stanno rapidamente scomparendo. Nella mia mente c'è solo un aspetto negativo:
Contro : mancanza di supporto linguistico in C / C ++ / Fortran
Almeno in C ++, questo truffatore sta scomparendo, poiché C ++ 14/17 ha aggiunto potenti funzionalità per supportare la programmazione funzionale. Potrebbe essere necessario scrivere tu stesso un codice di libreria / supporto, ma la lingua sarà tua amica. Ad esempio, ecco una libreria (warning: plug) che esegue immutabili array multidimensionali in C ++: https://github.com/jzrake/ndarray-v2 .
Inoltre, ecco un link a un buon libro sulla programmazione funzionale in C ++, sebbene non sia focalizzato su applicazioni scientifiche.
Ecco il mio riassunto di ciò che credo siano i professionisti:
Pro :
- Correttezza
- Comprensibilità
- Prestazione
In termini di correttezza , i programmi funzionali sono manifestamente ben posizionati : ti costringono a definire correttamente lo stato minimo delle variabili fisiche e la funzione che avanza nello stato in avanti nel tempo:
int main()
{
auto state = initial_condition();
while (should_continue(state))
{
state = advance(state);
side_effects(state);
}
return 0;
}
Risolvere un'equazione differenziale parziale (o ODE) è perfetto per la programmazione funzionale; stai solo applicando una funzione pura (advance
) alla soluzione corrente per generare quella successiva.
Nella mia esperienza, il software di simulazione fisica è generalmente gravato da una cattiva gestione dello stato . Di solito, ogni fase dell'algoritmo opera su un pezzo di uno stato condiviso (effettivamente globale). Ciò rende difficile, o addirittura impossibile, garantire il corretto ordine delle operazioni, lasciando il software vulnerabile a bug che possono manifestarsi come errori seg, o peggio, termini di errore che non bloccano il codice ma compromettono silenziosamente l'integrità della sua scienza produzione. Il tentativo di gestire lo stato condiviso in una simulazione fisica inibisce anche il multi-threading, il che è un problema per il futuro, poiché i supercomputer si stanno muovendo verso conteggi core più alti e il ridimensionamento con MPI spesso supera i compiti di ~ 100k. Al contrario, la programmazione funzionale rende banale il parallelismo della memoria condivisa, a causa dell'immutabilità.
Prestazione sono migliorate anche nella programmazione funzionale grazie alla valutazione pigra degli algoritmi (in C ++, ciò significa generare molti tipi in fase di compilazione, spesso uno per ogni applicazione di una funzione). Ma riduce il sovraccarico di accessi e allocazioni di memoria, oltre a eliminare l'invio virtuale - consentendo al compilatore di ottimizzare un intero algoritmo vedendo subito tutti gli oggetti funzione che lo compongono. In pratica, sperimenterai diverse disposizioni dei punti di valutazione (in cui il risultato dell'algoritmo viene memorizzato nella cache in un buffer di memoria) per ottimizzare l'uso della CPU rispetto alle allocazioni di memoria. Ciò è piuttosto semplice a causa dell'alta località (vedi l'esempio seguente) delle fasi dell'algoritmo rispetto a ciò che vedrai in genere in un modulo o in un codice basato su classi.
I programmi funzionali sono più facili da capire in quanto banalizzano lo stato della fisica. Ciò non significa che la loro sintassi sia facilmente comprensibile da tutti i tuoi colleghi! Gli autori dovrebbero fare attenzione a usare funzioni ben denominate e i ricercatori in generale dovrebbero abituarsi a vedere gli algoritmi espressi in modo funzionale anziché procedurale. Devo ammettere che l'assenza di strutture di controllo può essere scoraggiante per alcuni, ma non credo che ciò dovrebbe impedirci di andare nel futuro in grado di fare scienza di qualità migliore sui computer.
Di seguito è riportata una advance
funzione di esempio , adattata da un codice a volume finito usando il ndarray-v2
pacchetto. Nota gli to_shared
operatori: questi sono i punti di valutazione a cui alludevo prima.
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() };
}