Вот мои аргументы о том, почему функциональное программирование может и должно использоваться для вычислительной науки. Преимущества огромны, а минусы быстро уходят. На мой взгляд, есть только один минус:
Con : отсутствие языковой поддержки в C / C ++ / Fortran
По крайней мере, в C ++ этот недостаток исчезает - в C ++ 14/17 добавлены мощные средства поддержки функционального программирования. Возможно, вам придется написать некоторый код библиотеки / поддержки самостоятельно, но язык будет вашим другом. В качестве примера приведем библиотеку (Warning: Plug), которая делает неизменяемые многомерные массивы в C ++: https://github.com/jzrake/ndarray-v2 .
Кроме того, здесь есть ссылка на хорошую книгу по функциональному программированию на C ++, хотя она не ориентирована на научные приложения.
Вот мое резюме того, что я считаю профессионалами:
Плюсы :
- правильность
- Понятность
- Спектакль
С точки зрения корректности , функциональные программы явно корректны : они вынуждают вас правильно определять минимальное состояние ваших физических переменных и функцию, которая продвигает это состояние вперед во времени:
int main()
{
auto state = initial_condition();
while (should_continue(state))
{
state = advance(state);
side_effects(state);
}
return 0;
}
Решение уравнения в частных производных (или ОДУ) идеально подходит для функционального программирования; вы просто применяете чистую функцию ( advance
) к текущему решению, чтобы сгенерировать следующее.
По моему опыту, программное обеспечение для физического моделирования в целом обременено плохим управлением государством . Обычно каждый этап алгоритма работает на некотором фрагменте общего (эффективно глобального) состояния. Это затрудняет или даже делает невозможным обеспечение правильного порядка операций, оставляя программное обеспечение уязвимым для ошибок, которые могут проявляться как ошибки сегмента, или, что еще хуже, в терминах ошибок, которые не приводят к сбою кода, но бесшумно нарушают целостность его науки. выход. Попытка управлять общим состоянием в физическом моделировании также препятствует многопоточности, что является проблемой в будущем, поскольку суперкомпьютеры стремятся к большему количеству ядер, а масштабирование с использованием MPI часто достигает максимума при ~ 100 тыс. Задач. Напротив, функциональное программирование делает параллелизм разделяемой памяти тривиальным из-за неизменности.
Производительность также улучшается в функциональном программировании из-за ленивой оценки алгоритмов (в C ++ это означает генерацию множества типов во время компиляции - часто по одному для каждого приложения функции). Но это уменьшает накладные расходы на доступ к памяти и ее распределение, а также устраняет виртуальную диспетчеризацию, позволяя компилятору оптимизировать весь алгоритм, просматривая сразу все функциональные объекты, которые его составляют. На практике вы будете экспериментировать с различным расположением точек оценки (где результат алгоритма кэшируется в буфер памяти), чтобы оптимизировать использование ресурсов ЦП и памяти. Это довольно просто из-за высокой локализации (см. Пример ниже) этапов алгоритма по сравнению с тем, что вы обычно видите в модуле или коде на основе классов.
Функциональные программы легче понять, поскольку они упрощают физическое состояние. Это не значит, что их синтаксис понятен всем вашим коллегам! Авторы должны быть осторожны в использовании хорошо названных функций, а исследователи в целом должны привыкнуть видеть алгоритмы, выраженные функционально, а не процедурно. Я признаю, что отсутствие контрольных структур может быть неприятным для некоторых, но я не думаю, что это должно помешать нам идти в будущее, способное создавать более качественную науку на компьютерах.
Ниже приведен пример advance
функции, адаптированной из кода конечного объема с использованием ndarray-v2
пакета. Обратите внимание на to_shared
операторы - это те оценки, на которые я ссылался ранее.
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() };
}