Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Third macro exercise #72

Merged
merged 3 commits into from Dec 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions modules/45-state/10-about-state/description.ru.yml
Expand Up @@ -28,7 +28,7 @@ theory: |
(deref resource) ; => 12
```

Как видно из примеров, функция `swap!` требует атом, функцию модификатор (обычная функция, более сложные модификации рассмотрим чуть позже) и значение для функции модификатора. Для функции `reset!` достаточно атома и значения, на которое оно заменится. Удобство в том, что эти функции потокобезопасны и транзакционны, если произойдет ошибка, то транзакция откатится.
Как видно из примеров, функция `swap!` требует атом, функцию модификатор (обычная функция, более сложные модификации рассмотрим чуть позже) и значение для функции модификатора. Для функции `reset!` достаточно атома и значения, на которое оно заменится. Удобство в том, что эти функции потокобезопасны и транзакционны, если произойдет ошибка, то транзакция откатится.

instructions: |
Реализуйте функцию `transit`, которая принимает два атома, которые представляют счета в банках и число денег, которое нужно перевести с первого на второй аккаунт, в результате выполнения функции, верните счета в виде вектора. Больше подробностей в примерах.
Expand All @@ -40,4 +40,8 @@ instructions: |
; => [0 80]
```

tips: []
tips:
- |
[Про язык Elixir](https://elixir-lang.org/)
- |
[Про MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)
5 changes: 1 addition & 4 deletions modules/60-macros/20-macro-rules/description.ru.yml
Expand Up @@ -58,9 +58,6 @@ theory: |
- Данные, возвращаемые макросом немедленно исполняются и результат этого выполнения отдается наружу.

instructions: |
Исправьте `triplet-macro`, чтобы он работал так же, как и `triplet-fn` (не забывайте третье правило макросов!). Затем выведите результат (с помощью `println`) для следующих аргументов:

- 1, 2, 3
- -1, -2, -3
Исправьте `triplet-macro`, чтобы он работал так же, как и `triplet-fn` (не забывайте третье правило макросов!).

tips: []
3 changes: 0 additions & 3 deletions modules/60-macros/20-macro-rules/index.clj
Expand Up @@ -2,7 +2,4 @@
;BEGIN
(defmacro triplet-macro [a b c]
(list list a b c))

(println (triplet-macro 1 2 3))
(println (triplet-macro -1 -2 -3))
;END
8 changes: 6 additions & 2 deletions modules/60-macros/20-macro-rules/test.clj
@@ -1,4 +1,8 @@
(ns macro-rules-test
(:require [test-helper :refer [assert-output]]))
(:require [test-helper :refer [assert-solution]]
[index :refer [triplet-macro]]))

(assert-output "(1 2 3)\n(-1 -2 -3)")
(assert-solution
[[[1 2 3]] [[-1 -2 -3]] [[1.2 0 2/3]] [["a" "b" ["c"]]]]
[(list 1 2 3) (list -1 -2 -3) (list 1.2 0 2/3) (list "a" "b" ["c"])]
(fn [[a b c]] (triplet-macro a b c)))
2 changes: 2 additions & 0 deletions modules/60-macros/30-data-and-code/Makefile
@@ -0,0 +1,2 @@
test:
@ test.sh
6 changes: 6 additions & 0 deletions modules/60-macros/30-data-and-code/bb.edn
@@ -0,0 +1,6 @@
{:tasks
{:requires ([babashka.classpath :as cp])
:init (do
(cp/add-classpath ".")
(load-file "../../../src/test-helper.clj"))
check-solution (load-file "test.clj")}}
82 changes: 82 additions & 0 deletions modules/60-macros/30-data-and-code/description.ru.yml
@@ -0,0 +1,82 @@
---

name: Данные как код
theory: |
Когда речь заходит о Lisp-подобных языках, часто упоминается фраза "код как данные", разберемся, что же она означает и как эта фраза связана с макросами (об этом немного говорилось в теме Списки).

Рассмотрим примеры Clojure кода и Python (немножко разнообразия никогда не помешает!):

```python
def func(foo):
return do_something(foo)
```

В Clojure аналогичный код будет выглядеть так:

```clojure
(defn func [foo]
(do-something foo))
```

Синтаксис очень похож, разница лишь в том, что код на Clojure записывается внутри *списка*. Lisp-подобные языки не различают выражения `(func arg1 arg2)` и `(1 2 3)`. Рассмотрим это на примере:

```clojure
; Нет необходимости даже объявлять func, arg1 и arg2
; про символ ' будет описано чуть позже
(count '(func arg1 arg2))
; => 3

(count '(1 2 3))
; => 3
```

Однако в Python так сделать нельзя:

```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) и очень важно понимать, в чем между ними разница. Например:

```python
foo = 10
```

В таком коде мы размышляем о переменной `foo` как о `10`, грубо говоря, мы думаем о *значении*, а не о *символе* `foo`, которое оно представляет. В Clojure же (и во всех Lisp-подобных языках) ситуация другая. Язык позволяет ссылаться на *символ* не затрагивая его *значения*. В большинстве других языков такое невозможно. То есть если вы хотите сослаться на символ, нужно воспользоваться `'` (мы еще вернемся к теме символов). А теперь рассмотрим пример:

```clojure
(def foo 10)
; мы объявили символ и его значение

; ссылаемся на значение символа
foo
; => 10

; ссылаемся на символ
'foo
; => foo
```
Итак, после такой долгой подготовки напишем простенький макрос! В Lisp-подобных языках используется префиксная нотация, создадим макрос, который позволит записывать простые операции инфиксной нотацией, например `(2 + 2)`. В наш макрос передается список из трех элементов (да, `+` тоже является элементом списка). Затем нам нужно переставить переданные данные в валидное lisp выражение, то есть мы хотим получить такой эффект `(1 + 2) -> (+ 1 2)`. Как можно получить такое выражение? Всего лишь передвинуть второй элемент списка в начало!

```clojure
(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 +)` (то есть позволяет записывать формы постфиксной нотацией).

tips: []
5 changes: 5 additions & 0 deletions modules/60-macros/30-data-and-code/index.clj
@@ -0,0 +1,5 @@
(ns index)
;BEGIN
(defmacro postfix-notation [[left right operator]]
(list operator left right))
;END
7 changes: 7 additions & 0 deletions modules/60-macros/30-data-and-code/test.clj
@@ -0,0 +1,7 @@
(ns data-and-code-test
(:require [test-helper :refer [assert-solution]]
[index :refer [postfix-notation]]))

(assert-solution
[[[2 2 =]] [[2 2 +]] [[2 2 >]] [[2 2 /]]] [true 4 false 1]
(fn [[op1 op2 operation]] (postfix-notation (op1 op2 operation))))