Тазик декларативного кода и с наступающим

image-loader.svg

Здорово, когда на работе можно заниматься не только важными и серьёзными делами, но и чем-то интересным, пусть и без явной пользы. Наши деврелы активно поддерживают эту позицию и время от времени подкидывают идеи каких-нибудь забавных штук. В этот раз было так: «А давайте возьмём рецепты новогодних салатов и напишем их кодом?».

«А давайте», — подумал я. У нас как раз код-фриз и заниматься продуктовыми задачами нельзя. А окунуться в новогоднее настроение — можно :)

Началось всё с того, что я попробовал закодить рецепт оливье в виде класса на Kotlin, которому на вход единственного метода приходят «ингредиенты», а выдаёт он готовый салат. Шло неплохо, но получалась простая трансляция рецепта на язык программирования — код практически один в один повторял шаги приготовления, только синтаксис был другой. Скукотища.

Смотрите сами, вот этот рецепт:

ИНГРЕДИЕНТЫ (на 4 новогодних порции)
— 3 картофелины среднего размера
— 3–4 моркови
— 4–5 яиц
— 450 г консервированного зеленого горошка
— 5–6 солёных огурцов
— 350 г колбасы, ветчины отварной говядины или куриной грудки (выбирай на свой вкус)
— 5 побегов зеленого лука
— 1 пучок укропа
— 250 г майонеза
— соль
— чёрный молотый перец

ПОШАГОВЫЙ РЕЦЕПТ ПРИГОТОВЛЕНИЯ
1. Возьмите двухлитровую кастрюлю, наполните водой до середины, поставьте нагреваться на плиту.
2. Пока вода греется, подготовьте овощи. Картофель и морковь тщательно вымойте щёткой.
3. Когда вода в кастрюле закипит, добавьте половину чайной ложки соли, положите овощи. Варите до готовности овощей.
4. Вымойте яйца, возьмите кастрюлю подходящего размера, положите внутрь яйыа и залейте водой так, чтобы она покрывала их полностью. Поставьте кастрюлю на плиту, варите до готовности, не допускайте переваривания желтка (5–7 минут после закипания).
5. Овощи и яйца остудите, очистите и нарежьте кубиками со стороной 5 мм. Такими же кубиками нарежьте ветчину или колбасу, солёные огурцы.
6. Откройте банку консервированного горошка, слейте жидкость.
7. Вымойте, высушите и мелко нарежьте зелёный лук и укроп.
8. Все ингредиенты смешайте в большом салатнике, добавьте соль по вкусу, приправьте чёрным молотым перцем.
9. Перемешайте, заправьте майонезом, поставьте в холодильник.

Если предпочитаете салат с говядиной или курицей, то нужно заранее отварить их.

Похоже на код, не правда ли? Сделайте это, потом вот это, а здесь нужно делать действие, пока ждём результата предыдущего действия. Разве что «переменные» описаны перед «кодом», а не объявляются по необходимости, но и такое можно встретить в других языках программирования (например, Pascal). Ты, как какой-нибудь интерпретатор, исполняешь всё по этой инструкции и получаешь тазик оливье. Неудивительно, что идея написать это в императивном стиле пришла первой.

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

val boilEggs: (List) -> List

Ну не описывать же в деталях процесс варки яиц, в самом деле. Так и до салата можно не добраться. Код всё больше обрастал такими абстракциями: смешивание ингредиентов в салатнике, мытье картошки щёткой — это всё, на мой взгляд, было абсолютно неважно для готового блюда. Их создание занимало много времени, поэтому после примерно пары часов работы у меня было описано только три пункта из рецепта.

Тогда я подумал, как избавиться от них? Почти сразу вспомнил о декларативном стиле, который сейчас набирает популярность в мире мобильной разработки. Уже полгода как вышли стабильные версии UI-фреймворков Jetpack Compose и Swift UI, а на Flutter так и вообще уже написано немало приложений.

Их прелесть (как и вообще декларативного стиля) в том, что можно не задумываться о деталях реализации каждого элемента интерфейса и как добавить его на экран, а сконцентрироваться на том, что пользователь видит на экране своего телефона. Т.е. вместо описания последовательности действий мы в коде описываем желаемый результат:

  • не «добавить кнопку и установить ей красный цвет»,

    val button = Button()
    button.setColor(Color.Red)
    addView(button)
  • а «красная кнопка»

    Button(Color.Red)

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

Получилось вот так (синтаксис по мотивам Jetpack Compose):

СалатОливье {
  Смесь(
      заправка = Майонез,
      специи = [
        Соль(количество = поВкусу),
        ЧерныйПерец(вид = молотый, количество = поВкусу)
      ]
  ) {
    Картофель(
        обработка = [чистый, без(Кожица), отваренный],
        нарезка = кубиками(5мм)
    )
    Морковь(
        обработка = [чистый, без(Кожица), отваренный],
        нарезка = кубиками(5мм)
    )
    Огурец(
        обработка = [засоленный],
        нарезка = кубиками(5мм)
    )
    Яйцо(
        обработка = [чистый, без(Скорлупа), вареный(вкрутую)],
        нарезка = кубиками(5мм)
    )
    Горох(
        обработка = [консервированный]
    )
    НаВыбор {
      Колбаса(
          обработка = [вареная],
          нарезка = кубиками(5мм)
      )
      Курица(
          обработка = [вареная],
          нарезка = кубиками(5мм)
      )
      Говядина(
          обработка = [вареная],
          нарезка = кубиками(5мм)
      )
    }
    ЗеленыйЛук(нарезка = шинковка(мелкая))
    Укроп(нарезка = шинковка(мелкая))
  }
}

Что поменяется? Можно ли всё ещё рассчитывать на оливье?

Кажется, что с «декларативным рецептом» справится любой, даже начинающий повар. Разве что ребенку без базовых знаний будет сложно. А вот опытный шеф может просмотреть весь рецепт и легко поменять что-то по своему усмотрению, чтобы стало ещё лучше. Прямо как оптимизирующий компилятор. ;)

Какой подход использовать в своих «рецептах» — решать вам. А если декларативный вариант собьёт вас с толку, то берите бутылку шампанского и езжайте к маме — у неё точно найдётся, чем вас накормить.

И продолжайте экспериментировать! С Новым годом!

© Habrahabr.ru