Бесплатный курс по Racket. Зарегистрируйтесь для отслеживания прогресса →

Racket: Локальные объявления

define в Racket может использоваться как на уровне модуля, так и внутри функций:

(define (f)
  (define text "lorem")
  (displayln text))

(f) ; => "lorem"
; у `define text` локальная область видимости
(displayln text) ; <error>

Но с ним связано несколько тонких моментов:

  • Хотя лиспы похожи между собой, конкретно define ведет себя совершенно по-разному в разных диалектах Lisp. В некоторых объявления останутся локальными для текущей области видимости, в других же объявление всегда будет глобальным.
  • Объявления переменных должно идти в самом начале функции, до любых других выражений.

Существует и другой способ объявить локальные переменные, гораздо более популярный и предсказуемый:

(let ([text "lorem"]) (displayln text)) ; => lorem

Каждое объявление в let - это список из имени и выражения, вычисленное значение которого будет ассоциировано с именем. В наших примерах такие объявления записываются в квадратных скобках исключительно для удобства. Интерпретатору практически всегда понятно, что мы имеем в виду, вне зависимости от того, какие скобки мы использовали (Да-да, в Racket практически везде можно использовать квадратные скобки вместо круглых!). Перепишем уже знакомую нам функцию sum, используя локальные объявления:

; форма записи без локальных объявлений:
(define sum (lambda (x y) (+ x y)))
(sum 8 7) ; вызов глобальной функции

; форма записи с локальными объявлениями:
(let ([sum (lambda (x y) (+ x y))])
  (sum 8 7)) ; вызов локальной функции

Все объявления внутри let доступны только в выражениях, которые вызываются внутри самого let после списка объявлений (Объявления не видят друг друга! Подробнее - ниже). Вот ещё несколько более сложных примеров, с несколькими объявлениями:

(let ([x 2]
      [y (+ 4 3)])
  (+ x y)) ; 9
(define (sum-of-squares x y)
  (let ([x-square (* x x)]
        [y-square (* y y)])
    (+ x-square y-square)))

Можно пойти еще дальше - вызывать локальную функцию внутри глобальной:

(define (sum-of-squares x y)
  (let ([square (lambda (n) (* n n))])
    (+ (square x) (square y))))

(sum-of-squares 8 7) ; => 113

Объявления, созданные в рамках одного использования let, не видны друг другу. Поэтому мы не можем сделать так:

(let ([x 2]
      [y (* x 10)]) ; здесь - ошибка, потому что
                    ; переменная x ещё не объявлена.
  (+ x y))

Форма let так устроена, что каждое объявление из списка она создаёт "с чистого листа", поэтому объявления не зависят друг от друга, что иногда удобно. Скажем, мы можем свободно менять местами объявления в пределах одного списка. Если же нам непременно нужно использовать в последующих объявлениях предыдущие, мы можем воспользоваться формой let*:

(let* ([x 2]
       [y (* x 20)]
       [z (+ x y)])
  z)                ; 42

Задание

Реализуйте функцию square-of-sum, которая сначала складывает числа, а затем возводит в квадрат. Воспользуйтесь локальными объявлениями для хранения промежуточных результатов вычисления.

(square-of-sum 2 3) ; 25
Упражнение не проходит проверку — что делать? 😶

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

  • Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨

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

Мой код отличается от решения учителя 🤔

Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Прочитал урок — ничего не понятно 🙄

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются https://github.com/hexlet-basics
Если вы столкнулись с трудностями и не знаете, что делать, задайте вопрос в нашем большом и дружном сообществе