Free Clojure course. Sign Up for tracking progress →

Clojure: Данные как код

Когда речь заходит о 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 кодом, который можно выполнить.

Instructions

Для закрепления, создайте макрос postfix-notation, который позволяет выполнять такой код (2 2 +) (то есть позволяет записывать формы постфиксной нотацией).

The exercise doesn't pass checking. What to do? 😶

If you've reached a deadlock it's time to ask your question in the «Discussions». How ask a question correctly:

  • Be sure to attach the test output, without it it's almost impossible to figure out what went wrong, even if you show your code. It's complicated for developers to execute code in their heads, but having a mistake before their eyes most probably will be helpful.
In my environment the code works, but not here 🤨

Tests are designed so that they test the solution in different ways and against different data. Often the solution works with one kind of input data but doesn't work with others. Check the «Tests» tab to figure this out, you can find hints at the error output.

My code is different from the teacher's one 🤔

It's fine. 🙆 One task in programming can be solved in many different ways. If your code passed all tests, it complies with the task conditions.

In some rare cases, the solution may be adjusted to the tests, but this can be seen immediately.

I've read the lessons but nothing is clear 🙄

It's hard to make educational materials that will suit everyone. We do our best but there is always something to improve. If you see a material that is not clear to you, describe the problem in “Discussions”. It will be great if you'll write unclear points in the question form. Usually, we need a few days for corrections.

By the way, you can participate in courses improvement. There is a link below to the lessons course code which you can edit right in your browser.

Tips


If you got stuck and don't know what to do, you can ask a question in our huge and friendly community