Введение

В предыдущих частях этой серии мы исследовали концепции функционального программирования в Julia, такие как функции высшего порядка и рекурсия. В этой третьей части мы углубимся в ленивые вычисления и как их реализовать в Джулии. Ленивая оценка — это стратегия оценки, которая позволяет нам отложить выполнение дорогостоящих вычислений до тех пор, пока они не потребуются, предотвращая ненужные вычисления и оптимизируя производительность наших программ.

Предыдущие статьи см.:

Использование переходников для задержки вычислений

Преобразователи — это анонимные функции, которые оборачивают выражение или вычисление, откладывая его вычисление до тех пор, пока оно не будет вызвано явным образом. Они могут быть полезны в ситуациях, когда мы хотим избежать вычисления выражения, если в этом нет абсолютной необходимости, например, истинная ветвь оператора if или при работе с рекурсивными функциями.

Чтобы продемонстрировать силу преобразователей и отложенных вычислений, давайте определим функцию медленного сложения, которая имитирует дорогостоящие вычисления, приостанавливаясь на 5 секунд:

Мы также определим специальную функцию умножения, которая принимает целое число x, и функцию y, возвращающую целое число. Функция ведет себя следующим образом:

  1. Если x равно 0, он возвращает 0 без оценки y.
  2. Если x равно 1, он вычисляет y один раз и возвращает результат.
  3. Для других значений x он оценивает y рекурсивно x раз и возвращает сумму.

Теперь давайте посмотрим, как преобразователи могут помочь нам отложить оценку и избежать ненужных вычислений:

Первый результат выше не требует времени для оценки и возврата.

результат 2 занимает 5 секунд для оценки.

result3 занимает в два раза больше времени и оценивается за 10 секунд, а затем за 15 секунд в последнем примере.

Суть в том, что с нулевым умножением все работает отлично, преобразователь нам не нужен, он не вычислялся. И мы не делали предварительных расчетов и не тратили время на дорогостоящий расчет.

Во втором примере, result2, нам понадобился преобразователь, и мы вызвали его один раз. Мы потратили некоторое время на выполнение дорогостоящего расчета. Тем не менее, нам это действительно было нужно, так что не так уж плохо.

Теперь на оставшиеся случаи мы действительно потратили больше времени, чем если бы просто предварительно все просчитали. Поскольку функция рекурсивная, в итоге мы неоднократно вызывали преобразователь.

Как мы можем получить лучшее из обоих миров? В идеале мы не будем выполнять дорогостоящие вычисления, пока они нам не понадобятся. Если мы вычислим его, нам нужно будет запомнить результат, чтобы не пересчитывать его, если он понадобится нам снова.

Имея в виду эти несколько расплывчатые идеи, давайте реализуем нашу собственную ленивую оценку в Джулии.

Реализация ленивой оценки с задержкой и силой

Чтобы реализовать ленивую оценку в Julia, мы можем использовать метод под названием «Задержка и сила». Этот подход включает в себя создание изменяемой структуры с именем LazyPair, в которой будет храниться состояние вычисления и функция, подлежащая ленивому вычислению:

Далее мы определим функцию с именем myForce, которая будет либо возвращать результат оценки, либо ранее вычисленный результат, в зависимости от того, был ли оценен LazyPair или нет:

Такой подход позволяет нам извлечь выгоду из обоих миров:

  1. Мы не выполняем дорогостоящие вычисления до тех пор, пока они нам не понадобятся. По сути, когда мы создавали LazyPair, мы говорили «вот ваша функция, я обещаю вам, если вы меня заставите, я ее оценю».
  2. Мы запоминаем ответ, поэтому нам не нужно пересчитывать его, если он нам снова понадобится.

Теперь мы можем протестировать нашу реализацию с нашими функциями slow_Add и special_Mult, используя нашу ленивую оценку:

Чтобы получить результат вычислений, мы просто вызываем функцию myForce:

Заключение

В этой части нашей серии статей о функциональном программировании в Julia мы рассмотрели ленивое вычисление как метод оптимизации производительности за счет откладывания выполнения дорогостоящих вычислений до тех пор, пока они не потребуются. Мы продемонстрировали использование преобразователей для задержки вычислений и представили подход «Задержка и принудительное выполнение» для реализации ленивых вычислений в Julia, который по умолчанию не является ленивым, как Haskell. Понимание и внедрение ленивых вычислений может быть полезно для оптимизации ваших программ на Julia и расширения набора инструментов функционального программирования.

Удачного кодирования!

😄👩‍💻👨‍💻