Как разработчики, мы оба знакомы и довольны разделением между написанием кода и выполнением кода. Мы можем открыть текстовый редактор и написать код, но в большинстве языков нет никаких ожиданий, что процесс написания кода что-то даст. У нас есть отдельный процесс, который будет выполнять код. Некоторые языки требуют этапа компиляции (Java, C#, Go, Haskell и т. д.) перед выполнением, а некоторые допускают прямое выполнение (Javascript, Python, Ruby и т. д.), но во всех этих случаях существует жесткий барьер между кодирование и выполнение кода.

Расширение абстракции

Что, если мы добавим еще один интерпретатор в наши слои абстракции? Что бы это сделало?

Это не такая уж сумасшедшая идея.

Java делает это уже много лет. В Java есть контейнеры сервлетов, контейнеры EJB и OSGi. В .Net есть IIS, свойства которого аналогичны различным контейнерам Java (не путать с контейнерами Docker). Существует множество примеров того, как код управляет жизненным циклом другого кода.

С эффектами в качестве данных мы собираемся использовать очень специфический подход. Мы собираемся создать несколько стеков эффектов (стек «первым пришел — первым обслужен» (FIFO) — так что на самом деле это очередь, название — не моя вина), которые позже мы пропустим через интерпретаторы.

Эффекты, которые мы собираемся поместить в наш стек, будут выдуманным языком, который мы собираемся создать (эффекты в виде структуры данных могут поставляться с некоторыми часто используемыми эффектами). Этот язык будет доменно-ориентированным языком (DSL), который будет содержать команды, полезные для создания решения проблемы, для решения которой мы создаем программное обеспечение.

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

Этот новый язык будет запускаться через интерпретатор, который мы либо написали на нашем обычном языке программирования, либо который был предоставлен нам фреймворком. Многие фреймворки просят нас написать обработчик для каждой команды в нашем DSL. Некоторые фреймворки позволяют нам настраивать разные интерпретаторы для разных ситуаций. Представьте, что у вас есть один интерпретатор для производства и другой для тестирования. Вместо мокинга мы можем написать обработчик тестов с предсказуемым поведением, подходящим для тестирования.

Преимущества

Есть место для оптимизации. Во время продолжительного сеанса интерпретатор может узнать о программе, которую он выполняет, и оптимизировать способ выполнения программы.

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

Конкретный способ реализации и использования DSL.

Побочные эффекты возникают в обработчиках, а не в вашем DSL. Вы можете испытать радость разработки без побочных эффектов при использовании своих команд.

Тестирование становится легким. Вы можете протестировать изоляцию каждой пары команда/обработчик и использовать альтернативные обработчики вместо имитации. Написание специального обработчика для теста кажется сверхестественным.

Пробовать это

Есть три хороших места для изучения этой концепции.

Elm — это серверный язык, который был создан с использованием парадигмы данных, встроенной в язык.

Effects As Data — лучший фреймворк Javascript, о котором вы никогда не слышали. Она написана талантливым разработчиком по имени Фрэнки О’Рурк и очень удобна в использовании.

Free Monads — это жесткая функциональная реализация этих идей. Несколько языков имеют библиотеки Free Monad. Это наименее доступный, но самый мощный вариант из трех.

Вывод

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