diff --git a/modules/10-basics/10-hello-world/description.en.yml b/modules/10-basics/10-hello-world/description.en.yml
deleted file mode 100644
index 575b674..0000000
--- a/modules/10-basics/10-hello-world/description.en.yml
+++ /dev/null
@@ -1,32 +0,0 @@
----
-
-name: Hello, World!
-theory: |
- Clojure is a modern dialect of Lisp that encourages functional programming. The language uses the JVM platform. Like all Lisp-like languages, it is homoiconicity (code as data). Sometimes Clojure is called a pragmatic Lisp, because in addition to the advantages of Lisp, the language combines features of the JVM, which simplifies the creation of programs (there are even features for scripting with Clojure!). For a better understanding of the philosophy of the language, it is worth reading the report Simple Made Easy by Rich Hickey, the creator of the language.
-
- Traditionally, we will begin by writing a program called "Hello, World! This program will display text on the screen:
-
- Hello, World!
-
-
- To print something on the screen, you need to call a function (a symbolic expression). In Clojure, `(println some-str)` function is used, where `some-str` is a string enclosed in double quotes.
-
-instructions: |
- Copy the exact code from the instructions into the editor and run it by clicking “Run”.
-
- ```clojure
- (println "Hello, World!")
- ```
-
- Note that if you type hello, woRld! instead of Hello, World!, it will count as different text, because upper and lowercase letters are different symbols in Clojure. Letter size is called case, and any programmer will tell you that case is important. This rule affects almost everything in coding, so make sure you get used to paying close attention to the case at all times.
-tips:
- - |
- [Clojure official](https://clojure.org/)
- - |
- [Homoiconicity](https://en.wikipedia.org/wiki/Homoiconicity)
- - |
- [Rich Hickey, Simple Made Easy](https://www.youtube.com/watch?v=SxdOUGdseq4)
- - |
- [Babashka, Clojure scripting instrument](https://github.com/babashka/babashka)
- - |
- [S-expression](https://en.wikipedia.org/wiki/S-expression)
diff --git a/modules/10-basics/10-hello-world/description.ru.yml b/modules/10-basics/10-hello-world/description.ru.yml
deleted file mode 100644
index 7b9fec3..0000000
--- a/modules/10-basics/10-hello-world/description.ru.yml
+++ /dev/null
@@ -1,34 +0,0 @@
----
-
-name: Привет, Мир!
-theory: |
- Clojure - диалект Лиспа, который поощряет функциональное программирование. Сам язык использует платформу JVM. Так же, как и другие лиспо-подобные языки, Clojure - гомоиконный. Иногда Clojure называют прагматичным Лиспом, из-за того, что помимо преимуществ Лиспа язык совмещает в себе возможности JVM. Для понимания философии языка, стоит ознакомиться с докладом Simple Made Easy Рича Хикки, создателя языка.
-
- По традиции начнем с написания программы «Hello, World!». Эта программа будет выводить на экран текст:
-
- Hello, World!
-
-
- Чтобы вывести что-то на экран, нужно вызвать функцию . В языке Clojure вызов функции печати на экран — `(println some-str)`, где `some-str` - строка, заключенная в двойные кавычки.
-
-instructions: |
- Наберите в редакторе код из задания символ в символ и нажмите «Проверить».
-
- ```clojure
- (println "Hello, World!")
- ```
-
- Внимание: если вы напишете `heLLo, woRld!` вместо `Hello, World!`, то это будет считаться другим текстом, потому что заглавные и строчные буквы — это разные символы, отличающиеся _регистром_. В программировании регистр практически всегда имеет значение, поэтому привыкайте всегда обращать на него внимание!
-tips:
- - |
- [Немного о 'Hello, World!'](https://ru.hexlet.io/blog/posts/moy-chelovek-menya-ponimaet-istoriya-frazy-hello-world-i-ee-analogov)
- - |
- [Clojure](https://clojure.org/)
- - |
- [Гомоиконность](https://ru.wikipedia.org/wiki/%D0%93%D0%BE%D0%BC%D0%BE%D0%B8%D0%BA%D0%BE%D0%BD%D0%B8%D1%87%D0%BD%D0%BE%D1%81%D1%82%D1%8C)
- - |
- [Выступление создателя языка, Рича Хикки](https://www.youtube.com/watch?v=SxdOUGdseq4)
- - |
- [Babashka, инструмент, упрощающий скриптинг на Clojure](https://github.com/babashka/babashka)
- - |
- [Символьные выражение, S-expression](https://ru.wikipedia.org/wiki/S-%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5)
diff --git a/modules/10-basics/10-hello-world/en/data.yml b/modules/10-basics/10-hello-world/en/data.yml
index 78980a7..80e3aec 100644
--- a/modules/10-basics/10-hello-world/en/data.yml
+++ b/modules/10-basics/10-hello-world/en/data.yml
@@ -1,3 +1,4 @@
+---
name: Hello, World!
tips:
- |
diff --git a/modules/10-basics/10-hello-world/ru/data.yml b/modules/10-basics/10-hello-world/ru/data.yml
index 19deccc..d9c85a5 100644
--- a/modules/10-basics/10-hello-world/ru/data.yml
+++ b/modules/10-basics/10-hello-world/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Привет, Мир!
tips:
- >
diff --git a/modules/10-basics/15-prefix-notation/description.en.yml b/modules/10-basics/15-prefix-notation/description.en.yml
deleted file mode 100644
index 56bfbbd..0000000
--- a/modules/10-basics/15-prefix-notation/description.en.yml
+++ /dev/null
@@ -1,28 +0,0 @@
----
-
-name: Prefix notation
-theory: |
- As you may have noticed, in popular programming languages, a function call looks something like `f(arg1, arg2, arg3)`, but Clojure, like other Lisp-like languages, uses prefix notation (Polish notation). This notation style may seem awkward at first sight, but with a nested call, the convenience of this approach becomes apparent (it will take some time to get used to it). For example, a nested function call for an addition, subtraction and multiplication (yes, mathematical operations are functions too!) looks like this:
-
- ```clojure
- (+
- 1
- (- 2 3)
- (* 4 5 6)
- (+ 7 8 9 10 11 12))
- ```
-
- If you format the Clojure code carefully, it is easy to read, especially if the function call goes for more than two arguments. It is also worth noting that enumerating elements does not require commas, but they can be written, Clojure accepts both variants.
-
- ```clojure
- (+ 1 2 3) ; => 6
- (+ 1, 2, 3) ; => 6
- ```
-
-instructions: |
- Print the following expression to the standard output stream (using the `println` function): `1 + 10 - 2 * 7`.
-tips:
- - |
- [Polish notation](https://en.wikipedia.org/wiki/Polish_notation)
- - |
- [Default Clojure code linter and formatter](https://github.com/weavejester/cljfmt)
diff --git a/modules/10-basics/15-prefix-notation/description.ru.yml b/modules/10-basics/15-prefix-notation/description.ru.yml
deleted file mode 100644
index c57cb16..0000000
--- a/modules/10-basics/15-prefix-notation/description.ru.yml
+++ /dev/null
@@ -1,29 +0,0 @@
----
-
-name: Префиксная нотация
-theory: |
- В популярных языках программирования, вызов функции выглядит так `f(arg1, arg2, arg3)`, однако в Clojure, как и в других лиспо-подобных языках, используется префиксная нотация. С непривычки, такой способ записи кажется неуклюжим, однако при вложенном вызове становится очевидным удобство такого подхода. Вложенный вызов нескольких функций выглядит следующим образом:
-
- ```clojure
- (+
- 1
- (- 2 3)
- (* 4 5 6)
- (+ 7 8 9 10 11 12))
- ```
-
- Если аккуратно форматировать Clojure код, то никаких проблем с чтением этого кода не будет. Еще стоит заметить, что перечисление элементов не требует запятых, однако их можно писать, Clojure принимает оба варианта.
-
- ```clojure
- (+ 1 2 3) ; => 6
- (+ 1, 2, 3) ; => 6
- (+ (+ 1, 2), (+ 1, 2)) ; => 6
- ```
-
-instructions: |
- Выведите в стандартный поток вывода (с помощью функции `println`) следующее выражение: `1 + 10 - 2 * 7`
-tips:
- - |
- [Префиксная нотация](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%BB%D1%8C%D1%81%D0%BA%D0%B0%D1%8F_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C)
- - |
- [Стандартный линтер и форматтер Clojure кода](https://github.com/weavejester/cljfmt)
diff --git a/modules/10-basics/15-prefix-notation/en/data.yml b/modules/10-basics/15-prefix-notation/en/data.yml
index 6692e67..5f73c00 100644
--- a/modules/10-basics/15-prefix-notation/en/data.yml
+++ b/modules/10-basics/15-prefix-notation/en/data.yml
@@ -1,3 +1,4 @@
+---
name: Prefix notation
tips:
- |
diff --git a/modules/10-basics/15-prefix-notation/ru/data.yml b/modules/10-basics/15-prefix-notation/ru/data.yml
index 2c09060..c618125 100644
--- a/modules/10-basics/15-prefix-notation/ru/data.yml
+++ b/modules/10-basics/15-prefix-notation/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Префиксная нотация
tips:
- >
diff --git a/modules/10-basics/20-forms/description.en.yml b/modules/10-basics/20-forms/description.en.yml
deleted file mode 100644
index afae53a..0000000
--- a/modules/10-basics/20-forms/description.en.yml
+++ /dev/null
@@ -1,27 +0,0 @@
----
-
-name: Forms
-theory: |
- Lisp-like languages are said to be languages without syntax. Of course, they have a syntax, but it is as primitive as possible, actually consisting of lists and values. In addition, Lisp languages do not have keywords and their corresponding constructs. In popular languages, there are many control constructions, such as conditions, loops, returns, variable assignments, and more. Lisp-like languages have no such constructs (this does not mean that you can't implement a loop or write a condition in Clojure!)
-
- So how does Clojure know what it's working with now and what to do? It's all about _forms_. Any correct Lisp program is called a _form_. For example:
-
- ```clojure
- ;forms
- (println "some-str")
- (+ 1 2 3)
- (- 3)
- 8
- "hello world!"
-
- ; not forms, program will fail with error
- (1 2)
- Execution error (ClassCastException) at user/eval1 (REPL:1).
- class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')
- ```
-
- There are only two forms: normal and composite. The normal form corresponds to all values (and definitions, which we will get to know later) because they evaluate to themselves, e.g., the number _8_ or the string "hello". A compound form is a list that needs to be processed (evaluated) in one way or another.
-
-instructions: |
- Print to the standard output stream (using the `println` function) the result of the following expression: `- 23 * (- 3) + 15`
-tips: []
diff --git a/modules/10-basics/20-forms/description.ru.yml b/modules/10-basics/20-forms/description.ru.yml
deleted file mode 100644
index e8fc50b..0000000
--- a/modules/10-basics/20-forms/description.ru.yml
+++ /dev/null
@@ -1,27 +0,0 @@
----
-
-name: Формы
-theory: |
- Lisp-подобные языки называют языками без синтаксиса. Синтаксис у них конечно есть, но примитивный. Состоит синтаксис из списков и значений. В популярных языках используется множество управляющих конструкций, таких как условия, циклы, возврат, присвоение переменных и так далее. В Lisp-подобные языках таких конструкций нет, но это не значит, что на Clojure нельзя реализовать цикл или написать условие.
-
- Каким же образом Clojure понимает, с чем работает и что нужно сделать? Все дело в _формах_. Любая корректная программа на Lisp называется _формой_. Например:
-
- ```clojure
- ; формы
- (println "some-str")
- (+ 1 2 3)
- (- 3)
- 8
- "hello world!"
-
- ; а это не форма, такой код завершится ошибкой
- (1 2)
- Execution error (ClassCastException) at user/eval1 (REPL:1).
- class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')
- ```
-
- Формы делятся на два типа — нормальная и составная. Нормальной соответствуют значения, которые вычисляются сами в себя, например, число _8_ или строка "hello". Составная форма — список, который нужно вычислить тем или иным способом.
-
-instructions: |
- Выведите в стандартный поток вывода (с помощью функции `println`) результат вычисления следующего выражения: `- 23 * (- 3) + 15`
-tips: []
diff --git a/modules/10-basics/20-forms/en/data.yml b/modules/10-basics/20-forms/en/data.yml
index 50e45bc..abae2d2 100644
--- a/modules/10-basics/20-forms/en/data.yml
+++ b/modules/10-basics/20-forms/en/data.yml
@@ -1,2 +1,3 @@
+---
name: Forms
tips: []
diff --git a/modules/10-basics/20-forms/ru/data.yml b/modules/10-basics/20-forms/ru/data.yml
index c8e18da..fae7929 100644
--- a/modules/10-basics/20-forms/ru/data.yml
+++ b/modules/10-basics/20-forms/ru/data.yml
@@ -1,2 +1,3 @@
+---
name: Формы
tips: []
diff --git a/modules/10-basics/25-evaluation-order/description.en.yml b/modules/10-basics/25-evaluation-order/description.en.yml
deleted file mode 100644
index d068b61..0000000
--- a/modules/10-basics/25-evaluation-order/description.en.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-
-name: Evaluation order
-theory: |
- Let's try to turn the expression `5 - 3 + 1` into a Clojure program. The order in which the elements of this compound expression are calculated is strictly determined in terms of arithmetic. First, `5 - 3` is calculated, then one is added to the result.
-
- Let's start with what is calculated first: `5 - 3` turns into `(- 5 3)`. Then we add the result to one: `(+ (- 5 3) 1)`. Since addition is a commutative operation (remember "the sum does not change by changing the places of the summands"?), the same can be written in another order: `(+ 1 (- 5 3))`. What does not change is that there is an operation at the beginning of each list.
-
- Notice that the expressions evaluated first are at the bottom of the tree. This behavior is common to most conventional languages. Function arguments are evaluated first, then the function itself is called.
-
- Let's try another version: `5 - (3 + 1)`. In this expression, the parentheses set a different precedence. It means that the sum of one and three will be calculated first. So we write `(+ 3 1)` (or so `(+ 1 3)`). Now let's take the five and subtract the result from it: `(- 5 (+ 1 3))`.
-
- At such moments, another distinctive feature of Lisp languages comes to light. The tree structure of the program itself determines the sequence of subtree computation. There is no need to use additional brackets.
-
- Another example: `5 + 7 + (8 - 3) - (8 * 5)`. We act according to the usual scheme:
- * `(* 5 8)`
- * `(- 8 3)`
- * `(+ 5 7 (- 8 3))`
- * `(- (+ 5 7 (- 8 3)) (* 5 8))`
-
- In some situations, the order in which the list items are calculated does not match the order in which they are placed. This happens when using special forms and macros. We will talk about this later.
-
-instructions: |
- Print the following expression into the standard output stream (using the `println` function): `100 - 34 - 22 - (5 + 3 - 10)`.
-
-tips: []
diff --git a/modules/10-basics/25-evaluation-order/description.ru.yml b/modules/10-basics/25-evaluation-order/description.ru.yml
deleted file mode 100644
index 8001ed2..0000000
--- a/modules/10-basics/25-evaluation-order/description.ru.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-
-name: Порядок вычисления
-theory: |
- Превратим выражение `5 - 3 + 1` в программу на Clojure. С точки зрения арифметики, порядок вычисления элементов этого составного выражения строго определен. Сначала вычисляется `5 - 3`, затем к результату прибавляется единица.
-
- Так как первым вычисляется `5 - 3` то получаем форму `(- 5 3)`. Затем сложим результат с единицей: `(+ (- 5 3) 1)`. Так как операция сложения не зависит от расположения слагаемых то форму можно записать и в другом порядке: `(+ 1 (- 5 3))`. Неизменным остается то, что в начале каждого списка находится операция.
-
- Заметьте, выражения, вычисляемые первыми, расположены в глубине дерева. Такое поведение свойственно большинству языков. Сначала вычисляются аргументы функций, затем вызывается сама функция.
-
- Попробуем другой вариант: `5 - (3 + 1)`. В этом выражении скобки устанавливают другой приоритет. Это значит, что сначала вычислится сумма единицы и тройки. Так и запишем `(+ 3 1)` (или так `(+ 1 3)`). Теперь возьмём пятерку и вычтем из неё результат: `(- 5 (+ 1 3))`.
-
- В такие моменты проявляется ещё одно преимущество Lisp-языков. Древовидная структура программы сама определяет последовательность вычисления поддеревьев. Отпадает необходимость в дополнительных скобках.
-
- Ещё один пример: `5 + 7 + (8 - 3) - (8 * 5)`.
- * `(* 5 8)`
- * `(- 8 3)`
- * `(+ 5 7 (- 8 3))`
- * `(- (+ 5 7 (- 8 3)) (* 5 8))`
-
- В некоторых ситуациях порядок вычисления элементов списка не соответствует порядку их следования. Такое происходит при использовании специальных форм и макросов. Об этом поговорим позже.
-
-instructions: |
- Выведите в стандартный поток вывода (с помощью функции `println`) следующее выражение: `100 - 34 - 22 - (5 + 3 - 10)`
-
-tips: []
diff --git a/modules/10-basics/25-evaluation-order/en/data.yml b/modules/10-basics/25-evaluation-order/en/data.yml
index 6d3159f..13feff8 100644
--- a/modules/10-basics/25-evaluation-order/en/data.yml
+++ b/modules/10-basics/25-evaluation-order/en/data.yml
@@ -1,2 +1,3 @@
+---
name: Evaluation order
tips: []
diff --git a/modules/10-basics/25-evaluation-order/ru/data.yml b/modules/10-basics/25-evaluation-order/ru/data.yml
index 896969c..78e682a 100644
--- a/modules/10-basics/25-evaluation-order/ru/data.yml
+++ b/modules/10-basics/25-evaluation-order/ru/data.yml
@@ -1,2 +1,3 @@
+---
name: Порядок вычисления
tips: []
diff --git a/modules/10-basics/30-jvm-errors/description.en.yml b/modules/10-basics/30-jvm-errors/description.en.yml
deleted file mode 100644
index c378cb2..0000000
--- a/modules/10-basics/30-jvm-errors/description.en.yml
+++ /dev/null
@@ -1,82 +0,0 @@
----
-
-name: JVM and errors
-theory: |
- A bit about the JVM platform that Clojure uses. Very often, the guts of the JVM early stage look out, which makes most runtime errors hard to read, especially if the error occurred somewhere in the depths. For example:
-
- ```clojure
- ;call (1)
- (1)
- Execution error (ClassCastException) at user/eval1 (REPL:1).
- class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')
- ```
-
- The error does not look very informative...
-
- Consider the most common errors that can occur and understand what they mean:
-
- `X can not be cast to Y`
-
- Suppose we have some type `X`, but the function being called expects its argument to be of type `Y`. Clojure tries to cast type `X` to type `Y`, but crashes with an error.
-
- ```clojure
- ; Let's call the number as a function
- (def x 10)
- (x)
- Execution error (ClassCastException) at user/eval138 (REPL:1).
- class java.lang.Long cannot be cast to class clojure.lang.IFn
- ```
-
- `Don't know how to create Y: from X`
-
- The error is very similar to the example above, Clojure tries to convert `X` to `Y` but cannot.
-
- ```clojure
- ; Let's try to iterate simple number, instead of collection
- (reduce (fn [x y] (into x (+ x 1))) [1 2 3])
- Execution error (IllegalArgumentException) at user/eval1$fn (REPL:1).
- Don't know how to create ISeq from: java.lang.Long
- ```
-
- `Wrong number of args (X) passed to: Y`
-
- Here everything is clear from the name, we pass the wrong number of arguments to the function:
-
- ```clojure
- ; Function first takes only one argument
- (first 1 2 3)
- Execution error (ArityException) at user/eval3 (REPL:1).
- Wrong number of args (3) passed to: clojure.core/first--5402
- ```
-
- `X - failed: even-number-of-forms? at: [:bindings] spec: :clojure.core.specs.alpha/bindings`
-
- Such an error occurs during the compilation of Clojure code, for example we have a form `let` that uses a vector to connect identifier and data, inside which there are two elements, the identifier itself and the data. Clojure internally uses `clojure.spec` to check such declarations, so the error message may be slightly different each time:
-
- ```clojure
- ; valid code
- (let [a 2]
- (+ a 4)) ; => 6
-
- ; incorrect code
- (let [a b 3]
- (+ a b))
- Syntax error macroexpanding clojure.core/let at (REPL:1:1).
- [a b 3] - failed: even-number-of-forms? at: [:bindings] spec: :clojure.core.specs.alpha/bindings
- ```
-
- Although the error output may scare and discourage newcomers at first, in general it is possible to get used to this error output. However, thanks to the JVM, Clojure has all the features of the Java world and more!
-
- ```clojure
- (Integer/parseInt "123") ; 123
- ```
-
- In the code above, we called a static Java method that parses a string into a number. The same applies to libraries written for Java. There are also projects that use Java, Scala and Clojure at the same time.
-
-instructions: |
- Print to the standard output stream (using the `println` function) the result of parsing the string `256` (using the `Integer/parseInt` method).
-tips:
- - |
- [JVM](https://ru.wikipedia.org/wiki/Java_Virtual_Machine)
- - |
- [Java Interop](https://clojure.org/reference/java_interop)
diff --git a/modules/10-basics/30-jvm-errors/description.ru.yml b/modules/10-basics/30-jvm-errors/description.ru.yml
deleted file mode 100644
index 86e8895..0000000
--- a/modules/10-basics/30-jvm-errors/description.ru.yml
+++ /dev/null
@@ -1,74 +0,0 @@
----
-
-name: JVM и ошибки
-theory: |
- Вспомним платформу JVM, которую использует Clojure. Часто кишки JVM рантайма выглядывают наружу, из-за чего большинство ошибок во время работы программ оказываются трудночитаемыми. Например:
-
- ```clojure
- ; вызываем (1)
- (1)
- Execution error (ClassCastException) at user/eval1 (REPL:1).
- class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')
- ```
-
- Рассмотрим распространенные ошибки и разберемся, что эти ошибки означают:
-
- `X can not be cast to Y`
-
- Допустим есть тип `X`, но вызванная функция ожидает, что аргумент будет типом `Y`. Clojure пытается привести тип `X` к типу `Y`, но падает с ошибкой.
-
- ```clojure
- ; вызовем число как функцию
- (def x 10)
- (x)
- Execution error (ClassCastException) at user/eval138 (REPL:1).
- class java.lang.Long cannot be cast to class clojure.lang.IFn
- ```
-
- `Don't know how to create Y: from X`
-
- Ошибка очень схожа с примером выше, она возникает при конвертации `X` типа в `Y`.
-
- ```clojure
- ; попробуем проитерироваться по числу, вместо коллекции
- (reduce (fn [x y] (into x (+ x 1))) [1 2 3])
- Execution error (IllegalArgumentException) at user/eval1$fn (REPL:1).
- Don't know how to create ISeq from: java.lang.Long
- ```
-
- `Wrong number of args (X) passed to: Y`
-
- Здесь понятно из названия, что в функцию передано неправильное количество аргументов:
-
- ```clojure
- ; функция first принимает только один аргумент
- (first 1 2 3)
- Execution error (ArityException) at user/eval3 (REPL:1).
- Wrong number of args (3) passed to: clojure.core/first--5402
- ```
-
- `X - failed: even-number-of-forms? at: [:bindings] spec: :clojure.core.specs.alpha/bindings`
-
- Такая ошибка возникает во время компиляции Clojure, кода вектор, который используется формой `let` для связывания идентификатора с данными, содержит нечетное количество элементов вместо ожидаемых пар идентификаторов и данных. Clojure внутри себя использует `clojure.spec` чтобы провалидровать такие объявления, поэтому сообщение об ошибке может отличаться:
-
- ```clojure
- ; валидный пример
- (let [a 2]
- (+ a 4)) ; => 6
-
- ; невалидный пример
- (let [a b 3]
- (+ a b))
- Syntax error macroexpanding clojure.core/let at (REPL:1:1).
- [a b 3] - failed: even-number-of-forms? at: [:bindings] spec: :clojure.core.specs.alpha/bindings
- ```
-
- Хоть и вывод ошибок может поначалу оттолкнуть, но спустя время к ним привыкаешь.
-
-instructions: |
- Выведите в стандартный поток вывода (с помощью функции `println`) результат парсинга строки `256` (с помощью метода `Integer/parseInt`).
-tips:
- - |
- [JVM](https://ru.wikipedia.org/wiki/Java_Virtual_Machine)
- - |
- [Java Interop](https://clojure.org/reference/java_interop)
diff --git a/modules/10-basics/30-jvm-errors/en/data.yml b/modules/10-basics/30-jvm-errors/en/data.yml
index 82f2f3f..d378308 100644
--- a/modules/10-basics/30-jvm-errors/en/data.yml
+++ b/modules/10-basics/30-jvm-errors/en/data.yml
@@ -1,3 +1,4 @@
+---
name: JVM and errors
tips:
- |
diff --git a/modules/10-basics/30-jvm-errors/ru/data.yml b/modules/10-basics/30-jvm-errors/ru/data.yml
index 054ceb5..1b44401 100644
--- a/modules/10-basics/30-jvm-errors/ru/data.yml
+++ b/modules/10-basics/30-jvm-errors/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: JVM и ошибки
tips:
- |
diff --git a/modules/15-definitions/10-definitions-define/description.ru.yml b/modules/15-definitions/10-definitions-define/description.ru.yml
deleted file mode 100644
index 8bd2f9f..0000000
--- a/modules/15-definitions/10-definitions-define/description.ru.yml
+++ /dev/null
@@ -1,78 +0,0 @@
----
-
-name: Объявление символов
-theory: |
- Clojure — не является полностью функциональным языком программирования (Haskell, например, является), однако, это в большей степени продиктовано прагматичностью, которая заложена в дизайне языка. Благодаря этому, в Clojure можно оперировать как константами, так и переменными. Для начала, рассмотрим константы (создаются с помощью формы `def`):
-
- ```clojure
- (def id expr)
- ; id — идентификатор
- ; expr — выражение
-
- ; форма def 'связывает' идентификатор с выражением
- (def lang "clojure")
- (println lang) ; => clojure
- ```
-
- Значением объявления может быть как нормальная форма (значение), так и составная:
-
- ```clojure
- (def result (+ 7 (- 4 6)))
- (println result) ; => 5
- ```
-
- `def` связывает имя (идентификатор) и значение следующего за ним выражения в рамках `пространства имен`, в котором происходит объявление. Имена объявлений, состоящие из нескольких слов, соединяют с помощью дефиса. В Lisp-языках повсеместно принят так называемый "kebab-case".
-
- ```clojure
- (def dangerous-year 1984)
- (println dangerous-year) ; => 1984
- ```
-
- Если же нужно работать с каким-либо изменяемым состоянием, тогда на помощь приходит форма `atom` с набором функций, благодаря которым `атом` можно менять:
-
- ```clojure
- (def lang (atom "clojure"))
- (println @lang) ; => clojure
-
- ; мутируем lang
- (reset! lang "Clojure!!!")
- (println @lang) ; => "Clojure!!!
-
- ; объявим счетчик
- (def counter (atom 0))
-
- ; поменяем его с помощью форм swap! и inc
- (swap! counter inc) ; => 1
- (swap! counter inc) ; => 2
- ; а теперь уменьшим счетчик на 1
- (swap! counter dec) ; => 1
- ```
-
- В общем случае использовать атомы как переменные не рекомендуется. Clojure отлично поддерживает функциональную парадигму и всячески её поощряет. Код с переменными практически всегда легко заменяется на код с константами. Как было описано выше, тема атомов и изменяемого состояния будет рассмотрена позже в соответствующем модуле.
-
- Заметим, что в данном случае мы тоже использовали форму `def`, почему так? Идея связывания (от англ. binding) является идиоматическим способом записать функцию, в которую передается аргументом то, что записано в `связывании`. Звучит немного запутанно, поэтому еще раз рассмотрим пример выше поподробнее:
-
- ```clojure
- (def ; используем форму def
- lang ; lang является идентификатором, с которым происходит связывание
- (atom ; атом здесь является выражением, которое возвращает ссылку
- "clojure" ; данные, на которые ссылается атом
- )
- )
- ```
-
- Благодаря тому, что связывание является более высокоуровневой концепцией, чем ссылка, мы можем связывать идентификатор не только с данными или ссылками, но и с функциями (функции будут рассмотрены дальше, не страшно, если что-то в примере ниже не будет до конца понятно).
-
- ```clojure
- (def my-fn (fn [a] (inc a))) ; => свяжем идентификатор my-fn с анонимной функцией, в которой переданный аргумент увеличивается на 1
- (my-fn 1) ; => 2
- (my-fn 8) ; => 9
- ```
-
-instructions: |
- Создайте объявление, обозначающее "количество участников" (имя соорудите сами), присвойте ему значение 10 и распечатайте на экран.
-tips:
- - |
- [Про функциональную парадигму в Clojure](https://clojure.org/about/functional_programming)
- - |
- [Про форму def](https://clojure.org/guides/learn/syntax#_def)
diff --git a/modules/15-definitions/10-definitions-define/ru/data.yml b/modules/15-definitions/10-definitions-define/ru/data.yml
index c8fee69..da777aa 100644
--- a/modules/15-definitions/10-definitions-define/ru/data.yml
+++ b/modules/15-definitions/10-definitions-define/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Объявление символов
tips:
- >
diff --git a/modules/15-definitions/15-definitions-defonce/description.ru.yml b/modules/15-definitions/15-definitions-defonce/description.ru.yml
deleted file mode 100644
index 80d3230..0000000
--- a/modules/15-definitions/15-definitions-defonce/description.ru.yml
+++ /dev/null
@@ -1,29 +0,0 @@
----
-
-name: Еще об объявлении символов
-theory: |
- Помимо формы `def` есть еще одна форма — `defonce`. Основное ее преимущество в том, что если идентификатор уже определен, то он не переопределится и будет использоваться его прошлое значение.
-
- ```clojure
- (def some-value "hello")
-
- (defonce some-value "world")
-
- (println some-value) ; => "hello"
- ```
-
- Зачем это может быть нужно? Например, при прямом импорте каких-либо библиотек или модулей, в которых может быть заранее объявлен идентификатор. Однако если мы переопределим идентификатор с помощью `def`, тогда идентификатор будет иметь другое значение.
-
- ```clojure
- (def some-value "hello")
-
- (def some-value "world")
-
- (println some-value) ; => "world"
- ```
-
-instructions: |
- Создайте объявление, обозначающее "количество пар" (имя соорудите сами), присвойте ему значение 5, затем с помощью формы `defonce` переопределите количество пар на любое другое значение и распечатайте на экран.
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/defonce)
diff --git a/modules/15-definitions/15-definitions-defonce/ru/data.yml b/modules/15-definitions/15-definitions-defonce/ru/data.yml
index 0113ef2..02a50cd 100644
--- a/modules/15-definitions/15-definitions-defonce/ru/data.yml
+++ b/modules/15-definitions/15-definitions-defonce/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Еще об объявлении символов
tips:
- |
diff --git a/modules/15-definitions/20-definitions-define-functions/description.ru.yml b/modules/15-definitions/20-definitions-define-functions/description.ru.yml
deleted file mode 100644
index 50c2fd2..0000000
--- a/modules/15-definitions/20-definitions-define-functions/description.ru.yml
+++ /dev/null
@@ -1,74 +0,0 @@
----
-
-name: Объявление и вызов функций
-theory: |
- Функции в Clojure обладают следующими свойствами:
-
- * У функций нет имен. Во многих языках такие функции также существуют и называются анонимными функциями.
- * Функции являются объектами первого рода (или класса). Это значит, что их можно присваивать переменным, передавать в другие функции и возвращать из функций.
-
- ```clojure
- ; определение функции, вычисляющей сумму двух чисел
- (fn [x y] (+ x y))
- ```
-
- В примере выше определяется функция с двумя аргументами. Определение функции начинается со слова `fn`. Вторым элементом в форме определения функции идёт список аргументов. Третий и последующие элементы — тело функции. То есть тело может состоять из нескольких форм (как минимум из одной):
-
- ```clojure
- (fn []
- (println "one")
- (println "two"))
- ```
-
- Обратите внимание на отсутствие инструкции `return`. В отличие от большинства других языков, в Lisp-языках "инструкций" практически нет. Всё есть выражение. А выражения всегда возвращают результат. Если хорошо подумать, то такое поведение следует из самой структуры Lisp-программы. Фактически мы имеем дерево, которое должно вычисляться в какое-то значение, значит на каждом уровне должен создаваться возврат, поднимающийся выше по дереву, и так до самого корня. Возвращается всегда *последнее вычисленное выражение*.
- Пара примеров для закрепления:
-
- ```clojure
- ; печать на экран
- (fn [] (println "hello!"))
- ; куб числа
- (fn [n] (* n n n))
- ; среднее между двумя числами
- (fn [num1 num2] (/ (+ num1 num2) 2))
- ```
-
- Определение функции само по себе мало полезно, особенно если мы захотим использовать её несколько раз. Для повторного использования нужно создать объявление, в которое запишется функция. Такое возможно благодаря тому, что форма определения функции — это выражение, возвращающее саму функцию. Иными словами, мы просто записываем в наш идентификатор ссылку на функцию.
-
- ```clojure
- (def cube (fn [n] (* n n n)))
- ```
-
- Теперь попробуем вызвать:
-
- ```clojure
- (cube 2) ; 8
- (cube 3) ; 27
- ```
-
- Помимо связи анонимной функции с объявлением существует упрощенный вариант объявления функции и ее связывания с идентификатором. Делается это с помощью формы `defn`, которая упрощает процесс объявления функции (в таком способе объявления возможно даже написать документацию на объявляемую функцию!).
-
- ```clojure
- (defn cube [n] (* n n n))
- (cube 2) ; => 8
- (cube 3) ; => 27
-
- (defn increment
- "Function that increments passed number by one."
- [n] (+ n 1))
- (increment 2) ; => 3
- ```
-
-instructions: |
- Создайте функцию с именем `square`, которая вычисляет квадрат переданного числа
-
- ```clojure
- (square 3) ; 9
- ```
-
-definitions:
- - name: Объект первого рода
- description: Сущность в языке, которая рассматривается как данные. Это значит, что её можно записывать в переменную, передавать в функции и возвращать из функций.
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/defn)
diff --git a/modules/15-definitions/20-definitions-define-functions/ru/data.yml b/modules/15-definitions/20-definitions-define-functions/ru/data.yml
index 7eafbca..305a2c9 100644
--- a/modules/15-definitions/20-definitions-define-functions/ru/data.yml
+++ b/modules/15-definitions/20-definitions-define-functions/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Объявление и вызов функций
definitions:
- name: Объект первого рода
diff --git a/modules/15-definitions/25-definitions-lambda-call/description.ru.yml b/modules/15-definitions/25-definitions-lambda-call/description.ru.yml
deleted file mode 100644
index f4685fb..0000000
--- a/modules/15-definitions/25-definitions-lambda-call/description.ru.yml
+++ /dev/null
@@ -1,43 +0,0 @@
----
-
-name: Вызов функции без def
-theory: |
- Вспомним функцию `cube` из прошлого урока:
-
- ```clojure
- (def cube (fn [n] (* n n n)))
- (cube 2) ; 8
- ```
-
- Попробуйте ответить на вопрос, можно ли вызывать функцию сразу после объявления без использования `def`? Конечно можно, как и во всех других языках с поддержкой анонимных функций. Для этого достаточно, в форме вызова, подставить вместо имени саму функцию:
-
- ```clojure
- ((fn [n] (* n n n)) 2) ; 8
- ```
-
- Необычность этой структуры в том, что здесь первый элемент не идентификатор, а выражение, возвращающее функцию (а определение анонимной функции возвращает саму функцию). Из-за этого возникает двойная скобка. Проще всего это понять, если всегда воспринимать скобки как вызов. В дальнейшем при работе со списками, как с данными, мы увидим, что это не всегда так, но на данном этапе данное упрощение полезно. В форме вызова сразу после функции перечисляются параметры. В данном примере параметр только один, это 2. Так будет выглядеть пример вызова функции с двумя аргументами:
-
- ```clojure
- ((fn [x y] (+ x y))
- 2 3) ; 5
- ```
-
- Вызов функции — обычное выражение, это значит что его можно использовать во всех местах, где возможно появление выражения. В Lisp-языках это почти любые части программы:
-
- ```clojure
- (println ((fn [x y] (+ x y))
- 8 7)) ; => 15
- ```
-
-instructions: |
- 1. Определите (без создания переменной) и вызовите функцию, которая находит среднее арифметическое между двумя числами. В качестве чисел подставьте 2 и 4.
- 2. Запишите результат в переменную.
- 3. Выведите переменную на экран.
-definitions: []
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/fn)
- - |
- [Про лямбда-выражение](https://ru.wikipedia.org/wiki/%D0%9B%D1%8F%D0%BC%D0%B1%D0%B4%D0%B0-%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5)
- - |
- [Про лямбда-исчисление](https://ru.wikipedia.org/wiki/%D0%9B%D1%8F%D0%BC%D0%B1%D0%B4%D0%B0-%D0%B8%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5)
diff --git a/modules/15-definitions/25-definitions-lambda-call/ru/data.yml b/modules/15-definitions/25-definitions-lambda-call/ru/data.yml
index 27e9897..e3743bc 100644
--- a/modules/15-definitions/25-definitions-lambda-call/ru/data.yml
+++ b/modules/15-definitions/25-definitions-lambda-call/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Вызов функции без def
definitions: []
tips:
diff --git a/modules/15-definitions/30-function-shorthand/description.ru.yml b/modules/15-definitions/30-function-shorthand/description.ru.yml
deleted file mode 100644
index b1b3b40..0000000
--- a/modules/15-definitions/30-function-shorthand/description.ru.yml
+++ /dev/null
@@ -1,42 +0,0 @@
----
-
-name: Сокращенный синтаксис создания функции
-theory: |
- Создание функций - настолько частая операция, что в Clojure была добавлена сокращенная версия определения (с одновременным объявлением) функции с помощью `defn`. Возьмем для примера определение функции возведения в квадрат:
-
- ```clojure
- (def square (fn [n] (* n n)))
- ```
-
- А теперь посмотрим сокращенную версию этого же определения:
-
- ```clojure
- (defn square [n] (* n n))
- (square 3) ; 9
- ```
-
- Первое что бросается в глаза — отсутствие слова `fn`. Вместо него, после `defn` указывается имя функции, после имени - список параметров. Затем идет тело функции. Объявленная выше функция возведения в квадрат принимает один аргумент — `n`. Пример объявления функции с двумя аргументами:
-
- ```clojure
- (defn sum [x y] (+ x y))
- (sum 3 5) ; 8
- ```
-
- Помимо определения сигнатуры функции, мы можем тут же использовать деструктуризацию (destructuring). Пример функции суммирования, в которую передается массив из двух элементов:
-
- ```clojure
- (defn sum [[x y]] (+ x y))
- (sum [3 5]) ; 8
- (sum [10 10]) ; 20
- ```
-
-instructions: |
- Создайте функцию с именем `sum-of-squares` (используя короткий синтаксис), которая находит сумму квадратов двух чисел.
-
- ```clojure
- (sum-of-squares 2 3) ; 13
- ```
-definitions: []
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/defn)
diff --git a/modules/15-definitions/30-function-shorthand/ru/data.yml b/modules/15-definitions/30-function-shorthand/ru/data.yml
index a9106aa..2e5dd76 100644
--- a/modules/15-definitions/30-function-shorthand/ru/data.yml
+++ b/modules/15-definitions/30-function-shorthand/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Сокращенный синтаксис создания функции
definitions: []
tips:
diff --git a/modules/15-definitions/35-namespaces/description.ru.yml b/modules/15-definitions/35-namespaces/description.ru.yml
deleted file mode 100644
index c52ee62..0000000
--- a/modules/15-definitions/35-namespaces/description.ru.yml
+++ /dev/null
@@ -1,47 +0,0 @@
----
-
-name: Пространства имен
-theory: |
- Система пространства имен в Clojure похожа на подобные системы в других языках. Каждый файл представляет собой пространство имен, которое совпадает с файловой системой:
-
- ```clojure
- ; math.clj
- (ns math)
- (defn sum [a b] (+ a b))
- ```
-
- По умолчанию все объявления, сделанные в пространстве, экспортируются из модуля. Импорт объявлений из других модулей (читай "файлов") происходит с помощью формы `require`:
-
- ```clojure
- (require '[math :refer [sum]])
- ```
-
- Если есть необходимость не выставлять наружу какую-либо функцию, то используется форма `defn-`, которая превращает функцию в приватный метод (привет JVM):
-
- ```clojure
- ; math.clj
- (defn- sum [a b] (+ a b))
- ```
-
- Если мы попытаемся получить доступ из этого пространства имен, то возникнет ошибка импорта.
-
- При импорте можно так же указать дополнительные параметры `(require '[math :refer :all])`. Таким образом мы экспортируем *все объявления в пространстве имен*. Если же мы хотим импортировать только конкретные формы, то используется следующий синтаксис `(require '[math :refer [sum]])`. Еще мы можем переименовывать пространства имен, для простоты адресации `(require '[math :as m])`. И еще одна удобная возможность, при определении пространства имен, мы можем импортировать другие. Важно, что `require` работает только с пространствами имен. В примере выше предполагается что оба файла с определениями лежат в одной директории.
-
- Еще немного вариантов использования:
-
- ```clojure
- ; определяем наше пространство имен, импортируем пространство math
- (ns my-ns
- (:require [math :as m]) ; переименовываем для упрощения
-
- (defn my-cube [x] (m/cube x))
- ; ссылаемся на форму cube в пространстве имен math
- ```
-
-instructions: |
- Определите переменную `phone` со значением `"iphone"`
-
-definitions: []
-tips:
- - |
- [Официальная документация](https://clojure.org/reference/namespaces)
diff --git a/modules/15-definitions/35-namespaces/ru/data.yml b/modules/15-definitions/35-namespaces/ru/data.yml
index 5f3e762..3c9dc64 100644
--- a/modules/15-definitions/35-namespaces/ru/data.yml
+++ b/modules/15-definitions/35-namespaces/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Пространства имен
definitions: []
tips:
diff --git a/modules/15-definitions/40-let/description.ru.yml b/modules/15-definitions/40-let/description.ru.yml
deleted file mode 100644
index b5d739f..0000000
--- a/modules/15-definitions/40-let/description.ru.yml
+++ /dev/null
@@ -1,91 +0,0 @@
----
-
-name: Локальные объявления
-theory: |
- `def` в Clojure может использоваться как на уровне модуля, так и внутри функций:
-
- ```clojure
- (defn f []
- (def text "lorem")
- (println text))
- (f) ; => "lorem"
- ; у `def text` локальная область видимости
- (println text) ;
- ```
-
- Но с ним связано несколько тонких моментов:
-
- * Хотя лиспы похожи между собой, конкретно `def` ведет себя совершенно по-разному в разных диалектах Lisp. В некоторых объявления останутся локальными для текущей области видимости, в других же объявление всегда будет глобальным.
- * Объявления переменных должны идти в самом начале функции, до любых других выражений.
-
- Существует и другой способ объявить локальные переменные, гораздо более популярный и предсказуемый:
-
- ```clojure
- (let [text "lorem"] (println text)) ; => lorem
- ```
-
- Каждое объявление в `let` — это список из имени и выражения, вычисленное значение которого будет ассоциировано с именем. Перепишем уже знакомую нам функцию `sum`, используя локальные объявления:
-
- ```clojure
- ; форма записи без локальных объявлений:
- (def sum (fn [x y] (+ x y)))
- (sum 8 7) ; вызов глобальной функции
- ; форма записи с локальными объявлениями:
- (let [sum (fn [x y] (+ x y))]
- (sum 8 7)) ; вызов локальной функции
- ```
-
- Все объявления внутри `let` доступны только в выражениях, которые вызываются внутри самого `let` *после списка объявлений*. Вот ещё несколько более сложных примеров, с несколькими объявлениями:
-
- ```clojure
- (let [x 2
- y (+ 4 3)]
- (+ x y)) ; 9
- ```
-
- ```clojure
- (defn sum-of-squares [x y]
- (let [x-square (* x x)
- y-square (* y y)]
- (+ x-square y-square)))
- ```
-
- Можно пойти еще дальше — вызывать локальную функцию внутри глобальной:
-
- ```clojure
- (defn sum-of-squares [x y]
- (let [square (fn [n] (* n n))]
- (+ (square x) (square y))))
- (sum-of-squares 8 7) ; => 113
- ```
-
- Объявления, созданные в рамках одного использования `let`, видны друг другу. Поэтому мы можем сделать так:
-
- ```clojure
- (let [x 2
- y (* x 10)]
- (+ x y))
- ```
-
- Есть еще форма `letfn`, которая делает то же самое, только для функций, в более коротком виде:
-
- ```clojure
- (letfn [(increment [x] (+ x 1))
- (decrement [x] (- x 1))
- (put-value [] 10)]
- (increment (decrement (put-value)))); 10
- ```
-
-instructions: |
- Реализуйте функцию `prod-sum`, которая сначала умножает переданное число на себя, а затем суммируется между собой и полученным результатом умножения. Воспользуйтесь локальными объявлениями для хранения промежуточных результатов вычисления.
-
- ```clojure
- (prod-sum 2) ; 6
- (prod-sum 3) ; 12
- (prod-sum 4) ; 20
- ```
-
-definitions: []
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/let)
diff --git a/modules/15-definitions/40-let/ru/data.yml b/modules/15-definitions/40-let/ru/data.yml
index 6b60273..009f618 100644
--- a/modules/15-definitions/40-let/ru/data.yml
+++ b/modules/15-definitions/40-let/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Локальные объявления
definitions: []
tips:
diff --git a/modules/20-logic/10-bool-type/description.ru.yml b/modules/20-logic/10-bool-type/description.ru.yml
deleted file mode 100644
index 3fba4b4..0000000
--- a/modules/20-logic/10-bool-type/description.ru.yml
+++ /dev/null
@@ -1,53 +0,0 @@
----
-
-name: Логические операторы
-theory: |
- True и False в Clojure представлены значениями `true` и `false` соответственно. Большинство операций в Clojure рассматривают как ложь только `false` и `nil` (отсутствие значения). Все остальное считается истиной. Пара примеров проверки на равенство:
-
- ```clojure
- (= 42 42) ; true
- (= 42 24) ; false
- ```
-
- Равенство значений проверяется через функцию `=`. Напишем функцию `gt?`, которая возвращает `true`, если первое число больше второго и `false` в другом случае. В Clojure имена предикатов заканчиваются вопросительным знаком. При этом к ним не добавляется префикс "is".
-
- ```clojure
- (defn gt? [x y] (> x y))
- (gt? 3 2) ; true
- (gt? 10 15) ; false
- ```
-
- *Вот так разработчики на Ruby узнали почему в их языке предикаты выглядят как вопросы :)*
- Теперь напишем предикат, определяющий четность числа. Для этого нам понадобится функция `rem`, которая вычисляет остаток от деления.
-
- ```clojure
- (defn even? [n] (= (rem n 2) 0))
- (even? 3) ; false
- (even? 4) ; true
- ```
-
- > Строго говоря, такая функция уже есть в языке. Здесь мы её реализуем заново только ради примера.
- Логические операторы в Clojure не имеют символьных обозначений, вместо этого используются функции `and`, `or`, `not` и другие.
-
- ```clojure
- (not "moon") ; false
- (and (odd? 3) (even? 4)) ; true
- ```
-
- Как и в случае с арифметическими операциями, мы получаем два бонуса:
- 1. Префиксная нотация позволяет комбинировать любое число условий: `(and <...>)`.
- 2. Благодаря древовидной структуре исходного кода, приоритет всегда точно определен.
-
-instructions: |
- Реализуйте функцию `leap-year?`, которая проверяет, является ли год високосным. Любой год, который делится на 4 без остатка, является високосным годом. Тем не менее, есть еще небольшая особенность, которая должна быть учтена например, григорианский календарь предусматривает, что год, который делится без остатка на 100 является високосным годом только в том случае, если он также без остатка делится на 400.
-
- ```clojure
- (leap-year? 2012) ; true
- (leap-year? 1913) ; false
- (leap-year? 1804) ; true
- (leap-year? 2100) ; false
- ```
-
-tips:
- - |
- [Високосный год](https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D1%81%D0%BE%D0%BA%D0%BE%D1%81%D0%BD%D1%8B%D0%B9_%D0%B3%D0%BE%D0%B4)
diff --git a/modules/20-logic/10-bool-type/ru/data.yml b/modules/20-logic/10-bool-type/ru/data.yml
index c1a1c07..5629a72 100644
--- a/modules/20-logic/10-bool-type/ru/data.yml
+++ b/modules/20-logic/10-bool-type/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Логические операторы
tips:
- >
diff --git a/modules/20-logic/20-if/description.ru.yml b/modules/20-logic/20-if/description.ru.yml
deleted file mode 100644
index ec7e01e..0000000
--- a/modules/20-logic/20-if/description.ru.yml
+++ /dev/null
@@ -1,34 +0,0 @@
----
-
-name: Условная конструкция
-theory: |
- > (if test-expr then-expr else-expr)
- Условная конструкция в Clojure – это специальная форма, в которой 4 элемента. Первый – `if`, затем выражение-предикат (`test-expr`). Если предикат вернул истину, то выполняется `then-expr`, иначе `else-expr`. В Clojure `if` всегда содержит ветку `else`.
-
- ```clojure
- (if (> 3 2) "yes" "no") ; "yes"
- ```
-
- Подчеркну, что форма `if` это выражение, а значит у неё есть результат. Это резко отличается от большинства привычных языков, в которых `if` – это специальная инструкция. Выражения делают код проще, а его возможности шире. Все дальнейшие формы, которые мы рассмотрим, также будут выражениями. Почему `if` называется особой формой? Давайте разберем на примере:
-
- ```clojure
- (if (> 3 2) (println "yes") (println "no")) ; => yes
- ```
-
- Что напечатает на экран эта программа? В нормальных формах сначала вычисляются все элементы формы, а затем уже сама форма. Это так называемый аппликативный порядок вычисления. Он используется в большинстве языков и привычен для большинства программистов. Мы ожидаем, что аргументы функции вычисляются до того как попадут в функцию.
- Но в случае с примером выше, поведение другое. Оно зависит от результата предиката. То есть в форме `if` выполняется ровно то выражение, которое должно выполняться по логике формы `if`: первое, если предикат вернул истину, и второе, если ложь. Именно поэтому форма является особой. Подобную форму невозможно реализовать на самом языке без механизма макросов (а с макросами можно).
- _Существует и другой порядок, так называемый нормальный порядок вычисления. Самый известный язык, использующий его – Haskell. В этом языке ничто не вычисляется до тех пор, пока не понадобится результат вычисления._
-
-instructions: |
- Реализуйте функцию `sentence-type`, которая возвращает строку `"cry"`, если переданый текст написан заглавными буквами, и возвращает строку `"common"` в остальных случаях.
-
- ```clojure
- (sentence-type "HOW ARE YOU?") ; "cry"
- (sentence-type "Hello, world!") ; "common"
- ```
-
- Для перевода строки в верхний регистр используйте функцию `upper-case`.
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/if)
diff --git a/modules/20-logic/20-if/ru/data.yml b/modules/20-logic/20-if/ru/data.yml
index 22dc6cc..7a698a7 100644
--- a/modules/20-logic/20-if/ru/data.yml
+++ b/modules/20-logic/20-if/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Условная конструкция
tips:
- |
diff --git a/modules/20-logic/30-guards/description.ru.yml b/modules/20-logic/30-guards/description.ru.yml
deleted file mode 100644
index b943750..0000000
--- a/modules/20-logic/30-guards/description.ru.yml
+++ /dev/null
@@ -1,39 +0,0 @@
----
-
-name: When и When-not
-theory: |
- Обычного `if` без `else` в Clojure нет, но есть две специальные формы: `when` и `when-not`, предназначенные для этой цели.
- ### When
- > (when test-expr body ...+)
- Если результат `test-expr` истина, то вычисляется тело.
-
- ```clojure
- (when (pos? -5)
- (println "hi"))
- (when (pos? 5)
- (println "hi")
- (println " there"))
- ```
-
- ### When-not
- > Тоже самое что и (when (not test-expr) body ...+).
- `when-not` работает наоборот. Тело вычисляется в том случае, если `test-expr` – ложь. `when-not` хоть и бывает удобен, но резко становится нечитаемым, когда в `test-expr` появляются составные условия.
-
- ```clojure
- (when-not (pos? 5)
- (println "hi"))
- (when-not (pos? -5)
- (println "hi")
- (println " there"))
- ```
-
-instructions: |
- Реализуйте функцию `say-boom`, которая возвращает строку _Boom!_, если её вызвали с параметром `"go"`.
-
- ```clojure
- (say-boom "hey")
- (say-boom "go") ; "Boom!"
- ```
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/when)
diff --git a/modules/20-logic/30-guards/ru/data.yml b/modules/20-logic/30-guards/ru/data.yml
index a8a8bc7..145b67d 100644
--- a/modules/20-logic/30-guards/ru/data.yml
+++ b/modules/20-logic/30-guards/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: When и When-not
tips:
- |
diff --git a/modules/20-logic/35-case/description.ru.yml b/modules/20-logic/35-case/description.ru.yml
deleted file mode 100644
index 8e43ab2..0000000
--- a/modules/20-logic/35-case/description.ru.yml
+++ /dev/null
@@ -1,35 +0,0 @@
----
-
-name: Case
-theory: |
- Вместо `switch` в Clojure используется `case`. В общем случае `case` по своим возможностям шире, чем `switch` в большинстве языков программирования. Его использование в качестве `switch`, это наиболее простой способ познакомиться с ним:
-
- ```clojure
- (let [v 0]
- (case v
- 0 "zero"
- 1 "one"
- 2 "two"))
- ; "zero"
- ```
- Каждая ветка в `case` описывается значением и тем, что вернется из выражения, если же ни один из случаев не подошел, то записывается только возвращаемое выражение. Например:
-
- ```clojure
- (case 6
- 0 "zero"
- 1 "one"
- 2 "two"
- "many")
- ; "many"
- ```
-instructions: |
- Реализуйте функцию `humanize-permission`, которая принимает на вход символьное обозначение прав доступа в Unix системах, и возвращает текстовое описание.
-
- ```clojure
- (humanize-permission "x") ; execute
- (humanize-permission "r") ; read
- (humanize-permission "w") ; write
- ```
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/case)
diff --git a/modules/20-logic/35-case/ru/data.yml b/modules/20-logic/35-case/ru/data.yml
index 08270e5..aa2e3e8 100644
--- a/modules/20-logic/35-case/ru/data.yml
+++ b/modules/20-logic/35-case/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Case
tips:
- |
diff --git a/modules/20-logic/40-cond/description.ru.yml b/modules/20-logic/40-cond/description.ru.yml
deleted file mode 100644
index f190e4a..0000000
--- a/modules/20-logic/40-cond/description.ru.yml
+++ /dev/null
@@ -1,28 +0,0 @@
----
-
-name: Cond
-theory: |
- Для самых сложных случаев, там, где обычно применяется _if-else_if_ в Clojure есть еще одна форма: `cond`:
-
- ```clojure
- (cond
- (pos? -5) "first return"
- (zero? -5) "second return"
- (pos? 5) "third return"
- :else "boom!")
- ```
-
- Эта форма напоминает case, только в левой части находится предикат. Если его результат истина, то выполняется правая часть и её результат возвращается из `cond`.
- Если необходимо, в конце добавляется `:else`, который ведет себя аналогично `else` в других языках.
-
-instructions: |
- Реализуйте функцию `programmer-level`, которая принимает на вход количество баллов, и возвращает уровень разработчика на основе этого числа. Если баллов от 0 до 10, то это junior, от 10 до 20 – middle, от 20 и выше – senior.
-
- ```clojure
- (programmer-level 10) ; middle
- (programmer-level 0) ; junior
- (programmer-level 40) ; senior
- ```
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/cond)
diff --git a/modules/20-logic/40-cond/ru/data.yml b/modules/20-logic/40-cond/ru/data.yml
index f4010f6..f381b96 100644
--- a/modules/20-logic/40-cond/ru/data.yml
+++ b/modules/20-logic/40-cond/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Cond
tips:
- |
diff --git a/modules/20-logic/50-expressions/description.ru.yml b/modules/20-logic/50-expressions/description.ru.yml
deleted file mode 100644
index 94d5555..0000000
--- a/modules/20-logic/50-expressions/description.ru.yml
+++ /dev/null
@@ -1,52 +0,0 @@
----
-
-name: Логические выражения
-theory: |
- Во многих языках для того, чтобы внутри вычисляемых выражений использовать логическое ветвление, приходится использовать отдельные специфические варианты конструкции `if` (как это сделано в Python) или же вовсе — использовать отдельные *тернарные операторы*.
- Здесь Lisp-подобные языки — и Clojure в частности — обладают одним важным преимуществом: в этих языках всё есть выражение. Поэтому отдельные варианты условных конструкций не нужны, вместо этого можно использовать `if`, `case`, `cond` как часть любого другого выражения! Вот парочка примеров:
-
- ```clojure
- (println (if true "Ok" "Oops")) ; => Ok
- (println (when false "Ok")) ; => nil
- ```
-
- Заметьте, во втором случае условие для `when` было ложным, поэтому всё выражение `when` вычислилось в специальное "пустое" значение, но тем не менее — вычислилось! И функция `println` вывела это значение на экран, пусть даже и в таком своеобразном виде.
- Если помнить об этом свойстве языка, то можно писать довольно-таки сложные выражения, не выделяя промежуточные вычисления в переменные:
-
- ```clojure
- (defn classify [x]
- (cond
- (< x 0) "Negative"
- (case x
- (13 42 100500) true
- false) "Special"
- :else "Boring"))
- ```
-
- В этом примере используются сильные стороны и `cond` и `case`: первый хорошо справляется с выбором по условию, а второй хорошо проверяет на совпадение с конкретными значениями, выступая при этом в качестве того самого условия для `cond`.
- И, раз уж речь зашла о выносе подвыражений в переменные, то тут у Clojure тоже всё хорошо. В других языках иной раз приходится делать присваивание значений переменным из условной конструкции или обходиться тернарным оператором. В Clojure же можно использовать обычный `let`:
-
- ```clojure
- (defn classify [x]
- (let [special?
- (case x
- (13 42 100500) true
- false)]
- (cond
- (< x 0) "Negative"
- special? "Special"
- :else "Boring")))
- ```
-
- > Этот вариант читается лучше, чем предыдущий, но имеет один недостаток: значение `special?` вычисляется в любом случае, тогда как в предыдущем варианте его вычислять не нужно, если выполнилось условие, проверяющее аргумент на отрицательность.
-
-instructions: |
- Реализуйте функцию `do-today`, которая принимает порядковый номер дня недели (целое число) в качестве аргумента и вычисляется в
- - строку `"work"` для дней с понедельника (`1`) по пятницу (`5`),
- - строку `"rest"` для субботы (`6`) и воскресенья (`7`),
- - строку `"???"` для всех остальных значений, в том числе и для нечисловых!
- Попробуйте использовать в решении различные комбинации `if`, `cond` и `case`.
-
-tips:
- - |
- Используйте функцию-предикат `int?` чтобы проверить, что аргумент — целое число.
diff --git a/modules/20-logic/50-expressions/ru/data.yml b/modules/20-logic/50-expressions/ru/data.yml
index 5bbbda8..3f71f32 100644
--- a/modules/20-logic/50-expressions/ru/data.yml
+++ b/modules/20-logic/50-expressions/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Логические выражения
tips:
- >
diff --git a/modules/25-lists/10-intro/description.ru.yml b/modules/25-lists/10-intro/description.ru.yml
deleted file mode 100644
index 9fab213..0000000
--- a/modules/25-lists/10-intro/description.ru.yml
+++ /dev/null
@@ -1,34 +0,0 @@
----
-
-name: Объявление списков
-theory: |
- ### Функция `list` и цитирование
- В языке, призванном работать со списками просто обязан существовать синтаксис для объявления литералов списков. Проще всего воспользоваться функцией с именем `list`:
-
- ```clojure
- (list 1 2 3) ; '(1 2 3)
- (list "apple" "orange") ; '("apple" "orange")
- (list 1 (list 2 3) 4) ; '(1 (2 3) 4)
- ```
- Заметьте, что результаты вычисления показываются без упоминания имени функции "list", но зато с одинарной кавычкой перед открывающей скобкой.
-
- Дело в том, что в Clojure можно взять любое выражение в скобках и сказать интерпретатору, "не вычисляй его, а воспринимай как список". Такая операция называется "quotation" или "цитированием", а одинарная кавычка означает начало цитаты. Заканчивается же цитата там, где стоит закрывающая скобка, парная для той, что стоит после кавычки.
-
- Подробнее про цитирование вы узнаете позже (в модуле про макросы). Пока же рекомендуем вам использовать именно функцию `list` — именно потому, что это *функция*, а не специальный синтаксис.
-
- Если ещё раз посмотреть на примеры выше, то последний может навести на мысль: списки в Clojure *гетерогенные*, то есть любой список может одновременно содержать значения разных типов. В упомянутом примере в одном списке содержатся и числа, и вложенный список.
-
- ### Функция-предикат `list?`
- Чтобы узнать, является ли какое-то значение списком, используют функцию `list?`. Это распространённый приём — для каждого типа иметь функцию-тест с тем же именем и знаком вопроса в конце! Для строк это `string?`, для целых чисел это `integer?` и так далее.
-instructions: |
- Реализуйте функцию `triple`, которая должна принимать один аргумент любого типа и возвращать список, в котором содержится три копии аргумента:
-
- ```clojure
- (triple "a") ; '("a" "a" "a")
- (triple 0) ; '(0 0 0)
- ```
-tips:
- - |
- [Официальная документация](https://clojure.org/guides/learn/sequential_colls#_lists)
- - |
- [Про цитирование](https://clojuredocs.org/clojure.core/quote)
diff --git a/modules/25-lists/10-intro/ru/data.yml b/modules/25-lists/10-intro/ru/data.yml
index 8dc9cdf..e346344 100644
--- a/modules/25-lists/10-intro/ru/data.yml
+++ b/modules/25-lists/10-intro/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Объявление списков
tips:
- >
diff --git a/modules/25-lists/20-builtin-loops-map/description.ru.yml b/modules/25-lists/20-builtin-loops-map/description.ru.yml
deleted file mode 100644
index 7c2a54a..0000000
--- a/modules/25-lists/20-builtin-loops-map/description.ru.yml
+++ /dev/null
@@ -1,50 +0,0 @@
----
-
-name: Встроенные средства обхода списков, map
-theory: |
- Любой список рано или поздно захочется *обойти* (*traverse*), то есть поработать с отдельными элементами. В процедурных языках используются циклы, но многие языки имеют и декларативные средства работы с коллекциями — `map`, `filter`, `reduce`. А ведь сами эти функции пришли в программирование через LISP!
- Clojure тоже предоставляет полный набор таких функций. Ближайшие несколько уроков будут посвящены этим встроенным в Clojure функциям.
-
- ### `map`
- Итак, `map` в Clojure используется так:
-
- ```clojure
- (map inc (list 1 2 3)) ; '(2 3 4)
- ```
-
- Здесь `inc` — встроенная функция, добавляющая к числу единицу. Всё максимально предсказуемо: `map` превращает старый список в новый, применяя функцию к каждому элементу. Обход получается *функциональный*, потому что мы получаем **новый** список, не меняя старый.
- Может `map` обходить и несколько списков одновременно: можно применить `map` к нескольким спискам, тогда функция-аргумент будет применена ко всем первым элементам, затем ко всем вторым, и так далее. Но `map` потребует от входных списков иметь одинаковую длину, иначе вы получите ошибку.
- Вот так можно поэлементно просуммировать три списка:
-
- ```clojure
- (map +
- (list 1 2 3)
- (list 10 20 30)
- (list 100 200 300))
- ; '(111 222 333)
- ```
-
- Заметьте, не потребовалось даже использовать анонимную функцию, которая складывала бы три числа, ведь функция "`+`" принимает произвольное количество аргументов!
-
-instructions: |
- Реализуйте функцию `maps`, которая должна принимать два списка — список функций и *список списков* аргументов — и возвращать список результатов применения функций к наборам аргументов. Вот как использование `maps` должно выглядеть:
-
- ```clojure
- (maps
- (list
- inc
- string?)
- (list
- (list 10 20)
- (list "a" 0)))
- ; '((11 21) (true false))
- ```
-
- Здесь
- - `'(11 21)`, это результат применения `inc` к `(list 10 20)`
- - `'(true false)`, это результат применения `string?` к `(list "a" 0)`
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/map)
- - |
- [Про map, filter, reduce](https://ru.hexlet.io/blog/posts/js-prosto-o-slozhnom-filter-map-reduce)
diff --git a/modules/25-lists/20-builtin-loops-map/ru/data.yml b/modules/25-lists/20-builtin-loops-map/ru/data.yml
index 593c474..e665bd8 100644
--- a/modules/25-lists/20-builtin-loops-map/ru/data.yml
+++ b/modules/25-lists/20-builtin-loops-map/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Встроенные средства обхода списков, map
tips:
- |
diff --git a/modules/25-lists/30-builtin-filters/description.ru.yml b/modules/25-lists/30-builtin-filters/description.ru.yml
deleted file mode 100644
index c3ad7b0..0000000
--- a/modules/25-lists/30-builtin-filters/description.ru.yml
+++ /dev/null
@@ -1,31 +0,0 @@
----
-
-name: Фильтрация списков, filter
-theory: |
- Функция `map` может менять элементы списка, но не может менять их количество: сколько элементов было в исходном списке, столько же останется и в результирующем. Функция `filter` же, напротив, не может менять сами элементы, но может решать, какие из них попадут в выходной список, а какие будут отброшены. Так `map` и `filter`, выполняя каждая свою задачу, взаимно дополняют друг друга!
- ### `filter`
- Функция `filter` принимает два аргумента:
- 1. Функцию-предикат, которая должна вернуть `true` для тех элементов, которые нужно оставить
- 2. Список элементов для последующего отбора
- Результатом вызова `filter` будет *новый* список, содержащий элементы, одобренные аргументом-предикатом.
- Благодаря тому, что в Clojure доступно великое множество функций-предикатов, использовать `filter` легко и удобно:
-
- ```clojure
- (filter int? (list 1 2.5 "foo" 7)) ; '(1 7)
- (filter pos? (list -1 5 42 0 -100 3)) ; '(5 42 3)
- ```
-
-instructions: |
- Реализуйте функцию `increment-numbers`, которая берёт из списка-аргумента значения, являющиеся числами (`number?`) и возвращает список этих чисел, увеличив предварительно каждое число на единицу (`inc`). Пример:
-
- ```clojure
- (increment-numbers (list 10 "foo" false (list 2 3) 3/5)) ; '(11 8/5)
- ```
-
- > Заметьте, Clojure умеет работать с дробями вроде `3/5` и `8/5`!
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/filter)
- - |
- [Про map, filter, reduce](https://ru.hexlet.io/blog/posts/js-prosto-o-slozhnom-filter-map-reduce)
diff --git a/modules/25-lists/30-builtin-filters/ru/data.yml b/modules/25-lists/30-builtin-filters/ru/data.yml
index 7bc999f..5aa8bbf 100644
--- a/modules/25-lists/30-builtin-filters/ru/data.yml
+++ b/modules/25-lists/30-builtin-filters/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Фильтрация списков, filter
tips:
- |
diff --git a/modules/25-lists/40-reduce/description.ru.yml b/modules/25-lists/40-reduce/description.ru.yml
deleted file mode 100644
index 18ec91b..0000000
--- a/modules/25-lists/40-reduce/description.ru.yml
+++ /dev/null
@@ -1,58 +0,0 @@
----
-
-name: Сворачивание списков, функции свёртки reduce
-theory: |
- Функции `map` и `filter` обрабатывают списки, сохраняя саму структуру. Но иногда нужно избавиться от этой самой структуры, вычислив какое-то итоговое значение. Простейший пример — сумма всех чисел в списке. Или текст, собранный из списка строк.
- В процедурных языках для получения итоговых значений по списку проходят с использованием цикла и промежуточный результат хранят в отдельной переменной — в так называемом *аккумуляторе*.
- Декларативным же аналогом такого цикла будет операция *сворачивания* (*folding*) или, как ещё говорят, получение *свёртки* (*fold*). Суть сворачивания списка заключается в последовательном применении некоторой *бинарной операции* к очередному элементу списка и текущему значению аккумулятора у с целью получить новое значение аккумулятора. Давайте рассмотрим процесс сворачивания списка `(list 1 2 3 4)` в сумму чисел. Начальным значением аккумулятора будет `0`, а операцией — `+`. Сложить числа можно как минимум двумя способами:
- 1. двигаясь от первого элемента к последнему, слева-направо
- ```
- (((0 + 1) + 2) + 3) + 4
- ```
- 2. двигаясь от последнего элемента к первому, справа-налево
- ```
- 1 + (2 + (3 + (4 + 0)))
- ```
- Для операции сложения не имеет значения то, какой из вариантов мы выберем. Потому что операция сложения [ассоциативна](https://ru.wikipedia.org/wiki/%D0%90%D1%81%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F). Но далеко не все операции таковы: например, при конкатенации строк важно, последнюю мы будем с первой складывать или наоборот!
- Однако из-за того, что Clojure недостаточно ленив, то в нем используется только свертка слева-направо, с помощью функции `reduce`:
-
- ```clojure
- (reduce + 0 (list 1 2 3)) ; 6
- (reduce - 0 (list 1 2 3)) ; -6
- ```
-
- Попробуем теперь выводить каждое новое значение на экран, игнорируя аккумулятор:
-
- ```clojure
- (defn f [acc x]
- (println x))
- (reduce f nil '(1 2 3))
- ; => 1
- ; => 2
- ; => 3
- ```
-
- В большинстве случаев используют левую свёртку (`reduce`) потому, что она более интуитивна — двигается от первого элемента к последнему — и работает *эффективнее*. Однако иногда полезна именно правая, но в стандартной библиотеке она отсутствует.
- Стоит напоследок упомянуть, что `reduce` не может обходить несколько списков одновременно, как это делает `map`, поэтому придется предварительно подготовить обрабатываемые списки в промежуточный.
-
-instructions: |
- Реализуйте функцию `max-delta`, которая должна принимать два списка чисел и вычислять максимальную разницу (абсолютное значение разницы) между соответствующими парами элементов.
- Пример использования:
-
- ```clojure
- (max-delta
- (list 10 -15 35)
- (list 2 -12 42)) ; 8
- ```
- Вам пригодятся функции `Math/abs` и `max`:
-
- ```clojure
- (Math/abs 42) ; 42
- (Math/abs -13) ; 13
- (max 1 5 3) ; 5
- ```
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/reduce)
- - |
- [Про map, filter, reduce](https://ru.hexlet.io/blog/posts/js-prosto-o-slozhnom-filter-map-reduce)
diff --git a/modules/25-lists/40-reduce/ru/data.yml b/modules/25-lists/40-reduce/ru/data.yml
index 4e0656b..406b41d 100644
--- a/modules/25-lists/40-reduce/ru/data.yml
+++ b/modules/25-lists/40-reduce/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Сворачивание списков, функции свёртки reduce
tips:
- |
diff --git a/modules/25-lists/50-list-internals/description.ru.yml b/modules/25-lists/50-list-internals/description.ru.yml
deleted file mode 100644
index 1f331ce..0000000
--- a/modules/25-lists/50-list-internals/description.ru.yml
+++ /dev/null
@@ -1,48 +0,0 @@
----
-
-name: Внутреннее устройство списков, пары, cons, nil
-theory: |
- Все списки, с которыми вы имели дело до этого момента, либо создавались с помощью литералов и функции `list` либо являлись результатами вычисления каких-то выражений. Во всех этих случаях список выглядит, как нечто неделимое, цельное. Именно так работает абстракция: для работы со списками *достаточно* думать о списке, как о самостоятельной и самодостаточной сущности!
-
- Тем из вас, кто сталкивался в других языках с массивами, могло показаться, что и в Clojure списки, это те же массивы — отдельные участки памяти компьютера, хранящие все значения рядом.
-
- На самом же деле во всех языках семейства lisp, да и в большинстве других языков, располагающих к функциональному подходу, списки — это *цепочки вложенных пар*. А вот *пара* уже является неделимой структурой. И чтобы понять списки, нужно сначала освоить пары. Однако! В Clojure ситуация со списками немного иная, в списках используются обычный связный список с головой и хвостом. Если хочется лучше понять смысл пар, крайне рекомендуется прочесть Структуру и Интерпретацию Компьютерных Программ (SICP).
-
- Для работы со списками существуют функции `first`, `rest`, `last`:
-
- ```clojure
- (def my-list '(1 2 3))
-
- (first my-list) ; => 1
- (last my-list) ; => 3
- (rest my-list) ; => '(2 3)
- ```
-
- Помимо перечисленных функций выше, есть еще упрощающие обработку списков:
-
- ```clojure
- (count (list 1 2 3)) ; => 3
-
- (empty? (list)) ; => true
- (empty? (list 1 2 3)) ; => false
-
- (list? (list 1 2 3)) ; => true
-
- ; нумерация для nth идет с нуля
- (nth '(1 2 3) 1) ; => 2
- ```
-
-instructions: |
- Реализуйте функцию `lookup`, которая бы должна принимать аргумент-ключ и список пар "ключ-значение" и возвращать либо пару "ключ-значение", где ключ равен первому аргументу, либо возвращать `false`, если подходящих пар в списке не нашлось. Если подходящих пар найдётся несколько, вернуть нужно *первую*.
- Примеры:
-
- ```clojure
- (def user-ages
- (list (list "Tom" 31)
- (list "Alice" 22)
- (list "Bob" 42)))
- (lookup "Bob" user-ages) ; '("Bob" . 42)
- (lookup "Tom" user-ages) ; '("Tom" . 31)
- (lookup "Moe" user-ages) ; false
- ```
-tips: []
diff --git a/modules/25-lists/50-list-internals/ru/data.yml b/modules/25-lists/50-list-internals/ru/data.yml
index e48a307..8a892aa 100644
--- a/modules/25-lists/50-list-internals/ru/data.yml
+++ b/modules/25-lists/50-list-internals/ru/data.yml
@@ -1,2 +1,3 @@
+---
name: Внутреннее устройство списков, пары, cons, nil
tips: []
diff --git a/modules/25-lists/60-list-recursion/description.ru.yml b/modules/25-lists/60-list-recursion/description.ru.yml
deleted file mode 100644
index ac50257..0000000
--- a/modules/25-lists/60-list-recursion/description.ru.yml
+++ /dev/null
@@ -1,54 +0,0 @@
----
-
-name: Обход списков и рекурсия
-theory: |
- Универсальные функции вроде `map` и `filter` — основные инструменты по обработке списков. Но их выразительности хватает не всегда, и тут уже на помощь приходят функция свёртки `reduce`. Умение использовать этот готовый инструментарий в теории позволит решать *любые* задачи, возникающие при работе со списками.
- Однако умение работать со списками "вручную" полезно само по себе, так как учит работе с произвольными *рекурсивными структурами данных*: вспомните, список это пара из головы и списка-хвоста — простейшая рекурсивная структура данных!
- В функциональном программировании рекурсивные структуры данных принято обрабатывать… рекурсивными функциями! Так, функция, работающая со списком, отделяет голову от хвоста и формирует результат на основе значения головы и *результата применения себя* к хвосту — сама структура списка диктует то, как с ним нужно работать.
- Рассмотрим простейший пример обработки списка на примере функции вычисления суммы его элементов.
- Функция будет рекурсивной, а значит нам нужно определиться с *условием останова* (*terminating case*) — без него вычисление функции никогда не завершится. Для большинства функций, работающих со списками, останов наступает тогда, когда функция применяется к пустому списку: становится нечего делить на голову и хвост. Вот как функция будет выглядеть в коде:
-
- ```clojure
- (defn sum [vals]
- (if (empty? vals)
- 0 ; список пустой, сумма равна нулю
- (let [head (first vals) ; голова
- tail (rest vals)] ; хвост
- (+ head
- (sum tail))))) ; обрабатываем хвост рекурсивно
- ```
- ### Хвостовой вызов
- Так как мы используем рекурсию, вполне возможен вариант с переполнением стека, для предотвращения переполнения используется специальная функция `recur` (да, интерпретатор Clojure не умеет автоматически оптимизировать рекурсивные вызовы).
- Пример:
-
- ```clojure
- (defn sum
- ([vals] (sum vals 0))
- ([vals acc]
- (if (empty? vals)
- acc
- (recur (rest vals) (+ (first vals) acc)))))
- ; попробуем вызвать переполнение стека
- (sum (vec (range 0 9999999)))
- 49999985000001
- ; переполнение стека не произошло!
- ```
- > Умение интерпретатора видеть, когда обычный вызов функции с приращением стека вызовов можно заменить на возврат по стеку выше и подстановку нового вызова, называется *оптимизацией хвостового вызова* (*tail call optimization*). Некоторые интерпретаторы и компиляторы могут оптимизировать только рекурсивные вызовы (это уже так называемая *оптимизация хвостовой рекурсии*), другие — любые хвостовые вызовы.
-
-instructions: |
- Реализуйте функцию `skip`, которая должна принимать два аргумента — целое число n и список — и возвращать новый список, содержащий все элементы из первого списка за исключением n первых элементов. Если n окажется больше, чем количество элементов во входном списке, результатом должен быть пустой список.
- Примеры:
-
- ```clojure
- (skip -5 (list 1 2 3)) ; '(1 2 3)
- (skip 0 (list 1 2 3)) ; '(1 2 3)
- (skip 1 (list 1 2 3)) ; '(2 3)
- (skip 10 (list 1 2 3)) ; '()
- ```
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/recur)
- - |
- [И еще официальная документация](https://clojure.org/about/functional_programming#_recursive_looping)
- - |
- [Описание хвостовой рекурсии](https://ru.wikipedia.org/wiki/%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%B0%D1%8F_%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F)
diff --git a/modules/25-lists/60-list-recursion/ru/data.yml b/modules/25-lists/60-list-recursion/ru/data.yml
index f1f7e4c..1b4ed5c 100644
--- a/modules/25-lists/60-list-recursion/ru/data.yml
+++ b/modules/25-lists/60-list-recursion/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Обход списков и рекурсия
tips:
- |
diff --git a/modules/30-strings/10-strings/description.ru.yml b/modules/30-strings/10-strings/description.ru.yml
deleted file mode 100644
index 071bc28..0000000
--- a/modules/30-strings/10-strings/description.ru.yml
+++ /dev/null
@@ -1,34 +0,0 @@
----
-
-name: Строки
-theory: |
- Строки в Clojure, являются экземплярами класса `java.lang.String` и к ним можно применять различные функции определенные в этом классе. Форма записи строк Clojure совпадает со стандартной записью строк в Java. Строки всегда представлены двойными кавычками.
-
- ```clojure
- (ns my-ns
- (:require [clojure.string :as s]) ; импортируем модуль работы со строками
-
- (def my-str "hello, world")
-
- (def splitted-str
- (s/split my-str #", ")) ; ["hello" "world"]
-
- (str my-str "!") ; "hello, world!"
-
- (s/join #", " splitted-str) ; "hello, world"
-
- (count my-str) ; 12
- ```
-
- Работа со строками в целом типична, как и в большинстве языков программирования.
-
-instructions: |
- Реализуйте функцию `str-reverse`, которая переворачивает строку:
-
- ```clojure
- (str-reverse "Hello") ; "olleH"
- (str-reverse "") ; ""
- ```
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.string)
diff --git a/modules/30-strings/10-strings/ru/data.yml b/modules/30-strings/10-strings/ru/data.yml
index 2f95582..6f25e90 100644
--- a/modules/30-strings/10-strings/ru/data.yml
+++ b/modules/30-strings/10-strings/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Строки
tips:
- |
diff --git a/modules/30-strings/20-chars/description.ru.yml b/modules/30-strings/20-chars/description.ru.yml
deleted file mode 100644
index 5db9ff0..0000000
--- a/modules/30-strings/20-chars/description.ru.yml
+++ /dev/null
@@ -1,38 +0,0 @@
----
-
-name: Символы
-theory: |
- Помимо построковой обработки, в Clojure, конечно же, есть и посимвольная обработка.
-
- ```clojure
- (seq (char-array "asdf")) ; (\a \s \d \f)
-
- (char 97) ; \a
-
- (map char [65 66 67 68]) ; (\A \B \C \D)
-
-
- (apply str
- (map char [67 108 111 106 117 114 101]))
- ; "Clojure"
-
- (seq "abc") ; (\a \b \c)
-
- (apply str (char-array "1234")) ; "1234"
- ```
-
- Важно отметить, что не все функции, которые работают со строками, поддерживают работу с символами. Так же, напрямую вызывать на строке функции `map`, `filter`, `reduce` тоже не получится, поэтому необходимо предварительно подготовить обрабатываемые строки, например, с помощью функции `seq` или `clojure.string/split`.
-
-instructions: |
- Реализуйте функцию `next-chars`, которая создаёт новую строку на основе строки-аргумента таким образом, что каждый символ новой строки является "следующим" (с точки зрения кода) по отношению к соответствующему символу исходной строки.
- Примеры:
-
- ```clojure
- (next-chars "") ; ""
- (next-chars "abc") ; "bcd"
- (next-chars "12345") ; "23456"
- ```
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/char)
diff --git a/modules/30-strings/20-chars/ru/data.yml b/modules/30-strings/20-chars/ru/data.yml
index 00eb16f..c17a1c0 100644
--- a/modules/30-strings/20-chars/ru/data.yml
+++ b/modules/30-strings/20-chars/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Символы
tips:
- |
diff --git a/modules/30-strings/30-format/description.ru.yml b/modules/30-strings/30-format/description.ru.yml
deleted file mode 100644
index 0c1aa1b..0000000
--- a/modules/30-strings/30-format/description.ru.yml
+++ /dev/null
@@ -1,39 +0,0 @@
----
-
-name: Форматирование строк
-theory: |
- В программировании часто возникает задача собрать строку из значений, которые не являются строками сами по себе. Для решения подобных задач применяют *форматирование* (*formatting*) строк (оно же — "строковая интерполяция"). В Clojure рассмотрим самые популярные
- ### Функции `format` и `printf`
- Функции `format` и `printf` ожидают в качестве первого аргумента строку-шаблон и несколько значений, которые будут в этот шаблон подставлены. Разница же между функциями в том, что вызов `format` вычисляется в строку, а `printf` сразу выводит значение на экран (результатом же вычисления будет `nil`).
- Шаблон может содержать произвольный текст, в нужные места которого вставлены последовательности (подробности в классе `java.util.Formatter`).
- Вот несколько примеров применения `format`:
-
- ```clojure
- (format "Hello there, %s" "bob")
- ; => "Hello there, bob"
- (format "%5d" 3)
- ; => " 3"
- (format "Pad with leading zeros %07d" 5432)
- ; => "Pad with leading zeros 0005432"
- (format "Left justified :%-7d:" 5432)
- ; => "Left justified :5432 :"
- (format "decimal %d octal %o hex %x upper-case hex %X" 63 63 63 63)
- ; => "decimal 63 octal 77 hex 3f upper-case hex 3F"
- (format "%.3f" 2.0)
- ; => "2.000"
- (format "%.3f" (double (/ 5 2)))
- ; => "2.500"
- ```
-instructions: |
- Реализуйте функцию `number-presenter`, которая представляет число в нескольких форматах.
- Примеры:
-
- ```clojure
- (number-presenter 63)
- ; => "decimal 63 octal 77 hex 3f upper-case hex 3F"
- (number-presenter 14)
- ; => "decimal 14 octal 16 hex e upper-case hex E"
- ```
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/format)
diff --git a/modules/30-strings/30-format/ru/data.yml b/modules/30-strings/30-format/ru/data.yml
index a8c9684..f2bfc43 100644
--- a/modules/30-strings/30-format/ru/data.yml
+++ b/modules/30-strings/30-format/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Форматирование строк
tips:
- |
diff --git a/modules/35-vectors/10-intro-vectors/description.ru.yml b/modules/35-vectors/10-intro-vectors/description.ru.yml
deleted file mode 100644
index 1f2dbe6..0000000
--- a/modules/35-vectors/10-intro-vectors/description.ru.yml
+++ /dev/null
@@ -1,63 +0,0 @@
----
-
-name: О векторах
-theory: |
- Массивы, а точнее вектора, в Clojure ведут себя так же, как и массивы в других популярных языках программирования, у каждого элемента есть свой индекс, сами вектора иммутабельны (то есть при изменении исходного массива создается его копия с измененным элементом). Рассмотрим несколько примеров:
-
- ```clojure
- ([1 2 3] 0) ; => 1
- ([1 2 3] 1) ; => 2
- (get [1 2 3] 2) ; => 3
- (vector 1 2 3) ; => [1 2 3]
- (vector? [1 2 3]) ; => true
-
- ; в вектора можно складывать различные данные
- (vector "a" "b" [1] 2 (list 1 2))
- ; => ["a" "b" [1] 2 (1 2)]
-
- (count [1 2 3]) ; => 3
-
- ; добавление элементов
- (conj [1 2 3] [4 5 6])
- ; => [1 2 3 4 5 6]
-
- ; как уже говорилось, вектора иммутабельны
- (def v [1 2 3])
- ; => [1 2 3]
- (conj v [4 5])
- ; => [1 2 3 4 5]
- (println v)
- ; => [1 2 3]
- ```
-
- Стоит отметить особенность при обходе векторов с помощью `map` и `filter`, что при использовании обычных вариантов этих функций, результат будет упакован в список, если же необходимо получить результат в виде вектора, для функций `map` и `filter` используются их аналоги `mapv` и `filterv`.
- Примеры:
-
- ```clojure
- (map inc [1 2 3])
- ; => (2 3 4)
- (filter odd? [1 2 3 4 5])
- ; => (1 3 5)
-
- (mapv inc [1 2 3])
- ; => [2 3 4]
- (filterv odd? [1 2 3 4 5])
- ; => [1 3 5]
- ```
-
-instructions: |
- Реализуйте функцию `zip`, которая группирует элементы переданных векторов в подвектора.
- Примеры:
-
- ```clojure
- (zip [] [])
- ; => []
- (zip [1 2 3 4] [5 6 7 8])
- ; => [[1 5] [2 6] [3 7] [4 8]]
- (zip [1 2] [3 4])
- ; => [[1 3] [2 4]]
- ```
-
-tips:
- - |
- [Официальная документация](https://clojure.org/guides/learn/sequential_colls)
diff --git a/modules/35-vectors/10-intro-vectors/ru/data.yml b/modules/35-vectors/10-intro-vectors/ru/data.yml
index 4f98f1f..4c679de 100644
--- a/modules/35-vectors/10-intro-vectors/ru/data.yml
+++ b/modules/35-vectors/10-intro-vectors/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: О векторах
tips:
- >
diff --git a/modules/35-vectors/20-vectors-choose/description.ru.yml b/modules/35-vectors/20-vectors-choose/description.ru.yml
deleted file mode 100644
index 1fc39d4..0000000
--- a/modules/35-vectors/20-vectors-choose/description.ru.yml
+++ /dev/null
@@ -1,19 +0,0 @@
----
-
-name: Выбор векторов
-theory: |
- Когда использовать вектора, если есть списки? Достаточно знать ответы на несколько вопросов, если мы часто обращаемся к случайному элементу структуры данных и редко в нее что-либо добавляем, то векторы (массивы) будут правильным вариантом, если же мы динамически добавляем элементы в структуру данных и нам не так важна скорость получения случайного элемента, тогда можно использовать списки.
-
-instructions: |
- Реализуйте функцию `sum`, которая суммирует все элементы вектора.
- Примеры:
-
- ```clojure
- (sum []) ; => 0
- (sum [10 -20]) ; => -10
- (sum [1 2 3 4]) ; => 10
- ```
-
-tips:
- - |
- [Про разницу между массивами и списками](https://www.geeksforgeeks.org/linked-list-vs-array/)
diff --git a/modules/35-vectors/20-vectors-choose/ru/data.yml b/modules/35-vectors/20-vectors-choose/ru/data.yml
index e2e8214..bd4642e 100644
--- a/modules/35-vectors/20-vectors-choose/ru/data.yml
+++ b/modules/35-vectors/20-vectors-choose/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Выбор векторов
tips:
- >
diff --git a/modules/35-vectors/30-contains/description.ru.yml b/modules/35-vectors/30-contains/description.ru.yml
deleted file mode 100644
index fd2ad17..0000000
--- a/modules/35-vectors/30-contains/description.ru.yml
+++ /dev/null
@@ -1,54 +0,0 @@
----
-
-name: Поиск элемента в коллекции
-theory: |
- Одним из частых вопросов новичков в Clojure является неразбериха с функцией `contains?`. Казалось бы, в чем может быть проблема? Из названия функции, создается ощущение, что функция проверяет, есть ли элемент в коллекции, однако не все так просто. Функция `contains?` проверяет наличие **ключа** в переданной коллекции, то есть не наличие элемента, а наличие **ключа**.
-
- Примеры:
-
- ```clojure
- (contains? [10 20 30] 10) ; => false
- ; действительно, в векторе элемент с индексом 10 отсутствует
-
- (contains? [10 20 30] 1) ; => true
- ; элемент с первым индексом в векторе есть и это число 20
- ```
-
- Как же тогда проверить, есть ли элемент в коллекции? Есть несколько способов:
-
- - Вызвать метод Java `.contains` (методы из Java вызываются с префиксной точкой);
- - С помощью функции `some`;
- - С помощью множеств (set), объявляются с помощью символов `#{}`, либо с помощью функции `set`.
-
- Рассмотрим все варианты:
-
- ```clojure
- ; Java метод
- (.contains [10 20 30] 10) ; => true
- (.contains [10 20 30] 1) ; => false
-
- ; Функция some
- (some (fn [elem] (= elem 3)) [1 2 3 4]) ; => true
- (some (fn [elem] (= elem 6)) [1 2 3 4]) ; => nil
-
- ; Множества
- (some #{3} [1 2 3 4]) ; => 3
- (some #{6} [1 2 3 4]) ; => nil
-
- ; и еще множества
- (def my-set (set [1 2 3 3 2])) ; => #{1 2 3}
- (contains? my-set 2) ; => true
- (contains? my-set 10) ; => false
- ```
-
-instructions: |
- Реализуйте функцию `my-contains?`, которая проверяет, есть ли переданный **элемент** в коллекции. Для конвертации `nil` можно воспользоваться функцией `boolean`. Метод `.contains` использовать нельзя :)
- Примеры:
-
- ```clojure
- (my-contains? [1 2 4 9] 2) ; => true
- (my-contains? [1 2 4 9] 0) ; => false
- (my-contains? [1 2 4 9] 9) ; => true
- ```
-
-tips: []
diff --git a/modules/35-vectors/30-contains/ru/data.yml b/modules/35-vectors/30-contains/ru/data.yml
index 20ec8e2..228d4f9 100644
--- a/modules/35-vectors/30-contains/ru/data.yml
+++ b/modules/35-vectors/30-contains/ru/data.yml
@@ -1,2 +1,3 @@
+---
name: Поиск элемента в коллекции
tips: []
diff --git a/modules/35-vectors/40-immutable-structures/description.ru.yml b/modules/35-vectors/40-immutable-structures/description.ru.yml
deleted file mode 100644
index bf07855..0000000
--- a/modules/35-vectors/40-immutable-structures/description.ru.yml
+++ /dev/null
@@ -1,30 +0,0 @@
----
-
-name: Иммутабельные структуры данных
-theory: |
- Как было сказано ранее, структуры данных в Clojure иммутабельны. То есть при изменении коллекции, будет создаваться новая, с измененным элементом. Естественно, возникает вопрос, а насколько это оптимальная операция, на каждое изменение создавать новую коллекцию?
-
- Конечно же, это происходит не совсем так. В Clojure предусмотрен специальный механизм, который отслеживает изменения в структуре данных. Сделано это с помощью ссылок, например, если в векторе меняется один из элементов, то в новом векторе будут ссылки на старые значения, которые не поменялись, плюс новый элемент, который не будет ссылаться ни на что. Это позволяет сократить расходы на память. Однако, если изменить все элементы, тогда полученная структура данных не будет ссылаться на предыдущие элементы и вектор будет совершенно новым.
-
- Однако, в ситуациях, когда иммутабельность начинает сильно влиять на производительность, используются `трансдьюсеры` (transducers). Подробнее они будут рассмотрены в следующем упражнении.
-
-
-instructions: |
- Реализуйте функцию, которая разделит вектор на конкретное количество частей.
-
- Примеры:
-
- ```clojure
- (partiphify [1] 2)
- ; => [[1] []]
- (partiphify [1 2 3] 3)
- ; => [[1] [2] [3]]
- (partiphify [1 2 3 4 5] 2)
- ; => [[1 2 3] [4 5]]
- ```
-
-tips:
- - |
- [Об иммутабельности в Clojure](https://clojure.org/reference/transients)
- - |
- [Для любопытных, про трансдьюсеры](https://clojure.org/reference/transducers). Для решения задания можно воспользоваться функцией `partition-all`
diff --git a/modules/35-vectors/40-immutable-structures/ru/data.yml b/modules/35-vectors/40-immutable-structures/ru/data.yml
index cba332d..7bbd46b 100644
--- a/modules/35-vectors/40-immutable-structures/ru/data.yml
+++ b/modules/35-vectors/40-immutable-structures/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Иммутабельные структуры данных
tips:
- |
diff --git a/modules/35-vectors/50-transducers/description.ru.yml b/modules/35-vectors/50-transducers/description.ru.yml
deleted file mode 100644
index 2d11c5f..0000000
--- a/modules/35-vectors/50-transducers/description.ru.yml
+++ /dev/null
@@ -1,105 +0,0 @@
----
-
-name: Трансдьюсеры
-theory: |
- Как уже было упомянуто, по умолчанию обработка коллекций в Clojure знаменитой тройкой `map`, `filter` и `reduce` иммутабельна, что иногда может сильно увеличить расход памяти и время обработки коллекции большого размера. Однако в таких ситуациях на помощь приходят *трансдьюсеры*.
-
- Трансдьюсеры - это составные алгоритмические преобразования над коллекциями. Они независимы от контекста своих входных и выходных параметров и задают только суть преобразования в терминах отдельного элемента коллекции. Поскольку трансдьюсеры не зависят от ввода или вывода, их можно использовать в различных процессах - коллекциях, потоках, каналах и т. д.
-
- Редукционная функция - это такая функция, которую вы передаете для редукции, то есть - это функция, которая принимает накопленный результат и новую коллекцию и возвращает новый накопленный результат:
-
- ```clojure
- ;; сигнатура редукционной функции
- что угодно, вход -> что угодно
- ```
-
- Трансдьюсер (иногда называемый `xform` или `xf`) - это преобразование одной редуцирующей функции в другую:
-
- ```clojure
- ;; сигнатура преобразователя
- (что угодно, вход -> что угодно) -> (что угодно, вход -> что угодно)
- ```
-
- А теперь попробуем сделать свою трансформацию коллекции с помощью трансдьюсеров:
-
- ```clojure
- (filter odd?) ;; этот вызов возвращает трансдьюсер, который фильтрует нечетные числа
- (map inc) ;; этот вызов возвращает трансдьюсер, который инкрементирует все числа на 1
- (take 5) ;; этот вызов возвращает трансдьюсер, который берет из коллекции первые 5 чисел
- ```
-
- Трансдьюсеры компонуются с помощью обычной функциональной композиции. Рекомендуемый способ компоновки трансдьюсеров - это использование существующей функции `comp`:
-
- ```clojure
- (def xf
- (comp
- (filter odd?)
- (map inc)
- (take 5)))
- ```
-
- Трансдьюсер `xf` - это стек преобразований, который будет применен к переданным элементам. Каждая функция в стеке выполняется перед операцией, которую она обертывает. Композиция трансдьюсеров выполняется справа налево, но строит стек преобразований, который выполняется слева направо (в этом примере фильтрация происходит перед маппингом).
-
- В качестве мнемоники запомните, что упорядочивание функций трансдьюсеров в `comp` имеет тот же порядок, что и преобразования последовательности в макросе `->>`. Пример выше эквивалентен следующему примеру:
-
- ```clojure
- (->> coll
- (filter odd?)
- (map inc)
- (take 5))
- ```
-
- Функции, которые можно использовать в качестве трансдьюсеров: `map`, `cat`, `mapcat`, `filter`, `remove`, `take`, `take-while`, `take-nth`, `drop`, `drop-while`, `replace`, `partition-by`, `partition-all`, `keep`, `keep-indexed`, `map-indexed`, `distinct`, `interpose`, `dedupe`, `random-sample`.
-
- А теперь посмотрим, как применять трансдьюсеры. Самым распространенным способом применения трансдьюсеров является использование функции `transduce`, которая является аналогом функции `reduce`, только для трансдьюсеров:
-
- ```clojure
- (transduce xform f coll)
- (transduce xform f init coll)
- ```
-
- `transduce` нелениво редуцирует `coll` с помощью преобразователя `xform`, применяемого к редуцирующей функции `f`, используя `init` в качестве начального значения, если оно предоставлено, или `f` в противном случае. `f` функция знает о том, как накапливать результат. А теперь примеры:
-
- ```clojure
- (def xf (comp (filter odd?) (map inc)))
-
- (transduce xf + (range 5))
- ;; => 6
-
- (transduce xf + 100 (range 5))
- ;; => 106
- ```
-
- `into` позволяет применять трансдьюсер к входной коллекции и создать на основе результата применения трансдьюсера новую коллекцию. Старайтесь по возможности использовать эту функцию, так как она применяет `reduce` эффективнее (без промежуточных коллекций), если есть такая возможность.
-
- ```clojure
- (into [] xf (range 10))
- [2 4 6 8 10]
-
- (into () xf (range 10)) ; => в списки элементы добавляются иначе, поэтому итоговый список в обратном порядке
- (10 8 6 4 2)
-
- (into #{} xf (range 10))
- #{4 6 2 10 8}
- ```
-
- Про создание трансдьюсеров поговорим в следующем упражнении.
-
-instructions: |
- Скомбинируйте несколько трансдьюсеров в один `my-xf`, в котором происходит умножение всех элементов на 10, затем деление на 5, а затем выбор только четных элементов (фильтрация).
-
-tips:
- - |
- [Выступление Рича Хикки про трансдьюсеры](https://www.youtube.com/watch?v=6mTbuzafcII)
- - |
- [Пост Рича Хикки про трансдьюсеры](https://cognitect.com/blog/2014/8/6/transducers-are-coming)
- - |
- [Когда стоит использовать трансдьюсеры](https://clojure.org/guides/faq#transducers_vs_seqs)
- - |
- [Официальная документация](https://clojure.org/reference/transducers)
- - |
- [Полезное про стратегии вычислений](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%B0%D1%82%D0%B5%D0%B3%D0%B8%D1%8F_%D0%B2%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F)
- - |
- [Про трансдьюсеры на примере JS, 1](https://habr.com/ru/post/325388/)
- - |
- [Про трансдьюсеры на примере JS, 2](https://habr.com/ru/post/237613/)
diff --git a/modules/35-vectors/50-transducers/ru/data.yml b/modules/35-vectors/50-transducers/ru/data.yml
index 9e6782a..e6bb216 100644
--- a/modules/35-vectors/50-transducers/ru/data.yml
+++ b/modules/35-vectors/50-transducers/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Трансдьюсеры
tips:
- >
diff --git a/modules/35-vectors/60-transducers-creation/description.ru.yml b/modules/35-vectors/60-transducers-creation/description.ru.yml
deleted file mode 100644
index e145688..0000000
--- a/modules/35-vectors/60-transducers-creation/description.ru.yml
+++ /dev/null
@@ -1,116 +0,0 @@
----
-
-name: Создание трансдьюсеров
-theory: |
- В прошлом уроке мы научились комбинировать трансдьюсеры, в этом уроке научимся создавать свои трансдьюсеры! Зачем? Для того, если обработку коллекций не получается свести к простым операциям `map`, `filter`, `reduce`. Для создания трансдьюсеров требуется соблюдать следующую форму:
-
- ```clojure
- (defn my-transduce-fn
- ([] ...)
- ([result] ...)
- ([result input] ...)))
- ```
-
- Получается, чтобы создать трансдьюсер, нужно как минимум объявить 3 варианта обработки коллекции (на самом деле два, для 0 арности и 1 арности):
-
- - Init функция (арность функции 0), основное действие, которое будет применено к коллекции при применении трансдьюсера `my-transduce-fn`.
- - Completion функция (арность функции 1), не все обработки коллекций сводятся к какому-то итоговому значению, однако для тех, которые сводятся (например `transduce`), completion функция как раз и используется для получения итогового значения. В этой функции трансдьюсер `my-transduce-fn` должен быть вызван ровно один раз.
- - Step функция (арность функции 2), эта функция применяется на каждом шаге при свертке коллекции, в ней `my-transduce-fn` вызывается с арностью 0 или больше, в зависимости от логики, которую реализует трансдьюсер. Например, при использовании `filter` трансдьюсер `my-transduce-fn` может быть применен или нет в зависимости от предиката, при использовании в `map`, трансдьюсер применится ровно один раз, в случае с функцией `cat` - трансдьюсер может быть применен несколько раз, в зависимости от переданной коллекции.
-
- Описание может немного запутать, поэтому для практики сделаем несколько трансдьюсеров:
-
- ```clojure
- ; трансдьюсер для инкремента коллекции на 1
- (defn increment-all
- ([] (map inc))
- ([coll] (sequence (increment-all) coll)))
-
- ; трансдьюсер для фильтрации четных чисел
- (defn filter-evens
- ([] (filter even?))
- ([coll] (sequence (filter-evens) coll)))
-
- ; трансдьюсер для умножения всех элементов на 2
- (defn double-all
- ([] (map #(* % 2)))
- ([coll] (sequence (double-all) coll)))
-
- ; трансдьюсер для умножения всей коллекции на свои элементы (поиск произведения коллекции)
- (defn product
- ([] *)
- ([coll] (reduce (product) 1 coll))
- ([xform coll] (transduce xform (product) 1 coll)))
- ```
-
- А теперь потестируем полученные трансдьюсеры:
-
- ```clojure
- ; классический подход с иммутабельными данными
- ; используется макрос `->>` для удобства композиции функций с точки зрения организации кода
- (->> (range 15)
- (map inc)
- (filter even?)
- (map #(* % 2))
- (reduce * 1))
- 82575360
-
- ; версия с трансдьюсерами
- (def my-transducer ; соберем все операции в один трансдьюсер
- (comp
- (increment-all)
- (filter-evens)
- (double-all)))
-
- (product my-transducer (range 15))
- 82575360
-
- ; а теперь проверим время обработки для двух случаев
- (time
- (->> (range 15)
- (map inc)
- (filter even?)
- (map #(* % 2))
- (reduce * 1)))
-
- "Elapsed time: 0.226162 msecs"
- 82575360
-
- (time (product my-transducer (range 15)))
- "Elapsed time: 0.154372 msecs"
- 82575360
- ```
-
- Как видно из примеров, трансдьюсеры в результате оказались чуть быстрее чем при обычной обработке, однако пришлось писать чуть больше кода и знать некоторые особенности.
-
- Если же подводить итоги, то трансдьюсеры, так же как и макросы, являются инструментом, который подходит в одних случаях и не подходит в других. В случаях, если требуется эффективная обработка больших коллекций, то без трансдьюсеров никак, однако если коллекции сравнительно небольшие, то стоит воспользоваться классической тройкой. Еще одна особенность, что трансдьюсеры дают чуть больший контроль над обработкой. Ну и если нужно сразу вычислить что-либо без учета ленивости, то трансдьюсеры тут тоже подходят.
-
-instructions: |
- Для закрепления, создайте три трансдьюсера, `student-names` - который извлекает из вектора первый элемент, представляющей собой имя/фамилию студента, `lower-case-name` - который переводит имена/фамилии студентов в нижний регистр, `slugify-names` - который меняет разделитель имени/фамилии студента с пробела на `-`. А затем скомбинируйте эти трансдьюсеры в один `do-name-magic`.
-
- Пример:
-
- ```clojure
- (def students
- [["Luke Skywalker" "Jedi"]
- ["Hermione Granger" "Magic"]
- ["Walter White" "Chemistry"]])
-
- (into [] do-name-magic students)
- ["luke-skywalker" "hermione-granger" "walter-white"]
- ```
-
-tips:
- - |
- [Выступление Рича Хикки про трансдьюсеры](https://www.youtube.com/watch?v=6mTbuzafcII)
- - |
- [Пост Рича Хикки про трансдьюсеры](https://cognitect.com/blog/2014/8/6/transducers-are-coming)
- - |
- [Когда стоит использовать трансдьюсеры](https://clojure.org/guides/faq#transducers_vs_seqs)
- - |
- [Официальная документация](https://clojure.org/reference/transducers)
- - |
- [Полезное про стратегии вычислений](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%B0%D1%82%D0%B5%D0%B3%D0%B8%D1%8F_%D0%B2%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F)
- - |
- [Про трансдьюсеры на примере JS, 1](https://habr.com/ru/post/325388/)
- - |
- [Про трансдьюсеры на примере JS, 2](https://habr.com/ru/post/237613/)
diff --git a/modules/35-vectors/60-transducers-creation/ru/data.yml b/modules/35-vectors/60-transducers-creation/ru/data.yml
index cbd16a5..059794b 100644
--- a/modules/35-vectors/60-transducers-creation/ru/data.yml
+++ b/modules/35-vectors/60-transducers-creation/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Создание трансдьюсеров
tips:
- >
diff --git a/modules/40-hashes/10-intro-hashes/description.ru.yml b/modules/40-hashes/10-intro-hashes/description.ru.yml
deleted file mode 100644
index 1f73b40..0000000
--- a/modules/40-hashes/10-intro-hashes/description.ru.yml
+++ /dev/null
@@ -1,53 +0,0 @@
----
-
-name: О хеш-мапах
-theory: |
- В Clojure, как и в остальных языках используются хеш-мапы или как их еще называют словарями или ассоциативными массивами. Хеш-мапы в Clojure тоже иммутабельны. Для создания хеш-мапы ключ и значение записываются в фигурных скобках `{key value}`. Важно, что в создаваемом словаре каждому ключу записывается соответствующее значение, иначе возникнет синтаксическая ошибка. Теперь рассмотрим примеры:
-
- ```clojure
- (def my-hash
- {:a 1
- :b "2"
- :c (fn [a b] (+ a b)) ; анонимная функция
- "val" 12.2
- 7 "b"})
-
- (def wrong-hash {"a"}) ; у ключа хеш-мапы всегда должно быть значение
- Syntax error reading source at (REPL:1:22).
- Map literal must contain an even number of forms
-
- ; извлечем элемент
- (my-hash :a) ; => 1
- (my-hash :b) ; => "2"
- (my-hash 7) ; => "b"
-
- ; извлечем анонимную функцию и тут же вызовем её
- ((my-hash :c) 2 3) ; => 5
-
- ; добавим элемент
- (assoc {:a "b"} "c" 4)
- ; => {:a "b" "c" 4}
-
- ; уберем элемент
- (dissoc {:a "b"} :a)
- ; => {}
- ```
-
-instructions: |
- Создайте функцию `resolve`, которая извлекает из хеш-мапы доменов IP, связанный с именем домена. Если такая запись отсутствует, то верните `"DNS_PROBE_FINISHED_NXDOMAIN"`.
- Примеры:
-
- ```clojure
- (resolve {"rubyonrails.org" "211.116.107.5" "clojure.org" "103.95.84.1" "phoenixframework.org" "234.214.199.63" "reactjs.org" "20.199.101.214"}
- "clojure.org")
- ; => "103.95.84.1"
- (resolve {"rhythm.ru" "201.116.147.4" "building.ru" "103.176.11.27" "hexlet.io" "234.214.199.63" "brass.ru" "201.116.147.4"}
- "hexlet.io")
- ; => "234.214.199.63"
- (resolve {"some.com" "127.0.0.1"} "test.net")
- ; => "DNS_PROBE_FINISHED_NXDOMAIN"
- ```
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/hash-map)
diff --git a/modules/40-hashes/10-intro-hashes/ru/data.yml b/modules/40-hashes/10-intro-hashes/ru/data.yml
index d4eaed3..d7fc02d 100644
--- a/modules/40-hashes/10-intro-hashes/ru/data.yml
+++ b/modules/40-hashes/10-intro-hashes/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: О хеш-мапах
tips:
- |
diff --git a/modules/40-hashes/20-more-hashes/description.ru.yml b/modules/40-hashes/20-more-hashes/description.ru.yml
deleted file mode 100644
index c7b63e9..0000000
--- a/modules/40-hashes/20-more-hashes/description.ru.yml
+++ /dev/null
@@ -1,69 +0,0 @@
----
-
-name: Еще немного о хешах
-theory: |
- В Clojure с помощью хеш-мап коду легко задать полиморфное поведение. Хотя это и не единственный способ, но такой подход работает:
-
- ```clojure
- ; Создадим простой калькулятор с помощью хеш-мапы
- (defn calc [first-num second-num operation]
- (let [operations-map {"+" +
- "-" -
- "*" *
- "^" (fn [a b] (int (Math/pow a b)))}]
- ((operations-map operation) first-num second-num)))
-
- (calc 2 3 "+") ; => 5
- (calc 2 3 "-") ; => -1
- (calc 2 3 "*") ; => 6
- (calc 2 3 "^") ; => 8
- ```
-
- В языке реализованы функции по работе с вложенными словарями, например, обращение к вложенным элементам словаря.
-
- ```clojure
- ; создадим вложенный хеш
- (def company
- {:name "WidgetCo"
- :address {:street "123 Main St"
- :city "Springfield"
- :state "IL"}})
-
- ; обратимся к вложенному хешу
- (get-in company [:address :state])
- "IL"
-
- ; теперь обновим вложенный хеш
- (assoc-in company [:address :street] "303 Broadway")
-
- ; => {:name "WidgetCo",
- ; => :address {:state "IL",
- ; => :city "Springfield",
- ; => :street "303 Broadway"}}
-
- (def scores {"Andrew" 100 "Angela" 200})
- (merge scores {"Kim" 150 "Ivan" 200})
- ; => {"Andrew" 100, "Angela" 200, "Kim" 150, "Ivan" 200}
- ```
-
-instructions: |
- Создайте функцию `freq`, которая принимает коллекцию элементов и создает хеш-мапу с частотой появления элементов в коллекции. Больше подробностей в примерах.
-
- ```clojure
- (freq ["a" "b" "c" "a" "a" "c" "a" "d" "b"])
- ; => {"a" 4, "b" 2, "c" 2, "d" 1}
-
- (freq [])
- ; => {}
-
- (freq ["Clojure" "Ruby" "Clojure" "Elixir" "Ruby" "HTML" "JS"])
- ; => {"Clojure" 2, "Ruby" 2, "Elixir" 1, "HTML" 1, "JS" 1}
-
- (freq [10 10 10 20 300 41 53])
- ; => {10 3, 20 1, 300 1, 41 1, 53 1}
-
- (freq [:a :b :c :d :a :a])
- ; => {:a 3, :b 1, :c 1, :d 1}
- ```
-
-tips: []
diff --git a/modules/40-hashes/20-more-hashes/ru/data.yml b/modules/40-hashes/20-more-hashes/ru/data.yml
index 853d5e6..b2c440d 100644
--- a/modules/40-hashes/20-more-hashes/ru/data.yml
+++ b/modules/40-hashes/20-more-hashes/ru/data.yml
@@ -1,2 +1,3 @@
+---
name: Еще немного о хешах
tips: []
diff --git a/modules/40-hashes/30-sorted-maps/description.ru.yml b/modules/40-hashes/30-sorted-maps/description.ru.yml
deleted file mode 100644
index a0e2c0b..0000000
--- a/modules/40-hashes/30-sorted-maps/description.ru.yml
+++ /dev/null
@@ -1,51 +0,0 @@
----
-
-name: Сортированные хеш-мапы
-theory: |
- Помимо стандартных хеш-мап в Clojure реализованы сортированные хеш-мапы. Семантика соответствует названию. Ключи в такой хеш-мапе будут отсортированы с помощью встроенной функции `compare`, поведение которой описано в ссылке к этому уроку.
-
- Задается сортированная хеш-мапа с помощью функции `sorted-map`. Для изменения поведения сортировки используется функция `sorted-map-by`.
-
- ```clojure
- ; объявление сортированной хеш-мапы
- (sorted-map :c 0 :a 28 :b 35)
- {:a 28, :b 35, :c 0}
-
- ; если ключи повторяются, то будет выбран последний
- (sorted-map :c 0 :a 28 :a 35 :c 2)
- {:a 35, :c 2}
-
- ; важно отметить, что ключи должны быть одинакового типа, иначе функция `compare` не сможет их сравнить
- (sorted-map :с 0 "a" 28, 2 35)
- Execution error (ClassCastException) at java.lang.String/compareTo (String.java:134).
- class clojure.lang.Keyword cannot be cast to class java.lang.String (clojure.lang.Keyword is in unnamed module of loader 'bootstrap'; java.lang.String is in module java.base of loader 'bootstrap')
-
- ; теперь создадим сортированную хеш-мапу с пользовательской функцией-компаратором
- (sorted-map-by > 1 "a" 2 "b" 3 "c")
- {3 "c", 2 "b", 1 "a"}
- ```
-
-instructions: |
- Создайте функцию `to-sorted-map`, которая конвертирует обычную хеш-мапу в сортированную.
-
- ```clojure
- (to-sorted-map {3 :c 2 :b 1 :a})
- ; => {1 :a, 2 :b, 3 :c}
-
- (to-sorted-map {})
- ; => {}
-
- (to-sorted-map {"c" 3 "b" 2 "a" 1})
- ; => {"a" 1, "b" 2, "c" 3}
-
- (to-sorted-map {:c 3, :b 2, :a 1})
- ; => {:a 1, :b 2, :c 3}
- ```
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/sorted-map)
- - |
- [Функция compare, которая сортирует ключи](https://clojuredocs.org/clojure.core/compare)
- - |
- [Sorted-map-by официальная документация](https://clojuredocs.org/clojure.core/sorted-map-by)
diff --git a/modules/40-hashes/30-sorted-maps/ru/data.yml b/modules/40-hashes/30-sorted-maps/ru/data.yml
index 10c9df3..e96916d 100644
--- a/modules/40-hashes/30-sorted-maps/ru/data.yml
+++ b/modules/40-hashes/30-sorted-maps/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Сортированные хеш-мапы
tips:
- |
diff --git a/modules/45-state/10-about-state/description.ru.yml b/modules/45-state/10-about-state/description.ru.yml
deleted file mode 100644
index a91139f..0000000
--- a/modules/45-state/10-about-state/description.ru.yml
+++ /dev/null
@@ -1,53 +0,0 @@
----
-
-name: О состоянии
-theory: |
- Хранение и обработка состояния всегда было краеугольным камнем в программировании. Существуют целые фреймворки по работе с состоянием (например, Redux во фронтенде), дополнительные механизмы в языках программирования (горутины, акторная модель в Elixir), даже в операционных системах есть такие инструменты, например, семафоры. Все эти инструменты позволяют организовать конкурентный доступ к ресурсу (его состоянию), для чтения и изменения каким-либо образом. В Clojure реализован механизм MVCC (multiversion concurrency control), который часто используется в базах данных, его основная идея заключается в предоставлении каждому пользователю так называемого «снимка» базы, обладающего тем свойством, что вносимые пользователем изменения невидимы другим пользователям до момента фиксации транзакции. Этот способ управления позволяет добиться того, что пишущие транзакции не блокируют читающих, и читающие транзакции не блокируют пишущих. Для создания транзакционной ссылки (refs) используется функция `atom`, для модификации ссылок используются функции `swap!` и `reset!`, для чтения значения, хранящегося по ссылке, используется `deref` или `@`, рассмотрим несколько примером:
-
- ```clojure
- (def resource (atom 10)) ; объявляем транзакционную ссылку
- ; попробуем прочесть значение, которое хранится в resource
-
- (deref resource) ; => 10
- @resource ; => 10
- ; оба варианта подходят для чтения транзакционных ссылок
- ; теперь попробуем внести изменения
-
- (swap! resource + 10) ; прибавляем 10
- ; => 20
- @resource ; => 20
-
- (swap! resource - 5) ; отнимем 5
- ; => 15
- (deref resource) ; => 15
- ; теперь перезапишем значение, которое хранится по ссылке
- (reset! resource -1) => -1
- @resource ; => -1
-
- (reset! resource 12) ; => 12
- (deref resource) ; => 12
- ```
-
- Как видно из примеров, функция `swap!` требует атом, функцию модификатор (обычная функция, более сложные модификации рассмотрим чуть позже) и значение для функции модификатора. Для функции `reset!` достаточно атома и значения, на которое оно заменится. Удобство в том, что эти функции потокобезопасны и транзакционны, если произойдет ошибка, то транзакция откатится.
-
-instructions: |
- Реализуйте функцию `transit`, которая принимает два атома. Атомы представляют счета в банках и число денег, которое нужно перевести с первого на второй аккаунт, в результате выполнения функции, верните счета в виде вектора. Больше подробностей в примерах.
-
- ```clojure
- (transit (atom 100) (atom 20) 20)
- ; => [80 40]
- (transit (atom 50) (atom 30) 50)
- ; => [0 80]
- ```
-
-tips:
- - |
- [Про язык Elixir](https://elixir-lang.org/)
- - |
- [Про MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)
- - |
- [Официальная документация](https://clojure.org/reference/atoms)
- - |
- [Про ссылки и транзакции](https://clojure.org/reference/refs)
- - |
- [Software Transactional Memory, используемая в Clojure как механизм работы с состоянием](https://en.wikipedia.org/wiki/Software_transactional_memory)
diff --git a/modules/45-state/10-about-state/ru/data.yml b/modules/45-state/10-about-state/ru/data.yml
index 892d152..435978c 100644
--- a/modules/45-state/10-about-state/ru/data.yml
+++ b/modules/45-state/10-about-state/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: О состоянии
tips:
- |
diff --git a/modules/45-state/20-atoms-validation/description.ru.yml b/modules/45-state/20-atoms-validation/description.ru.yml
deleted file mode 100644
index 41c423d..0000000
--- a/modules/45-state/20-atoms-validation/description.ru.yml
+++ /dev/null
@@ -1,33 +0,0 @@
----
-
-name: Валидация атомов
-theory: |
- Еще одной из удобных возможностей атомов — валидация. Рассмотрим на примерах:
-
- ```clojure
- (def my-atom (atom 0 :validator even?)) ; создаем атом, который валидирует свое состояние на четность
-
- @my-atom ; => 0
-
- (swap! my-atom inc) ; увеличиваем значение атома на единицу
- IllegalStateException Invalid reference state clojure.lang.ARef.validate (ARef.java:33)
-
- (swap! my-atom (partial + 2)) ; увеличим атом на 2
-
- @my-atom ; => 2
- ```
-
- Как видно из примера, при попытке изменить атом на некорректное значение, бросается исключение об ошибке изменения. Функция-валидатор может быть какой угодно, главное, чтобы она принимала один аргумент, само значение атома, а возвращать `boolean` или выбрасывать исключение, которое описывает ошибку валидации (исключения в Clojure обрабатываются так же, как и в большинстве других языков).
-
-instructions: |
- Реализуйте функцию-валидатор `vec-even?`, которая проверяет, что атом является вектором и все его элементы четные (пустой вектор не является валидным случаем).
-
- ```clojure
- (vec-even? []) ; => false
- (vec-even? [0 2 4 6]) ; => true
- (vec-even? [1 3 5]) ; => false
- ```
-
-tips:
- - |
- [Официальная документация](https://clojure.org/reference/atoms)
diff --git a/modules/45-state/20-atoms-validation/ru/data.yml b/modules/45-state/20-atoms-validation/ru/data.yml
index 2a16c58..28959f2 100644
--- a/modules/45-state/20-atoms-validation/ru/data.yml
+++ b/modules/45-state/20-atoms-validation/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Валидация атомов
tips:
- |
diff --git a/modules/45-state/30-about-agents/description.ru.yml b/modules/45-state/30-about-agents/description.ru.yml
deleted file mode 100644
index f5c555f..0000000
--- a/modules/45-state/30-about-agents/description.ru.yml
+++ /dev/null
@@ -1,67 +0,0 @@
----
-
-name: Агенты
-theory: |
- Теперь поговорим про агентов. Что их отличает от атомов? Дело в том, что любое изменение состояния атома является `синхронным`, в то время как состояние агента меняется `асинхронно`. В остальном агенты похожи на атомы, в них можно добавлять валидации, настраивать обработку ошибок.
-
- Рассмотрим несколько примеров:
-
- ```clojure
- (def my-agent (agent 0))
-
- @my-agent ; => 0
-
- (send my-agent inc)
- #object[clojure.lang.Agent 0x59c25f30 {:status :ready, :val 1}]
-
- @my-agent ; => 1
-
- (send my-agent dec)
- #object[clojure.lang.Agent 0x59c25f30 {:status :ready, :val 1}]
-
- @my-agent ; => 0
- ```
-
- Как видно из в последнем примере, при попытке уменьшить значение агента на единицу, вернулось состояние агента с неизмененным значением, затем мы извлекли состояние агента вручную, однако в нем уже хранился `0`, почему? Как упоминалось выше, изменения к состоянию агента применяются `асинхронно`, важно помнить эту особенность, при работе с ними. Если же нужно получить значение агента после обновления, то можно использовать функции `await` и `await-for`.
-
- Рассмотрим еще пару примеров с обработкой ошибок в агентах:
-
- ```clojure
- (def broken-agent (agent 0))
-
- (send broken-agent (fn [_] (throw
- (Exception. "Houston we have a problem!"))))
- #object[clojure.lang.Agent 0xd76bd8c {:status :ready, :val 0}]
-
- (send broken-agent inc)
- Execution error at user/eval2175$fn (REPL:1).
- Houston we have a problem!
-
- ; Теперь настроим агента так, чтобы при возникновении ошибок процесс изменения не прерывался
-
- (def not-broken-agent (agent 0 :error-mode :continue))
-
- (send not-broken-agent (fn [_] (throw
- (Exception. "Houston we have one more problem!!!"))))
- #object[clojure.lang.Agent 0x44b27f64 {:status :ready, :val 0}]
-
- (send not-broken-agent inc)
- #object[clojure.lang.Agent 0x44b27f64 {:status :ready, :val 0}]
-
- ; ошибки не возникло!
- @not-broken-agent ; => 1
- ```
-
-instructions: |
- Реализуйте функцию `transit`, которая ведет себя так же, как в упражнении с атомами, только с помощью агентов. Функция принимает два агента. Агенты представляют счета в банках и число денег, которое нужно перевести с первого на второй аккаунт, в результате выполнения функции, верните счета в виде вектора (помните, изменения в агентах применяются асинхронно!).
-
- ```clojure
- (transit (agent 100) (agent 20) 20)
- ; => [80 40]
- (transit (agent 50) (agent 30) 50)
- ; => [0 80]
- ```
-
-tips:
- - |
- [Официальная документация](https://clojure.org/reference/agents)
diff --git a/modules/45-state/30-about-agents/ru/data.yml b/modules/45-state/30-about-agents/ru/data.yml
index 8102044..78e8c7d 100644
--- a/modules/45-state/30-about-agents/ru/data.yml
+++ b/modules/45-state/30-about-agents/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Агенты
tips:
- |
diff --git a/modules/45-state/40-about-watchers/description.ru.yml b/modules/45-state/40-about-watchers/description.ru.yml
deleted file mode 100644
index 49ad8fe..0000000
--- a/modules/45-state/40-about-watchers/description.ru.yml
+++ /dev/null
@@ -1,46 +0,0 @@
----
-
-name: Наблюдатели
-theory: |
- Помимо атомов и агентов, в Clojure существуют `наблюдатели`, их основное использование заключается в наблюдении за изменяемым ресурсом (издалека напоминает супервизоров из Elixir/Erlang, но более простых).
-
- Рассмотрим несколько примеров:
-
- ```clojure
- (def my-atom (atom 0))
-
- ; добавим наблюдателя для нашего атома, у наблюдателя должно быть уникальное имя
- (add-watch my-atom "my-watcher"
- ; обратите внимание на аргументы, которые передаются в функцию, вызываемую наблюдателем при изменении состояния
- (fn [key variable old-state new-state]
- (println key variable old-state new-state)))
- #object[clojure.lang.Atom 0x673d073a {:status :ready, :val 0}]
-
- (swap! my-atom inc)
- my-watcher #object[clojure.lang.Atom 0x673d073a {:status :ready, :val 1}] 0 1
- 1
-
- (swap! my-atom (fn [state] (* state 4)))
- my-watcher #object[clojure.lang.Atom 0x673d073a {:status :ready, :val 4}] 1 4
- 4
-
- (swap! my-atom (fn [state] (* state 4)))
- my-watcher #object[clojure.lang.Atom 0x673d073a {:status :ready, :val 16}] 4 16
- 16
-
- ; теперь уберем наблюдателя
- (remove-watch my-atom "my-watcher")
- #object[clojure.lang.Atom 0x673d073a {:status :ready, :val 16}]
-
- (swap! my-atom inc)
- 17
- ```
-
- Наблюдатели позволяют упростить процесс логирования и отладки и сделать жизнь разработчика чуть легче, однако, никто не мешает добавить в функцию наблюдателя какие-либо внешние мутации (например, мутация другого ресурса), но лучше так не делать :)
-
-instructions: |
- Создайте атом (начальное значение 0) и добавьте к нему наблюдателя, который при изменении атома выводит сообщение (с помощью `print`) в следующем виде `Change state from x to y.`, где `x` — прошлое состояние атома, а `y` — новое состояние атома. Затем дважды увеличьте атом на 1, потом уменьшите его значение на 1.
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/add-watch)
diff --git a/modules/45-state/40-about-watchers/ru/data.yml b/modules/45-state/40-about-watchers/ru/data.yml
index 7a41a22..87c8fde 100644
--- a/modules/45-state/40-about-watchers/ru/data.yml
+++ b/modules/45-state/40-about-watchers/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Наблюдатели
tips:
- |
diff --git a/modules/50-polymorphism/10-intro-polymorphism/description.ru.yml b/modules/50-polymorphism/10-intro-polymorphism/description.ru.yml
deleted file mode 100644
index 87c5111..0000000
--- a/modules/50-polymorphism/10-intro-polymorphism/description.ru.yml
+++ /dev/null
@@ -1,119 +0,0 @@
----
-
-name: О полиморфизме
-theory: |
- Как уже ранее говорилось, получить полиморфное поведение в Clojure достаточно просто:
-
- ```clojure
- ; Создадим простой калькулятор с помощью хеш-мапы
- (defn calc [first-num second-num operation]
- (let [operations-map {"+" +
- "-" -
- "*" *
- "^" (fn [a b] (int (Math/pow a b)))}]
- ((operations-map operation) first-num second-num)))
-
- (calc 2 3 "+") ; => 5
- (calc 2 3 "-") ; => -1
- (calc 2 3 "*") ; => 6
- (calc 2 3 "^") ; => 8
- ```
-
- Однако! В Clojure есть еще несколько механизмов, для достижения такого же эффекта, с помощью `defmulti` и `defmethod`. Рассмотрим пример:
-
- ```clojure
- ; Определим мультиметод
- (defmulti greeting
- ; в методе мы извлекаем из хеш-мапы значение под ключем `language`
- (fn [x] (get x "language")))
-
- ; Определим методы для мультиметода, значение,
- ; которое будет передано в него и будет нашим ключем диспетчеризации
- (defmethod greeting "English" [_]
- "Hello!")
-
- (defmethod greeting "Russian" [_]
- "Привет!")
-
- ; Создадим хеш-мапы для проверки наших методов
- (def en {"id" "1" "language" "English"})
- (def ru {"id" "2" "language" "Russian"})
-
- (greeting en)
- ; => "Hello!"
-
- (greeting ru)
- ; => "Привет!"
- ```
-
- Как было описано выше, для регистрации методов для мультиметода нам необходимо какое-либо значение, по которому мы будем производить диспетчеризацию.
-
- ```clojure
- ; Попробуем вызвать метод на значении, которое еще не зарегистрировано
- (def de {"id" "3" "language" "Hallo"})
-
- (greeting de)
- Execution error (IllegalArgumentException) at user/eval2502 (REPL:1).
- No method in multimethod 'greeting' for dispatch value: Hallo
- ```
-
- При попытке вызвать мультиметод на незарегистрированном значении возникает ошибка, однако с помощью опции `:default` можно подстраховаться и обработать такую ситуацию.
-
- ```clojure
- ; Создадим метод по умолчанию, если не найдено подходящего
- ; Важно, мы передаем в качестве значения символ :default !
- (defmethod greeting :default [_]
- "Incorrect language provided!")
-
- (greeting de)
- ; => "Incorrect language provided!"
- ```
-
- Важно отметить, что мультиметод может ориентироваться не только на переданное значение, но и тип (то есть класс) передаваемого значения:
-
- ```clojure
- ; Создаем мультиметод, который производит
- ; диспетчеризацию по переданному типу (классу)
- (defmulti hey class)
-
- (defmethod hey Long [_] "You passed an integer!")
-
- (defmethod hey String [x] (str "You passed a string: " x " !"))
-
- (defmethod hey clojure.lang.PersistentVector [_]
- "You passed a vector!")
-
- (defmethod hey :default [_] "Ooops, unknown value!")
-
- (hey 2)
- ; => "You passed an integer!"
-
- (hey "my awesome string")
- ; => "You passed a string: my awesome string !"
-
- (hey [1 2])
- ; => "You passed a vector!"
-
- (hey {:some "value"})
- ; => "Ooops, unknown value!"
- ```
-
-instructions: |
- Создайте калькулятор (конечно же с помощью `defmulti` и `defmethod`), который поддерживает сложение, вычитание и умножение, порядок передачи аргументов приведен в примерах (помните, чтобы мультиметод сработал, ему необходимо получить значение, по которому будет производиться диспетчеризация. Назовите мультиметод `my-calc`.
-
- ```clojure
- (my-calc ["+" 1 2])
- ; => 3
-
- (my-calc ["-" 3 1])
- ; => 2
-
- (my-calc ["*" 3 3])
- ; => 9
- ```
-
-tips:
- - |
- [Официальная документация](https://clojure.org/reference/multimethods)
- - |
- [Большая статья про полиморфизм](https://medium.com/devschacht/polymorphism-207d9f9cd78)
diff --git a/modules/50-polymorphism/10-intro-polymorphism/ru/data.yml b/modules/50-polymorphism/10-intro-polymorphism/ru/data.yml
index a653172..6647f7f 100644
--- a/modules/50-polymorphism/10-intro-polymorphism/ru/data.yml
+++ b/modules/50-polymorphism/10-intro-polymorphism/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: О полиморфизме
tips:
- |
diff --git a/modules/50-polymorphism/20-protocols-and-records/description.ru.yml b/modules/50-polymorphism/20-protocols-and-records/description.ru.yml
deleted file mode 100644
index faa40cb..0000000
--- a/modules/50-polymorphism/20-protocols-and-records/description.ru.yml
+++ /dev/null
@@ -1,59 +0,0 @@
----
-
-name: О протоколах
-theory: |
- Есть еще один вариант, для получения полиморфного поведения, используется он в основном, когда скорость полиморфных функций начинает бить по рукам (так бывает крайне редко), когда хочется избежать `expression problem` (возможность расширять набор операций над типами данных без изменений определения типов данных), либо необходимо выстроить высокоуровневые абстракции для сложных типов и операций над ними (главное не переусердствовать). Для создания протокола нам потребуется две функции — `defprotocol`, которая отвечает за генерацию протокола и `defrecord`, которая создает класс JVM.
-
- Рассмотрим пример:
-
- ```clojure
- ; Определим протокол, обратите внимание, что
- ; реализовывать протокол нет необходимости, достаточно определения
- (defprotocol Fly
- (fly [this] "Method to fly"))
-
- ; Теперь создадим класс Bird, в котором определим поведение протокола
- (defrecord Bird [name]
- Fly
- (fly [this] (str (:name this) " flies...")))
-
- ; Проверим, наследует ли запись протокол
- (extends? Fly Bird)
- ; => true
-
- ; Создадим инстанс класса и свяжем его с идентификатором
- (def crow (Bird. "Crow"))
-
- ; И вызовем метод, который определили
- (fly crow)
- ; => "Crow flies..."
-
- ; Определим еще один класс со своим методом, для закрепления
- (defrecord Plane [name]
- Fly
- (fly [this] (str (:name this) " flew away!")))
-
- (def plane (Plane. "Hawker Hurricane"))
-
- (fly plane)
- ; => "Hawker Hurricane flew away!"
- ```
-
- Clojure действительно гибкий язык, в котором можно комбинировать различные подходы, однако некоторые из них (речь про протоколы), вносят усложнения в код, но при правильном применении можно получить абстракции, с которыми удобно работать. В случае с протоколами, выбор в их сторону должен быть обоснован, так как Clojure в целом пропагандирует подход к стремлению делать как можно проще (опять же, сам создатель языка выступал с докладом Simple Made Easy).
-
-instructions: |
- Создайте протокол `SaysSomething`, затем определите три класса `Human`, `Dog` и `Cat` (такой пример подобран специально :)). В каждом классе определите метод `say-something` с выводом в консоль (с помощью `println`) следующей строки:
-
- - Для Human `Hello, World!`
- - Для Cat `Meow, World!`
- - Для Dog `Bark, World!`
-
- Поочередно создайте инстанс каждого класса и вызовите у них метод `say-something`.
-
-tips:
- - |
- [Про Expression Problem](https://en.wikipedia.org/wiki/Expression_problem)
- - |
- [Подкаст про Expression Problem](https://soundcloud.com/mimpod/episode_61)
- - |
- [Simple Made Easy](https://www.youtube.com/watch?v=SxdOUGdseq4)
diff --git a/modules/50-polymorphism/20-protocols-and-records/ru/data.yml b/modules/50-polymorphism/20-protocols-and-records/ru/data.yml
index cd64bea..bc99041 100644
--- a/modules/50-polymorphism/20-protocols-and-records/ru/data.yml
+++ b/modules/50-polymorphism/20-protocols-and-records/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: О протоколах
tips:
- |
diff --git a/modules/55-macros/10-intro-macros/description.ru.yml b/modules/55-macros/10-intro-macros/description.ru.yml
deleted file mode 100644
index 2386d1f..0000000
--- a/modules/55-macros/10-intro-macros/description.ru.yml
+++ /dev/null
@@ -1,106 +0,0 @@
----
-
-name: О макросах
-theory: |
- Lisp-подобные языки, в том числе Clojure, всегда славились своими макросами, которые позволяют расширять выразительность языка, позволяя программировать в терминах предметной области. По сути с помощью макросов мы программируем язык программирования! Этот процесс называется метапрограммированием.
-
- Начнем, как обычно, с примеров:
-
- ```clojure
- (defn identity-fn [x] x)
-
- (defmacro identity-macro [x] x)
- ; Проверим, как ведут себя макросы в сравнении с обычными функциями
-
- (identity-fn 2) ; => 2
- (identity-macro 2) ; => 2
-
- (identity-fn (+ 1 2)) ; => 3
- (identity-macro (+ 1 2)) ; => 3
- ```
-
- Пока что макросы ведут себя так же, как и обычные функции, попробуем другой пример:
-
- ```clojure
- (defn identity-fn [x]
- (println "identity of x")
- x)
-
- (defmacro identity-macro [x]
- (println "identity of x")
- x)
-
- (identity-fn 10)
- ; => identity of x
- ; => 10
-
- (identity-macro 10)
- ; => identity of x
- ; => 10
-
- ; Пока что, все идентично, посмотрим дальше...
-
- (identity-fn (println "Im nil"))
- ; => Im nil
- ; => identity of x
- ; => nil
-
- (identity-macro (println "Im nil"))
- ; => identity of x
- ; => Im nil
- ; => nil
-
- ; Хм, а здесь получился другой порядок
- ; Макрос себя ведет примерно как такая функция
- (defn identity-macro-fn []
- (println "identity of x")
- (println "Im nil"))
-
- (identity-macro-fn)
- ; => identity of x
- ; => Im nil
- ; => nil
-
- ; Попробуем разобрать макрос с помощью функции macroexpand-1
- (macroexpand-1 '(identity-macro (println "Im nil")))
- ; => identity of x
- ; => (println "Im nil")
- ; Вывод очень напоминает описание функции identity-macro-fn
- ```
-
- Подведем итоги и сделаем вывод.
-
- Шаги выполнения функции `identity-fn`:
-
- - Аргумент `(println "Im nil")` выполняется первым, срабатывает побочный эффект вывода строки;
- - Результат выполнения `(println "Im nil")` т.е. `nil` передается в `identity-fn`;
- - `identity-fn` затем выполняет это `(println "identity of x")`, после чего срабатывает побочный эффект;
- - В итоге возвращается значение, переданное в функцию, т.е. `nil`.
-
- Шаги выполнения макроса `identity-macro`:
-
- - `(println "Im nil")` НЕ выполняется и передается напрямую в тело макроса;
- - Макрос возвращает тело, которое похоже на такой вызов функций:
-
- ```clojure
- (do
- (println "identity of x")
- (println "Im nil"))
- ```
-
- - Затем Clojure выполняет полученное тело, вызвав сначала первый `println` и напечатав `"identity of x"`;
- - Дальше вызывается второй `println` и печатается текст `"Im nil"`;
- - В итоге, возвращается nil.
-
- Получилось объемно! Но это позволяет нам сформулировать первое правило макросов - Аргументы не выполняются перед тем, как были отправлены в тело макроса. Продолжим рассматривать макросы в следующем упражнении.
-
-instructions: |
- Для закрепления, создайте функцию и макрос (названия не важны), которые печатают на экран (с помощью `println`) "Hello from fn!" и "Hello from macro!" соответственно. Поочередно вызовите их с таким аргументом: `(println (+ 1 2))`.
-
-tips:
- - |
- [Официальная документация](https://clojure.org/reference/macros)
- - |
- [Еще немного о макросах](https://www.braveclojure.com/writing-macros/)
- - |
- [Про метапрограммирование](https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%B0%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
diff --git a/modules/55-macros/10-intro-macros/ru/data.yml b/modules/55-macros/10-intro-macros/ru/data.yml
index f867322..d76ea1d 100644
--- a/modules/55-macros/10-intro-macros/ru/data.yml
+++ b/modules/55-macros/10-intro-macros/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: О макросах
tips:
- |
diff --git a/modules/55-macros/20-macro-rules/description.ru.yml b/modules/55-macros/20-macro-rules/description.ru.yml
deleted file mode 100644
index 0f5b00a..0000000
--- a/modules/55-macros/20-macro-rules/description.ru.yml
+++ /dev/null
@@ -1,65 +0,0 @@
----
-
-name: Правила макросов
-theory: |
- Теперь посмотрим, как выполняется код внутри макросов:
-
- ```clojure
- (defmacro id-mac [x]
- "Hello, macro!"
- x)
-
- (macroexpand-1 '(id-mac (println "str")))
- ; => (println "str")
- ```
-
- Как видно из примера, строка, `"Hello, macro!"` нигде не появилась, следовательно, как и в обычной функции, возвращается последняя форма. Вспомним макрос из прошлого упражнения:
-
- ```clojure
- (defmacro identity-macro [x]
- (println "identity of x")
- x)
- ```
-
- Как мы уже выяснили, когда мы вызываем `identity-macro`, форма `(println "identity of x")` вызывается до возвращения последней формы, что еще раз подтверждает, что тело макроса выполняется так же как и в обычной функции. Это будет вторым правилом макросов: Тело макросов выполняется в соответствии с обычными правилами Clojure.
-
- Посмотрим еще несколько функций и макросов:
-
- ```clojure
- (defn triplet-fn [a b c]
- (list a b c))
-
- (defmacro triplet-macro [a b c]
- (list a b c))
-
- (triplet-fn 1 2 3)
- ; => (1 2 3)
-
- (triplet-macro 1 2 3)
- ; => java.lang.Exception: Cannot call 1 as a function.
-
- ; Что-то пошло не так, посмотрим, во что макрос разворачивается
-
- (macroexpand '(triplet-macro 1 2 3))
- ; => (1 2 3)
-
- ; Хм, кажется все начинает сходиться, попробуем вызвать макрос напрямую
- (eval (macroexpand '(triplet-macro 1 2 3)))
- ; => java.lang.Exception: Cannot call 1 as a function.
-
- ; Ошибка такая же, как и в примере выше, Clojure пытается
- ; выполнить код (1 2 3), который не является валидной формой
- ```
-
- Пора подводить итоги! Из примера выше можно сформировать еще одно, третье правило макросов: Данные, возвращаемые макросом немедленно вычисляются и результат этого вычисления отдается наружу при вызове этого макроса (третье правило не совсем корректно, но для нынешнего понимания этого пока что достаточно).
-
- Полезно помнить, что возвращаемая форма выполняется дважды, когда вы пишете макрос и единожды, когда пишете обычную функцию. Теперь вспомним еще раз все правила, которые сформулировали:
-
- - Аргументы не выполняются перед тем, как были отправлены в тело макроса;
- - Тело макросов выполняется в соответствии с обычными правилами Clojure;
- - Данные, возвращаемые макросом немедленно вычисляются и результат этого вычисления отдается наружу при вызове этого макроса.
-
-instructions: |
- Исправьте `triplet-macro`, чтобы он работал так же, как и `triplet-fn` (не забывайте третье правило макросов!).
-
-tips: []
diff --git a/modules/55-macros/20-macro-rules/ru/data.yml b/modules/55-macros/20-macro-rules/ru/data.yml
index d6712cb..2c916b8 100644
--- a/modules/55-macros/20-macro-rules/ru/data.yml
+++ b/modules/55-macros/20-macro-rules/ru/data.yml
@@ -1,2 +1,3 @@
+---
name: Правила макросов
tips: []
diff --git a/modules/55-macros/30-data-and-code/description.ru.yml b/modules/55-macros/30-data-and-code/description.ru.yml
deleted file mode 100644
index 71827a9..0000000
--- a/modules/55-macros/30-data-and-code/description.ru.yml
+++ /dev/null
@@ -1,89 +0,0 @@
----
-
-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 "", line 1, in
- 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:
- - |
- [Модуль по спискам](https://ru.code-basics.com/languages/clojure/lessons/intro)
- - |
- [Статья о символе '](https://8thlight.com/blog/colin-jones/2012/05/22/quoting-without-confusion.html)
- - |
- [Еще немного о макросах](https://www.braveclojure.com/writing-macros/)
diff --git a/modules/55-macros/30-data-and-code/ru/data.yml b/modules/55-macros/30-data-and-code/ru/data.yml
index eb17e3f..ed75452 100644
--- a/modules/55-macros/30-data-and-code/ru/data.yml
+++ b/modules/55-macros/30-data-and-code/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Данные как код
tips:
- >
diff --git a/modules/55-macros/40-quote/description.ru.yml b/modules/55-macros/40-quote/description.ru.yml
deleted file mode 100644
index 0b83972..0000000
--- a/modules/55-macros/40-quote/description.ru.yml
+++ /dev/null
@@ -1,118 +0,0 @@
----
-
-name: Цитирование
-theory: |
- Начнем с небольшой задачи, допустим, у нас есть определенный список названий в пространстве имен, которые мы хотим оставить нетронутыми в этом пространстве. Иначе говоря, мы хотим позволить именовать (определять) функции только разрешенными именами.
-
- Сначала проанализируем, какие данные будут переданы в наш макрос. По сути, они будут такими же, как и в форме `defn`, то есть `name`, `args` и `body`.
-
- Классическое объявление функции это `список` - `(defn name args body)`. То есть если мы хотим определить функцию через макрос, мы должны вернуть список, как при определении функции. Единственное отличие, наш макрос `special-defn` должен проверить, находится ли `name` в запрещенном списке имен, перед тем, как объявить функцию в пространстве имен.
-
- Теперь попробуем решить эту задачу:
-
- ```clojure
- ; сохраним в множество строку, которую сконвертирум в символ (потому что в defn название тоже передается символом)
- (def forbidden-list #{(symbol "clojure") (symbol "is") (symbol "bad")})
-
- (defmacro special-defn [name args body]
- (if-not (contains? forbidden-list name)
- (list defn name args body)
- "you can't define this function"))
-
- Syntax error compiling
- Can't take value of a macro: #'clojure.core/defn
- ```
-
- Ошибка возникает из-за того, что Clojure пытается вычислить у символа `defn` его значение. Но так получить значение макроса нельзя, возникает ошибка. Но нам и не нужно значение `defn`. Нам нужно получить список вида: `(defn name args body)`. Познакомимся с оператором цитирования (quote).
-
- Оператор `'` (quote operator), является первым из инструментов, о которых узнает программист, когда сталкивается с макросами. Этот оператор сообщает Clojure пропустить исполнение переданного символа. Рассмотрим пример:
-
- ```clojure
- ; попробуем создать функцию с неопределенным до этого символом
- (defn get-foo [] foo)
- Syntax error compiling at (REPL:1:1).
- Unable to resolve symbol: foo in this context
-
- ; логично, мы попытались вычислить foo и получили ошибку
- ; теперь воспользуемся оператором '
- (defn get-foo [] 'foo)
-
- ; ошибки не возникло, вызовем нашу функцию
- (get-foo)
- foo
- ```
-
- Несмотря на то, что символ `foo` не был связан со значением, функция `get-foo` вернула этот символ (не его значение!), однако при попытке вычислить его значение, все равно возникнет ошибка.
-
- Получается, если нужно сослаться на символ, не вычисляя его значения, нужно использовать оператор `'`.
-
- Если расположить оператор `'` перед выражением, то этот оператор рекурсивно применится к выражению и его подвыражениям. Рассмотрим несколько примеров:
-
- ```clojure
- '(foo bar baz)
- (foo bar baz)
-
- '(foo (bar tavern pub) baz)
- (foo (bar tavern pub) baz)
- ```
-
- Теперь попробуем применить полученные знания к нашей изначальной задаче:
-
- ```clojure
- (def forbidden-list #{(symbol "clojure") (symbol "is") (symbol "bad")})
-
- (defmacro special-defn [name args body]
- (if-not (contains? forbidden-list name)
- '(defn name args body)
- "you can't define this function"))
-
- #'user/special-defn
- ; ура, наш макрос удачно скомпилировался, попробуем теперь его в деле
-
- (special-defn clojure [a] a)
- "you can't define this function"
- ; этот пример работает
-
- (special-defn my-fn [a] a)
- Syntax error macroexpanding clojure.core/defn
- args - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
- args - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n :bodies] spec: :clojure.core.specs.alpha/params+body
-
- ; а этот пример не работает :(
- ```
-
- Мы раньше использовали `macroexpand`, как и `macroexpand-1`. Это очень полезные функции, чтобы выяснить, какой список возвращает макрос. Поэтому воспользуемся ими в очередной раз и разберемся, в чем проблема:
-
- ```clojure
- (macroexpand '(special-defn my-fn [a] a))
- Syntax error macroexpanding clojure.core/defn at ...
- args - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
- args - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n :bodies] spec: :clojure.core.specs.alpha/params+body
- ```
-
- Не очень информативный вывод, тогда воспользуемся `macroexpand-1`:
-
- ```clojure
- (macroexpand-1 '(special-defn my-fn [a] a))
- (defn name args body)
- ```
-
- `defn` выглядит нормально! Но что за `name`, `args` и `body`? Разве они не должны были замениться `my-fn`, `[a]` и `a`?
-
- Список, который мы ожидали должен был быть `(defn my-fn [a] a)`, но макрос вернул символы `name`, `args`, и `body` вместо их значений.
-
- Перед тем, как мы исправим наш макрос, проверьте себя, почему `macroexpand-1` сработал, а `macroexpand` нет? Причина в том, что `macroexpand` рекурсивно вызывает `macroexpand-1`, пока не вернется валидная Clojure форма. Так как мы возвращаем `defn`, а `defn` является макросом, то `macroexpand` пытается развернуть его и вызывает ошибку.
-
-instructions: |
- Вернемся к исправлению макроса `special-defn`. Вспомним, что мы хотим вернуть список, содержащий *символ* `defn` и *значения* `name`, `args` и `body` (подумайте о порядке "цитирования" в итоговом списке).
-
- Затем создайте две функции через полученный макрос:
-
- - `my-sum`, в которую передаются два числа и суммируются;
- - `my-diff`, в которую передаются два числа, а затем из первого вычитается второе число.
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/quote)
- - |
- [Еще немного о макросах](https://www.braveclojure.com/writing-macros/)
diff --git a/modules/55-macros/40-quote/ru/data.yml b/modules/55-macros/40-quote/ru/data.yml
index bfae4dc..7702bda 100644
--- a/modules/55-macros/40-quote/ru/data.yml
+++ b/modules/55-macros/40-quote/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Цитирование
tips:
- |
diff --git a/modules/55-macros/50-unquote/description.ru.yml b/modules/55-macros/50-unquote/description.ru.yml
deleted file mode 100644
index 2c6ff18..0000000
--- a/modules/55-macros/50-unquote/description.ru.yml
+++ /dev/null
@@ -1,60 +0,0 @@
----
-
-name: Вычисления внутри цитирования
-theory: |
- Попробуем решить еще одну задачку, создадим макрос, который принимает вектор чисел, суммирует их, а потом вычитает из суммы этот же вектор. Если бы мы писали функцию, то она выглядела бы так:
-
- ```clojure
- (defn strange-fn [coll]
- (apply - (apply + coll) coll))
-
- (strange-fn [1 2 3 4])
- 0
- ```
-
- Раз мы хотим решить задачу через макрос, то последней формой, которую он будет возвращать, будет символы `apply`, `+` и `-`, поэтому применим оператор цитирования к последней форме, которую возвращаем:
-
- ```clojure
- (defmacro strange-macro [coll]
- `(apply - (apply + coll) coll))
-
- (strange-macro [1 2 3 4])
- Could not resolve symbol: coll
- ```
-
- Ох, нам нужно значение, которое хранится в `coll`, но как же получить это значение?
-
- Для того чтобы подсказать Clojure, что внутри макроса при цитировании нужно вычислить, используется оператор `~` (unquote). Этот оператор используется вместе с оператором цитирования `'`, можно сказать, что он отменяет цитату в процитированном макросе (сложности перевода со словом unquote).
-
- Теперь попробуем на другом примере новый оператор:
-
- ```clojure
- ; получим сумму двух сумм элементов вектора
- (defmacro twice-sum-macro [coll]
- `(+ (apply + ~coll) (apply + ~coll)))
-
- (twice-sum-macro [1 2 3 4])
- 20
- ```
-
- Как видно из примера, мы воспользовались оператором `~`, для вычисления значения, которое хранилось в `coll`. Однако из-за того, что мы теперь производим зависимые вычисления внутри формы (зависимость от переданных аргументов), которую возвращаем, не получится воспользоваться формами `macroexpand` и `macroexpand-1`.
-
- ```clojure
- (macroexpand twice-sum-macro)
- Syntax error compiling at (REPL:1:1).
- Can't take value of a macro: #'user/twice-sum-macro
-
- (macroexpand-1 twice-sum-macro)
- Syntax error compiling at (REPL:1:1).
- Can't take value of a macro: #'user/twice-sum-macro
- ```
-
-
-instructions: |
- Вернемся к первоначальной задачи, которую мы поставили, исправьте макрос `strange-macro` так (не забудьте про оператор `~`), что бы он работал :)
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/unquote)
- - |
- [Еще немного о макросах](https://www.braveclojure.com/writing-macros/)
diff --git a/modules/55-macros/50-unquote/ru/data.yml b/modules/55-macros/50-unquote/ru/data.yml
index 39bf10d..300d64f 100644
--- a/modules/55-macros/50-unquote/ru/data.yml
+++ b/modules/55-macros/50-unquote/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Вычисления внутри цитирования
tips:
- |
diff --git a/modules/55-macros/60-splicing/description.ru.yml b/modules/55-macros/60-splicing/description.ru.yml
deleted file mode 100644
index 8af04fc..0000000
--- a/modules/55-macros/60-splicing/description.ru.yml
+++ /dev/null
@@ -1,118 +0,0 @@
----
-
-name: Splicing
-theory: |
- Попробуем решить еще одну странную задачку! Создадим макрос, который принимает вектор чисел, выводит их на экран и затем возвращает вектор, попробуйте сделать такой макрос сами, перед тем как читать дальше.
-
- А теперь рассмотрим макрос, который решает нашу задачу:
-
- ```clojure
- (defmacro print-els [coll]
- `(do ~(map println coll)
- ~coll))
-
- (print-els [1 2 3])
- 1
- 2
- 3
- Syntax error (IllegalArgumentException)
- Can't call nil, form: (nil nil nil)
- ```
-
- Хммм, не работает, воспользуемся `macroexpand-1`:
-
- ```clojure
- (macroexpand-1 '(print-els [1 2 3]))
- 1
- 2
- 3
- (do (nil nil nil) [1 2 3])
- ```
-
- Итак, вызов `~(map println coll)` вернул `(nil nil nil)`, а так как nil нельзя вызвать, возникает ошибка. Однако, эта часть кода работает!
-
- ```clojure
- (do (map println [1 2 3]))
- 1
- 2
- 3
- (nil nil nil)
-
- ; и этот код работает
- (do nil nil nil [1 2 3])
- [1 2 3]
- ```
-
- По сути, мы хотим чтобы макрос и возвращал код выше.
-
- Для решения этой проблемы воспользуемся новым оператором `~@` (unquote splicing). Этот оператор используется, если в макросе нужно получить содержание Clojure формы.
-
- ```clojure
- (defmacro print-els [coll]
- `(do ~@(map println coll)
- ~coll))
-
- (print-els [1 2 3])
- 1
- 2
- 3
- [1 2 3]
-
- (macroexpand-1 '(print-els [1 2 3]))
- 1
- 2
- 3
- (do nil nil nil [1 2 3])
- ```
-
- А может мы просто вернем форму и вычислим ее снаружи макроса? Попробуем так:
-
- ```clojure
- (defmacro print-els* [coll]
- `(do (map println ~coll)
- ~coll))
- ```
-
- Но что произойдет, если в пространстве имен, в котором мы определяем наш макрос, форма `map` означает нечто иное? Допустим, `map` будет определять хеш-мап?
-
- ```clojure
- ; переопределим форму map
- (def map #{:a 1})
-
- ; числа 1 2 3 не вывелись в консоль
-
- (print-els* [1 2 3])
- [1 2 3]
- ```
-
- Помните третье правило макросов? *Данные, возвращаемые макросом немедленно вычисляются и результат этого вычисления отдается наружу при вызове этого макроса.*
-
- Но как мы видим, *контекст*, в котором мы вызываем макрос, тоже важен! Поэтому немного дополним правило: *Данные, возвращаемые макросом, немедленно вычисляются в контексте пространства имен, в котором макрос был вызван.*
-
-
-instructions: |
- Создайте макрос `strange-print`, который принимает строку, выводит в консоль строку сначала в обратном порядке, затем полностью в верхнем регистре, а затем полностью в нижнем и верните из макроса исходную строку.
-
- А потом вызовите созданный макрос для строк: `foo`, `!baz!`, `cloJURE`.
-
- Пример:
-
- ```clojure
- (strange-print "foo")
- oof
- FOO
- foo
- "foo"
-
- (strange-print "!baz!")
- !zab!
- !BAZ!
- !baz!
- "!baz!"
- ```
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/unquote-splicing)
- - |
- [Еще немного о макросах](https://www.braveclojure.com/writing-macros/)
diff --git a/modules/55-macros/60-splicing/ru/data.yml b/modules/55-macros/60-splicing/ru/data.yml
index 2ee009d..4709385 100644
--- a/modules/55-macros/60-splicing/ru/data.yml
+++ b/modules/55-macros/60-splicing/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Splicing
tips:
- >
diff --git a/modules/55-macros/70-gensym/description.ru.yml b/modules/55-macros/70-gensym/description.ru.yml
deleted file mode 100644
index 68a4b06..0000000
--- a/modules/55-macros/70-gensym/description.ru.yml
+++ /dev/null
@@ -1,92 +0,0 @@
----
-
-name: Gensym
-theory: |
- А теперь затронем еще одну важную тему в макросах, а точнее, их *гигиеничность*. Почему это важно? Рассмотрим для начала примеры:
-
- ```clojure
- (defmacro return-some-list [x]
- (list 'let ['one 5]
- ['one x]))
-
- (return-some-list 1)
- [5 1]
- ```
-
- Пока что все в порядке, но теперь добавим переменную `one` в наше пространство имен:
-
- ```clojure
- (def one 1)
-
- (return-some-list one)
- [5 5]
- ```
-
- Совсем не то, что ожидалось, не правда ли? Помните, что аргументы переданные в макрос не вычисляются и из-за того, что мы возвращаем символ `one` в контексте вызова формы `let`, значение `one` перетирается 5.
-
- ```clojure
- (macroexpand-1 '(return-some-list one))
- (let [one 5] [one one]) ; one здесь равняется 5
- ```
-
- Этот эффект называется *symbol capture* или *variable capture* (захват/перекрытие символа или переменной, если переводить на русский), одна из частых ошибок, возникающих при создании макросов.
-
- Можно попытаться процитировать объявление внутри макроса, однако это вызовет исключение. Попробуем:
-
- ```clojure
- (defmacro return-some-list [x]
- `(let [one 5]
- [one ~x]))
- #'user/return-some-list
-
- (return-some-list one)
- Syntax error macroexpanding clojure.core/let .
- user/one - failed: simple-symbol? at: [:bindings :form :local-symbol] spec: :clojure.core.specs.alpha/local-name
- ```
-
- Это происходит из-за того, что `let` принимает только *простые символы*, в то время как *сложные символы* - нет (на англ. fully-qualified symbols). Так как источник ошибки уходит глубоко во внутренности Clojure и потребует немало времени и сил, чтобы понять, что вообще происходит, создатели языка позаботились об этом понятным исключением `failed: simple-symbol?`.
-
- ```clojure
- (simple-symbol? 'one)
- true
-
- (simple-symbol? 'user/one) ; в этом символе участвует еще и пространство имен
- false
- ```
-
- Однако, чтобы решить проблему с перекрытием объявления `one`, мы бы могли использовать какое-нибудь уникальное название и Clojure представляет такую возможность благодаря `gensym`!
-
- ```clojure
- (gensym)
- G__2144 ; у вас скорее всего вывод будет отличаться
- ```
-
- Проблема с перекрытием объявлений достаточно распространенная, поэтому в Clojure есть синтаксический сахар под это дело, называется он `autogensym` или `#`:
-
- ```clojure
- `(one#)
- (one__2148__auto__)
-
- (simple-symbol? `one#)
- true
- ```
-
- А теперь воспользуемся полученными знаниями для нашего макроса, в котором происходило перекрытие `one`:
-
- ```clojure
- (defmacro return-some-list [x]
- `(let [one# 5]
- [one# ~x]))
-
- (return-some-list one)
- [5 1] ; работает!
- ```
-
- Вот и все, это почти все инструменты, которые используются при написании макросов, однако нужно обсудить еще несколько важных вещей в следующем упражнении.
-
-instructions: |
- Создайте макрос `auto-sum`, внутри которого объявляется символ `my-var` со значением 10 и складывается с переданным в макрос числом (не забывайте про перекрытие объявлений!).
-
-tips:
- - |
- [Официальная документация](https://clojuredocs.org/clojure.core/gensym)
diff --git a/modules/55-macros/70-gensym/ru/data.yml b/modules/55-macros/70-gensym/ru/data.yml
index dffd3a8..994ab17 100644
--- a/modules/55-macros/70-gensym/ru/data.yml
+++ b/modules/55-macros/70-gensym/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Gensym
tips:
- |
diff --git a/modules/55-macros/80-closing-thoughts/description.ru.yml b/modules/55-macros/80-closing-thoughts/description.ru.yml
deleted file mode 100644
index 1f5eb6c..0000000
--- a/modules/55-macros/80-closing-thoughts/description.ru.yml
+++ /dev/null
@@ -1,179 +0,0 @@
----
-
-name: Советы по созданию макросов
-theory: |
- Знание правил и синтаксиса макросов, еще не гарантируют правильность их написания, однако можно направить свои рассуждения в правильную сторону придерживаясь примерно такого алгоритма (впрочем, этот алгоритм подходит и под проектирование функций):
-
- 1. Подумайте о том, какие данные получает макрос
- 2. Задумайтесь о том, какие данные макрос должен вернуть
- 3. Подумайте, каким образом исходные данные трансформировать в возвращаемые
-
- Хоть и алгоритм звучит немного очевидно(?) и иногда бывают случаи, когда пункт 2 идет впереди пункта 1, все же он позволяет направить мысли в нужное русло.
-
- Попробуем сделать макрос `postfix` исходя из алгоритма выше:
-
- 1. Данными для макроса будут списки, например `(1 2 +)`, то есть в общем виде `(operand1 operand2 operator)`
- 2. Так как мы знаем, что в Lisp-подобных языках оператор идет первым, а за ним операнды, к которым оператор будет применен. То есть, в результате данные будут иметь следующую форму `(operator operand1 operand2)`
- 3. Посмотрим на примере, как данные будут меняться
-
- ```clojure
- ; входные данные => данные, которые возвращает макрос
- (1 2 +) => (+ 1 2)
- ```
-
- По сути, мы возвращаем список из трех элементов:
-
- ```clojure
- ; входны данные => внутри макроса => данные, которые возвращает макрос
- (1 2 +) => (list + 1 2) => (+ 1 2)
- ```
-
- Так как нам нужно значение, вычисляемое макросом, то и пользоваться оператором цитирования `'` не нужно. Значит макрос будет выглядеть так:
-
- ```clojure
- (defmacro postfix [[op1 op2 operator]]
- (list operator op1 op2))
-
- (postfix (1 2 +))
- 3
-
- (postfix (1 2 -))
- -1
- ```
-
- Благодаря тому, что Clojure предоставляет удобный REPL, создавать макросы с помощью такого алгоритма заметно проще.
-
- Так как макросы всего лишь инструмент, то у них есть, конечно же и недостатки. Поэтому поговорим подробнее о них.
-
- *Макросы не являются значением*
-
- Функция может быть значением, но макрос же - нет. Это означает, что мы не можем передавать макросы в функции высшего порядка:
-
- ```clojure
- (defmacro my-odd [x] `(odd? ~x))
-
- (filter my-odd [1 2 3 4 5 6])
- Syntax error compiling
- Can't take value of a macro: #'user/my-odd
- ```
-
- Если все же нужно передать макрос в функции высшего порядка, то придется обернуть макрос в функцию (вы это могли видеть в тестах, когда макрос, который тестируется, обернут в анонимную функцию):
-
- ```clojure
- (filter #(my-odd %) [1 2 3 4 5 6])
- ```
-
- Однако это может сработать далеко не во всех случаях.
-
- *Разворачивание макроса происходит во время компиляции кода*
-
- Это то, о чем упоминалось в третьем правиле макросов, что оно не совсем точное. Когда вы вызываете макрос в своем коде, он заменяется на список, который вызванный макрос возвращает после *компиляции* программы. А список, который вернул макрос, будет выполнен при *запуске* программы.
-
- Звучит немного запутанно, так как при экспериментировании в REPL вы не замечаете того, как компилируется и запускается код, так как это происходит моментально. Поэтому исследуем этот вопрос чуть подробнее:
-
- ```clojure
- (defmacro muliply-2 [xs]
- `(* 2 ~@xs))
- ```
-
- Теперь создадим файл `example.clj` и используем там макрос `multiply-2`:
-
- ```clojure
- (muliply-2 [1 3])
- ```
-
- После того как мы скомпилируем файл и посмотрим на итоговый код, то увидим следующее:
-
- ```clojure
- (* 2 1 3)
- ```
-
- И только этот код будет выполнен при запуске *скомпилированной* программы. Так как макросу нужно знать, что находится в `xs` во время *компиляции* (из-за `~@xs`), мы не можем передать переменную в макрос:
-
- ```clojure
- (defn multiply-by-2 [nums]
- (muliply-2 nums))
- Syntax error (IllegalArgumentException) compiling
- Don't know how to create ISeq from: clojure.lang.Symbol
- ```
-
- Так как `nums` будут переданы только во время *запуска* программы, макрос не знает о том, что хранится в `nums`, однако внутри макроса мы пытаемся извлечь значения, которые хранятся в переданном *символе* (оператор `~@`). Это еще одна причина, почему нельзя передавать макросы в функции высшего порядка.
-
- *Макросы привлекают макросы*
-
- Сделаем еще один макрос:
-
- ```clojure
- (defmacro add [& args]
- `(+ ~@args))
-
- (add 1 2 3)
- 6
- ```
-
- Но что будет, если мы захотим узнать сумму каждого вектора чисел в переданном списке? В обычном случае мы бы использовали простую комбинацию функций:
-
- ```clojure
- (map #(apply + %) [[1 2 3] [2 4] [3 3]])
- (6 6 6)
- ```
-
- Но как мы помним, макросы нельзя передавать как значения:
-
- ```clojure
- (defmacro add [& args]
- `(+ ~@args))
-
- (map #(apply add %) [[1 2 3] [2 4]])
- ; Syntax error compiling at
- ; Can't take value of a macro: #'user/add
- ```
-
- Придется писать еще один макрос...
-
- ```clojure
- (defmacro add-vecs [vecs]
- (loop [f (first vecs)
- r (rest vecs)
- res `(list)]
- (if (seq r)
- (recur (first r) (rest r) (concat res `((add ~@f))))
- (concat res `((add ~@f))))))
-
- (add-vecs [[1 2 3] [2 4] [3 3]])
- (6 6 6)
- ```
-
- Как быстро все начало запутываться... А мы ведь только начали...
-
- *Макросы сложнее читать, писать и понимать*
-
- Из-за того, что макросы по сути вычисляются "дважды", понимать их всегда сложнее, чем обычную функцию, а как мы знаем, понятность кода является важной частью программирования. В целом, это одна из причин, почему многие критикуют Lisp-подобные языки, так как поддерживать проект, который состоит из кучи макросов, написанных программистами, которые уже не работают в организации, задача крайне авантюрная :)
-
- *Не нужно писать макрос, если с этим справится обычная функция*
-
- Исключением являются случаи, когда макрос является более удобным способом организации кода, чем простая функция, прекрасным примером являются макросы `->` и `-->`:
-
- ```clojure
- ; это валидный код, однако он выглядит немного запутанно
- (map str
- (map #(* % %)
- (map inc
- (filter even? (range 25)))))
-
- ; тот же самый код, однако операции, которые производятся над данными, понимаются намного проще и быстрее
- (->> (range 25)
- (filter even?)
- (map inc)
- (map #(* % %))
- (map str))
- ```
-
- В целом это все, что хотелось сказать о макросах. Для углубления понимания макросов, можно заняться чтением библиотек, используемых в Clojure, например `clojure.test`. Ну и практика, куда же без нее :)
-
-instructions: |
- В последнем упражнении основной упор был на теорию, поэтому задание будет простым :) Создайте макрос `macro-inc`, который увеличивает переданное число на 1.
-
-tips:
- - |
- [Пример хороших макросов в clojure.test](https://github.com/clojure/clojure/blob/master/src/clj/clojure/test.clj)
diff --git a/modules/55-macros/80-closing-thoughts/ru/data.yml b/modules/55-macros/80-closing-thoughts/ru/data.yml
index 7bfa041..5b977ac 100644
--- a/modules/55-macros/80-closing-thoughts/ru/data.yml
+++ b/modules/55-macros/80-closing-thoughts/ru/data.yml
@@ -1,3 +1,4 @@
+---
name: Советы по созданию макросов
tips:
- >