Когда речь заходит о Lisp-подобных языках, часто упоминается фраза "код как данные", разберемся, что же она означает и как эта фраза связана с макросами (об этом немного говорилось в теме Списки).
Рассмотрим примеры Clojure кода и Python (немножко разнообразия никогда не помешает!):
def func(foo):
return do_something(foo)
В Clojure аналогичный код будет выглядеть так:
(defn func [foo]
(do-something foo))
Синтаксис очень похож, разница лишь в том, что код на Clojure записывается внутри списка. Lisp-подобные языки не различают выражения (func arg1 arg2)
и (1 2 3)
. Рассмотрим это на примере:
; Нет необходимости даже объявлять func, arg1 и arg2
; про символ ' будет описано чуть позже
(count '(func arg1 arg2))
; => 3
(count '(1 2 3))
; => 3
Однако в Python так сделать нельзя:
len(func(arg1, arg2))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'func' is not defined
А какая здесь связь с макросами? Если описывать упрощенно, то макросы создают валидные lisp формы для их выполнения (evaluation). Макросы можно воспринимать как lisp трансляторы: вы передаете какие-то данные и макрос переводит эти данные уже в валидные lisp данные (которые как код можно выполнить).
Теперь переключимся на символ '
, который мы использовали в коде выше. Зачем он нужен? В Lisp-подобных языках есть такие понятия как символ (symbol) и значение (value) и очень важно понимать, в чем между ними разница. Например:
foo = 10
В таком коде мы размышляем о переменной foo
как о 10
, грубо говоря, мы думаем о значении, а не о символе foo
, которое оно представляет. В Clojure же (и во всех Lisp-подобных языках) ситуация другая. Язык позволяет ссылаться на символ не затрагивая его значения. В большинстве других языков такое невозможно. То есть если вы хотите сослаться на символ (эта тема уже затрагивалась в модуле со списками), нужно воспользоваться '
(мы еще вернемся к теме символов). А теперь рассмотрим пример:
(def foo 10)
; мы объявили символ и его значение
; ссылаемся на значение символа
foo
; => 10
; ссылаемся на символ
'foo
; => foo
Итак, после такой долгой подготовки напишем простенький макрос! В Lisp-подобных языках используется префиксная нотация, создадим макрос, который позволит записывать простые операции инфиксной нотацией, например (2 + 2)
. В наш макрос передается список из трех элементов (да, +
тоже является элементом списка). Затем нам нужно переставить переданные данные в валидное lisp выражение, то есть мы хотим получить такой эффект (1 + 2) -> (+ 1 2)
. Как можно получить такое выражение? Всего лишь передвинуть второй элемент списка в начало!
(defmacro infix-notation [[left operator right]]
(list operator left right))
(infix-notation (1 + 2))
; => 3
(infix-notation (1 > 2))
; => false
(1 + 2)
и (1 > 2)
не являются валидным lisp кодом, но наш макрос позволяет перевести его в (+ 1 2)
и (> 1 2)
соответственно, а эти выражения уже являются корректным lisp кодом, который можно выполнить.
Для закрепления, создайте макрос postfix-notation
, который позволяет выполнять такой код (2 2 +)
(то есть позволяет записывать формы постфиксной нотацией).
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1(ns data-and-code-test
2 (:require [test-helper :refer [assert-solution]]
3 [index :refer [postfix-notation]]))
4
5(assert-solution
6 [[[2 2 =]] [[2 2 +]] [[2 2 >]] [[2 2 /]]] [true 4 false 1]
7 (fn [[op1 op2 operation]] (postfix-notation (op1 op2 operation))))
8
Решение учителя откроется через: