Тазик декларативного кода и с наступающим
Здорово, когда на работе можно заниматься не только важными и серьёзными делами, но и чем-то интересным, пусть и без явной пользы. Наши деврелы активно поддерживают эту позицию и время от времени подкидывают идеи каких-нибудь забавных штук. В этот раз было так: «А давайте возьмём рецепты новогодних салатов и напишем их кодом?».
«А давайте», — подумал я. У нас как раз код-фриз и заниматься продуктовыми задачами нельзя. А окунуться в новогоднее настроение — можно :)
Началось всё с того, что я попробовал закодить рецепт оливье в виде класса на 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мм)
)
}
ЗеленыйЛук(нарезка = шинковка(мелкая))
Укроп(нарезка = шинковка(мелкая))
}
}
Что поменяется? Можно ли всё ещё рассчитывать на оливье?
Кажется, что с «декларативным рецептом» справится любой, даже начинающий повар. Разве что ребенку без базовых знаний будет сложно. А вот опытный шеф может просмотреть весь рецепт и легко поменять что-то по своему усмотрению, чтобы стало ещё лучше. Прямо как оптимизирующий компилятор. ;)
Какой подход использовать в своих «рецептах» — решать вам. А если декларативный вариант собьёт вас с толку, то берите бутылку шампанского и езжайте к маме — у неё точно найдётся, чем вас накормить.
И продолжайте экспериментировать! С Новым годом!