-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #82 from solar05/add-quoting
Add quoting exercise
- Loading branch information
Showing
8 changed files
with
186 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
test: | ||
@ test.sh | ||
|
||
.PHONY: test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
name: Intoduction to quote and unquote | ||
theory: | | ||
🚧 In development | ||
instructions: | | ||
🚧 In development | ||
tips: [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
--- | ||
name: Знакомство с quote и unquote | ||
theory: | | ||
Вспомним пример из прошлого упражнения: | ||
```elixir | ||
defmodule Exercise do | ||
defmacro double(x) do | ||
{:*, [], [2, x]} | ||
end | ||
end | ||
``` | ||
В этом макросе мы вернули код во `внутреннем представлении` языка Elixir, но работать с таким представлением неудобно, особенно если есть вложенность: | ||
```elixir | ||
# в этом макросе выполняется следующая операция ((x * 4) + 3) * x | ||
defmodule Exercise do | ||
defmacro magic(x) do | ||
{:*, [], | ||
[ | ||
{:+, [], | ||
[{:*, [], [x, 4]}, 3]}, | ||
x | ||
] | ||
} | ||
end | ||
end | ||
require Exercise | ||
Exercise.magic(1) | ||
# 7, т.к. ((1 * 4) + 3) * 1 | ||
Exercise.magic(2) | ||
# 22, т.к. ((2 * 4) + 3) * 2 | ||
``` | ||
В примере выше мы напрямую управляли структурой AST дерева, однако понимать этот код стало сложнее. Для упрощения работы с AST в макросах Elixir есть функции `quote` и `unquote` прямиком перекочевавшие из Lisp-подобных языков. | ||
Функция `quote` принимает произвольное выражение на языке Elixir и преобразует его во внутренне представление языка: | ||
```elixir | ||
quote do: 1 + 2 | ||
# => {:+, [], [1, 2]} | ||
quote do: Integer.to_string((1 + 2) * 3) | ||
# => {{:., [], []}, [], | ||
# => [ | ||
# => {:*, [], | ||
# => [{:+, [], [1, 2]}, 3]} | ||
# => ]} | ||
``` | ||
Теперь попробуем переписать макрос из начала упражнения: | ||
```elixir | ||
defmodule Exercise do | ||
defmacro double(x) do | ||
quote do: 2 * x | ||
end | ||
end | ||
require Exercise | ||
Exercise.double(2) | ||
# => error: undefined variable "x" (context Exercise) | ||
``` | ||
Код не работает из-за того, что `x` при передаче в макрос уже в форме внутреннего представления, поэтому нужно сообщить Elixir, что аргумент не нужно переводить во внутренне представление, для этого используется функция `unquote`: | ||
```elixir | ||
defmodule Exercise do | ||
defmacro double(x) do | ||
quote do | ||
2 * unquote(x) | ||
end | ||
end | ||
end | ||
require Exercise | ||
Exercise.double(2) | ||
# => 4 | ||
``` | ||
По сути мы сообщили Elixir следующее: `Преврати 2 * x во внутреннее представление, но оставь аргумент x в исходном виде.` | ||
Подведем итоги: `quote` означает `преврати все в блоке do в формат внутреннего представления`, `unquote` означает `не превращай это во внутренний формат`. | ||
Многие, кто пишут макросы, частно допускают ошибку, забывая передать аргументы функции `unquote`. Важно помнить, что *все* аргументы передаются макросам во внутреннем формате. | ||
Так же важно знать, что при передаче в `quote` атома, числа, строки, списка или кортежа с двумя элементами, функция вернет аргумент без изменений, а не кортеж во внутреннем формате. | ||
```elixir | ||
quote do: :a | ||
# => :a | ||
quote do: 2 | ||
# => 2 | ||
quote do: "hello" | ||
# => "hello | ||
quote do: [1, 2, 3] | ||
# => [1, 2, 3] | ||
quote do: {1, 2} | ||
# => {1, 2} | ||
quote do: {1, 2, 3} | ||
# => {:{}, [], [1, 2, 3]} | ||
quote do: %{a: 2} | ||
# => {:%{}, [], [a: 2]} | ||
``` | ||
Происходит это потому, что элементы из примера выше тоже представляют собой часть AST структуры Elixir, поэтому их не нужно дополнительно переводить во внутреннее представление. | ||
instructions: | | ||
Создайте макрос `my_unless`, который повторяет семантику `unless`: | ||
```elixir | ||
require Solution | ||
Solution.my_unless(false, do: 1 + 3) | ||
# => 4 | ||
Solution.my_unless(true, do: 1 + 3) | ||
# => nil | ||
Solution.my_unless(2 == 2, "hello") | ||
# => nil | ||
Solution.my_unless(2 == 1, "world") | ||
# => "world" | ||
``` | ||
tips: | ||
- | | ||
[Официальная документация](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#quote/2) | ||
- | | ||
[Про AST](https://ru.wikipedia.org/wiki/%D0%90%D0%B1%D1%81%D1%82%D1%80%D0%B0%D0%BA%D1%82%D0%BD%D0%BE%D0%B5_%D1%81%D0%B8%D0%BD%D1%82%D0%B0%D0%BA%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
defmodule Solution do | ||
# BEGIN | ||
defmacro my_unless(condition, do: expression) do | ||
quote do | ||
if(!unquote(condition), do: unquote(expression)) | ||
end | ||
end | ||
|
||
# END | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
defmodule Exercise.MixProject do | ||
use Mix.Project | ||
|
||
def project do | ||
[ | ||
app: :exercise, | ||
version: "0.1.0", | ||
] | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
defmodule Test do | ||
use ExUnit.Case | ||
|
||
require Solution | ||
|
||
test "my_unless work" do | ||
assert Solution.my_unless(false, do: 1 + 3) == 4 | ||
refute Solution.my_unless(true, do: 2) | ||
refute Solution.my_unless(2 == 2, do: "Hello") | ||
assert Solution.my_unless(1 == 2, do: "world") == "world" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ExUnit.start() |