Logo
Книга для начинающих
ВходРегистрация
/
Программирование
/
Курс Elixir
/

AST и подведение итогов

Elixir: AST и подведение итогов

В Elixir есть функции, упрощающих работу с абстрактным синтаксическим деревом (АСТ) языка. Например:

module = """
  defmodule Example do
    def sum(a, b) do
      a + b
    end
  end
"""

{:ok, result} = Code.string_to_quoted(module)
result
# => {:defmodule, [line: 1],
# =>   [
# =>     {:__aliases__, [line: 1], [:Example]},
# =>     [
# =>       do: {:def, [line: 2],
# =>         [
# =>           {:sum, [line: 2], [{:a, [line: 2], nil}, {:b, [line: 2], nil}]},
# =>           [do: {:+, [line: 3], [{:a, [line: 3], nil}, {:b, [line: 3], nil}]}]
# =>         ]}
# =>   ]
# => ]}

quoted = quote do: 1 + 2

quoted |> Macro.to_string |> IO.puts
# => 1 + 2
# => :ok

quoted_pipe = quote do: 100 |> div(10) |> div(5)
Macro.unpipe(quoted_pipe)
# => [
# =>   {100, 0},
# =>   {{:div, [context: Elixir, imports: [{2, Kernel}]], [10]}, 0},
# =>   {{:div, [context: Elixir, imports: [{2, Kernel}]], [5]}, 0}
# => ]

Теперь подведем итоги модуля. Мы рассмотрели, как с помощью макросов можно расширять возможности языка и напрямую работать с АСТ.

Однако, в большинстве случаев прибегать к макросам является плохой затеей:

  • Макросы сложнее понимать;
  • Макросы сложнее отлаживать;
  • Макросы притягивают макросы, то есть приходится писать дополнительные обвязки-макросы.

Перед тем как написать, задумайтесь, есть ли возможность решить задачу с помощью функции? Ответ на этот вопрос почти всегда будет - да.

Хоть Elixir помогает и одновременно ограничивает разработчиков, например:

  • Макросы по умолчанию гигиеничны;
  • Макросы не дают возможности глобально внедрять произвольный код, то есть необходимо напрямую в конкретном модуле вызвать require или import нужного макроса;
  • Использование макросов в коде явно, поэтому неожиданного поведения, как например, полного переопределения функции, за спиной у разработчика не происходит;
  • Использование явных quote и unqoute тоже упрощает понимание макроса, так как сразу видно, что будет выполнено сразу, а что потом.

Но даже эти механизмы не снимают ответственность с разработчика за написанный им код, пишите макросы ответственно и вдумчиво, и только если в этом есть острая необходимость!

Задание

Создайте функцию collect_module_stats принимающую строку, в которой определяется модуль и функции модуля, а затем подсчитывается статистика по функциям, определенные внутри модуля.

Для начала, изучите функцию string_to_quoted модуля Code и функцию prewalk из модуля Macro. Формат собираемой статистики представлен в примерах:

new_module = """
  defmodule MyModule do

  end
"""

Solution.collect_module_stats(new_module)
# => []

new_module = """
  defmodule MyModule do
    def hello() do
      "world"
    end
  end
"""

Solution.collect_module_stats(new_module)
# => [%{arity: 0, name: :hello}]

new_module = """
  defmodule MyModule do
    def hello() do
      "world"
    end

    defp test(a, b) do
      a + b
    end
  end
"""

Solution.collect_module_stats(new_module)
# => [%{arity: 2, name: :test}, %{arity: 0, name: :hello}]

new_module = """
  defmodule MyModule do
    def hello(string) do
      [string, "world"]
    end

    def magic(a, b, c) do
      (a + b) * c
    end

    defp test(a, b) do
      a + b
    end
  end
"""

Solution.collect_module_stats(new_module)
# => [%{arity: 2, name: :test}, %{arity: 3, name: :magic}, %{arity: 1, name: :hello}]

Полезное

  • Официальная документация Code

  • Официальная документация Macro

  • Про AST

  • Примеры макросов на основе ExUnit, фреймворка для тестирования

  • Веб фреймворк, в котором тоже много используется метапрограммирование

Команда проекта находится в телеграм-сообществе. Там можно задать любой вопрос и повлиять на проект

Если вы зашли в тупик, то самое время поговорить с нашим асситентом Тота во вкладке "ИИ-помощник":

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи. В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в обратной связи нашего сообщества

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются
/
Программирование
/
Курс Elixir
/

AST и подведение итогов

Elixir: AST и подведение итогов

В Elixir есть функции, упрощающих работу с абстрактным синтаксическим деревом (АСТ) языка. Например:

module = """
  defmodule Example do
    def sum(a, b) do
      a + b
    end
  end
"""

{:ok, result} = Code.string_to_quoted(module)
result
# => {:defmodule, [line: 1],
# =>   [
# =>     {:__aliases__, [line: 1], [:Example]},
# =>     [
# =>       do: {:def, [line: 2],
# =>         [
# =>           {:sum, [line: 2], [{:a, [line: 2], nil}, {:b, [line: 2], nil}]},
# =>           [do: {:+, [line: 3], [{:a, [line: 3], nil}, {:b, [line: 3], nil}]}]
# =>         ]}
# =>   ]
# => ]}

quoted = quote do: 1 + 2

quoted |> Macro.to_string |> IO.puts
# => 1 + 2
# => :ok

quoted_pipe = quote do: 100 |> div(10) |> div(5)
Macro.unpipe(quoted_pipe)
# => [
# =>   {100, 0},
# =>   {{:div, [context: Elixir, imports: [{2, Kernel}]], [10]}, 0},
# =>   {{:div, [context: Elixir, imports: [{2, Kernel}]], [5]}, 0}
# => ]

Теперь подведем итоги модуля. Мы рассмотрели, как с помощью макросов можно расширять возможности языка и напрямую работать с АСТ.

Однако, в большинстве случаев прибегать к макросам является плохой затеей:

  • Макросы сложнее понимать;
  • Макросы сложнее отлаживать;
  • Макросы притягивают макросы, то есть приходится писать дополнительные обвязки-макросы.

Перед тем как написать, задумайтесь, есть ли возможность решить задачу с помощью функции? Ответ на этот вопрос почти всегда будет - да.

Хоть Elixir помогает и одновременно ограничивает разработчиков, например:

  • Макросы по умолчанию гигиеничны;
  • Макросы не дают возможности глобально внедрять произвольный код, то есть необходимо напрямую в конкретном модуле вызвать require или import нужного макроса;
  • Использование макросов в коде явно, поэтому неожиданного поведения, как например, полного переопределения функции, за спиной у разработчика не происходит;
  • Использование явных quote и unqoute тоже упрощает понимание макроса, так как сразу видно, что будет выполнено сразу, а что потом.

Но даже эти механизмы не снимают ответственность с разработчика за написанный им код, пишите макросы ответственно и вдумчиво, и только если в этом есть острая необходимость!

Задание

Создайте функцию collect_module_stats принимающую строку, в которой определяется модуль и функции модуля, а затем подсчитывается статистика по функциям, определенные внутри модуля.

Для начала, изучите функцию string_to_quoted модуля Code и функцию prewalk из модуля Macro. Формат собираемой статистики представлен в примерах:

new_module = """
  defmodule MyModule do

  end
"""

Solution.collect_module_stats(new_module)
# => []

new_module = """
  defmodule MyModule do
    def hello() do
      "world"
    end
  end
"""

Solution.collect_module_stats(new_module)
# => [%{arity: 0, name: :hello}]

new_module = """
  defmodule MyModule do
    def hello() do
      "world"
    end

    defp test(a, b) do
      a + b
    end
  end
"""

Solution.collect_module_stats(new_module)
# => [%{arity: 2, name: :test}, %{arity: 0, name: :hello}]

new_module = """
  defmodule MyModule do
    def hello(string) do
      [string, "world"]
    end

    def magic(a, b, c) do
      (a + b) * c
    end

    defp test(a, b) do
      a + b
    end
  end
"""

Solution.collect_module_stats(new_module)
# => [%{arity: 2, name: :test}, %{arity: 3, name: :magic}, %{arity: 1, name: :hello}]

Полезное

  • Официальная документация Code

  • Официальная документация Macro

  • Про AST

  • Примеры макросов на основе ExUnit, фреймворка для тестирования

  • Веб фреймворк, в котором тоже много используется метапрограммирование

Команда проекта находится в телеграм-сообществе. Там можно задать любой вопрос и повлиять на проект

Если вы зашли в тупик, то самое время поговорить с нашим асситентом Тота во вкладке "ИИ-помощник":

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи. В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в обратной связи нашего сообщества

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются
← ПредыдущийСледующий →
← ПредыдущийСледующий →
← ПредыдущийСледующий →

Ваше упражнение проверяется по этим тестам

defmodule Test do
  use ExUnit.Case

  describe "collect_module_stats work" do
    test "when no function defined" do
      new_module = """
        defmacro MyModule do
          require Integer

          @some_attr "1"

          defmacro no_interference do
            quote do: a = 1
          end
        end
      """

      assert Solution.collect_module_stats(new_module) == []
    end

    test "when few functions defined" do
      new_module = """
        defmacro MyModule do
          def hello() do
            "world"
          end

          def test(a, b) do
            a + b
          end
        end
      """

      assert Solution.collect_module_stats(new_module) == [
               %{arity: 2, name: :test},
               %{arity: 0, name: :hello}
             ]
    end

    test "when few functions and protocols defined" do
      new_module = """
        defmacro MyModule do
          def hello(string) do
            [string, "world"]
          end

          def magic(a, b, c) do
            (a + b) * c
          end

          defp test(a, b) do
            a + b
          end
        end
      """

      assert Solution.collect_module_stats(new_module) == [
               %{arity: 2, name: :test},
               %{arity: 3, name: :magic},
               %{arity: 1, name: :hello}
             ]
    end
  end
end
← ПредыдущийСледующий →

Решение учителя откроется через:

20:00

waiting_clock
← ПредыдущийСледующий →