Clojure — Глобальные и локальные символы

До сих пор, когда требовалось сохранить значение в памяти, мы использовали понятие «переменная», выполняя «определение символа» с помощью команды «def». Однако такой подход соответствует тому, что в более традиционных языках мы знаем как «определение глобальной переменной».

Как вы знаете, определения «глобальных переменных» могут принести ряд проблем приложениям, так как практически невозможно иметь представление обо всех, и с этим могут возникнуть проблемы с заменой значений во время их использования, что может вызвать ошибки в различных точках приложения и сделать приложение нестабильным и сложным в обслуживании.

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

Глобальный масштаб

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

Тот, который показывает «стандартное» среднее значение плюс дополнительное очко;
Другой, показывающий средний показатель студента в состоянии «восстановления», что добавляет два дополнительных балла к среднему показателю;
В то время как программа, показывающая «стандартное» среднее, обращается к глобальному символу, чтобы произвести расчет без каких-либо изменений, программа, показывающая среднее для студентов в состоянии «восстановления», также использует тот же глобальный символ, но всегда меняет его значение на «два».

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

(def ponto-extra 1)

(defn verifica-situacao-aluno-padrao
  "Retorna a media do aluno somada a seu ponto extra"
  [nota1 nota2]
  (+ (/ (+ nota1 nota2) 2) ponto-extra)
)
(defn verifica-situacao-aluno-recuperacao
  "Retorna a media do aluno somada a seu ponto extra (em caso de recuperação)"
  [nota1 nota2]
  (def ponto-extra 2)
  (+ (/ (+ nota1 nota2) 2) ponto-extra)
)

ponto-extra
(verifica-situacao-aluno-padrao 5 5)
(verifica-situacao-aluno-recuperacao 5 5)
(verifica-situacao-aluno-padrao 5 5)
Войдите в полноэкранный режим Выход из полноэкранного режима

Об изображении:

  • В третьей строке мы определяем наш глобальный символ «dot-extra»;
  • С пятой по шестнадцатую строку мы определяем две наши функции, «по умолчанию» и «восстановление» соответственно;
  • На девятнадцатой строке мы проверяем значение нашего глобального символа «dot-extra», которое равно «1»;
  • В двадцать первой строке мы запрашиваем ситуацию студента со «стандартным» распорядком и видим, что его среднее значение равно «6»;
  • В двадцать третьей строке мы знакомимся с ситуацией ученика с «восстановительной» рутиной и видим, что его средний показатель равен «7»;
  • В строке двадцать пять мы снова рассматриваем ситуацию с учеником со «стандартной» программой, и теперь мы видим другой результат, его среднее значение теперь «7», то есть такое же значение, как и у ученика с «восстановительной» программой;
  • В двадцать седьмой строке мы снова проверяем значение нашего глобального символа «dot-extra», и, как и ожидалось, оно теперь равно «2»;

Как уже объяснялось, функция «восстановления» изменяет значение глобального символа, который используется другими функциями, это изменение не ожидалось в других частях приложения, и это породило проблему расхождения значений, и, что еще хуже, к моментам, когда проблема не возникает (так как она возникает только с первого выполнения процедуры «восстановления») и процедурам, которые продолжают работать (процедура «восстановления» работает всегда). Мы только что создали ошибку в нашем приложении, которую будет совсем не просто смоделировать/найти/исправить.

Местный масштаб

Как уже было замечено ранее, неуместное использование глобального символа породило ошибку в нашем приложении, для решения этой проблемы разработчику, создавшему функцию «recovery», следовало бы использовать в этой процедуре символ локальной области видимости, поскольку используемое там значение «dot-extra» является правилом, специфичным для этой процедуры.

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

  • Слово «let» должно использоваться между круглыми скобками;
  • Он получает массив символов/значений;
  • Он получает одну или несколько функций;
  • Он возвращает результат выполнения своей последней функции;
(let [idade 40] (+ idade 1))
Войдите в полноэкранный режим Выход из полноэкранного режима

На изображении выше:

  • В первой строке мы создаем нашу локальную область видимости с помощью «let», в этой области видимости у нас есть символ «age» со значением «40» внутри вектора, и у нас есть функция, которая суммирует значение «1» с нашим символом «age»;
  • Во второй строке мы имеем результат функции sum;
  • В третьей строке мы пытаемся использовать символ «age» и получаем ошибку, говорящую о том, что не удалось решить символ;

Ну, теперь, когда мы знаем, как решить проблему с областью видимости, давайте применим исправление в нашей функции «recovery», чтобы она перестала использовать глобальный символ, сначала сбросим глобальный символ на его правильное значение, затем исправим функцию и запустим тесты:

(def ponto-extra 1)

(defn verifica-situacao-aluno-recuperacao
  "Retorna a media do aluno somada a seu ponto extra (em caso de recuperação)"
  [nota1 nota2]
  (let [ponto-extra-recuperacao 2]
    (+ (/ (+ nota1 nota2) 2) ponto-extra-recuperacao))  
)

(verifica-situacao-aluno-padrao 5 5)
(verifica-situacao-aluno-recuperacao 5 5)
(verifica-situacao-aluno-padrao 5 5)
ponto-extra
Войдите в полноэкранный режим Выход из полноэкранного режима

На изображении выше:

  • В первой строке мы сбросили наш глобальный символ на его правильное значение;
  • С третьей по восьмую строку мы рефакторим нашу функцию, используя «let»;
  • В строках десять, двенадцать и четырнадцать мы повторяем тесты, и теперь их соответствующие результаты верны;

Заключение

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

Узнать больше

  • Clojure
  • Функциональное программирование
  • Первоначально написано здесь

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *