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
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1#lang racket
2
3(require (only-in rackunit check-equal? test-begin))
4(require "index.rkt")
5
6(test-begin
7 (check-equal? (square-of-sum 2 3) 25)
8 (check-equal? (square-of-sum 5 8) 169))
9
Решение учителя откроется через: