Эссе разбирает принцип «push ifs up, fors down» из Tiger Style TigerBeetle
Исходная рекомендация взята из документа Tiger Style компании TigerBeetle: все ветвления (switch/if) стоит держать в «родительской» функции, а неветвящуюся логику выносить во вспомогательные. За управление потоком отвечает одна функция, остальным оно безразлично. Сжато это формулируется так: «push ifs up and fors down» (поднимай условия вверх, опускай циклы вниз). Те же идеи ранее описывал в своём блоге matklad.
Первая часть эссе пересказывает пример matklad. Если функция frobnicate принимает Option
Вторая часть проводит аналогию с оптимизацией запросов в реляционных базах данных. Проекции и выборки (WHERE) выполняют как можно раньше, а соединения (join) откладывают, чтобы они работали на меньших данных. Терминология здесь «перевёрнута»: план запроса, дерево, данные идут от листьев к корню, поэтому «вниз по дереву» означает «раньше по времени». Когда оптимизатор «опускает» предикат, это то, что в остальном тексте названо «вверх» или «раньше». Аналог «fors вниз», векторизованное (пакетное) исполнение: вместо построчного вызова оператора через виртуальный next() (стиль Volcano) оператор вызывается один раз на пакет примерно в тысячу кортежей и крутит внутри плотный цикл. Накладные расходы и решения на вызов оплачиваются раз на пакет, а внутренний цикл содержит мало ветвлений и дружит с кэшем.
Третья часть, взгляд из функционального программирования и теории категорий. «Поднятие if» трактуется как ограничение до подобъекта: множество элементов, удовлетворяющих предикату, с вложением (мономорфизмом) в исходное множество. До этого вызываемая функция принимала любой A и проверяла условие внутри; после, проверку делает вызывающий код, а входной тип функции, это подмножество, что в коде выражается типом Walrus вместо Option
Далее разбирается совет «фильтруй до map». Выражения filter p (map f xs) и map f (filter p xs) не эквивалентны: в первом p проверяет результат f, во втором, вход. Законная перестановка задаётся равенством filter p . map f == map f . filter (p . f). Автор выводит его через параметричность, представляя filter как catMaybes . map (keep p); естественность catMaybes и обеспечивает законность преобразования. Выгода не автоматическая: filter (p . f) всё равно вычисляет f для каждого элемента, чтобы проверить его. Перестановка окупается, когда p . f упрощается до дешёвого предиката q на входе, обычно потому, что p смотрит на часть значения, которую f не трогает. Обе стороны остаются одним проходом O(n); экономятся вызовы f на элементах, которые всё равно отбросят.
Итоговые условия допустимости такие. Вынос if из цикла корректен, когда условие не зависит от итерации (loop-invariant); условие, зависящее от элемента, из цикла уйти не может и может лишь переместиться на границу и быть записанным в типе. Спуск выборки ниже соединения допустим, если предикат ссылается на столбцы только одной стороны соединения. Фильтрация до map корректна по закону выше и экономит работу, только если p . f сводится к дешёвому предикату на входе. Часть «fors вниз», скорее про стоимость, чем про эквивалентность: стрелка меняется с A -> B на [A] -> [B], и затраты на подготовку платятся один раз на пакет. Вывод автора: именно алгебра подсказывает, какие перестановки законны.
Ключевые факты
- Рекомендация из Tiger Style (TigerBeetle): ветвления держать в родительской функции, а циклы опускать вглубь; ту же идею ранее разбирал matklad.
- Пример: функция принимает Walrus вместо Option
, а вместо frobnicate в цикле используется frobnicate_batch(walruses) с циклом внутри: горячий цикл без ветвления можно векторизовать. - Аналогия в базах данных: проекции и выборки выполняются раньше соединений, а векторизованное исполнение вызывает оператор раз на пакет примерно в тысячу кортежей вместо построчного вызова.
- Закон filter p . map f == map f . filter (p . f) делает перестановку законной, но не обязательно дешевле: filter (p . f) всё равно вычисляет f для каждого элемента.
- Условия законности: if выносится из цикла, только если условие не зависит от итерации; выборка спускается ниже join, только если предикат касается столбцов одной стороны.
Почему это важно
Эссе не приносит новых результатов: это пересказ известной идиомы из Tiger Style TigerBeetle и блога matklad. Его ценность в том, что одна и та же мысль («где живёт решение» и «где крутится цикл») показана как общий принцип: в типах функций, в планах запросов баз данных и в алгебре комбинаторов. Заодно текст чётко обозначает границы: идиома не универсальна, и у каждой перестановки есть условия законности.
Кому это важно
Прикладным программистам, которые делят большие функции на части и решают, где держать проверки и циклы. Тем, кто пишет код с тяжёлыми циклами и думает о векторизации. Тем, кто интересуется устройством оптимизаторов запросов или связью программирования с теорией категорий. К ИИ материал отношения не имеет: это общая инженерная тема.
Как это применить
Практический смысл, по тексту: если функция ветвится по входу, подумайте, не перенести ли ветвление к вызывающему коду и не выразить ли предусловие типом (Walrus вместо Option
Можно ли доверять
Это авторское эссе, в тексте не приведено ни замеров, ни бенчмарков, поэтому выигрыш по скорости не подтверждён цифрами: заявления о векторизации и о кэш-дружелюбности внутреннего цикла даны как рассуждения. Размер пакета «около тысячи кортежей», приблизительная величина и не привязана к конкретной СУБД. Правила про законность преобразований (вывод закона filter/map через естественность catMaybes), формальные выкладки, их можно проверить самостоятельно.
Риски и подводные камни
Главные ограничения названы в самом тексте. Условие, зависящее от элемента, нельзя вынести из цикла: его можно лишь перенести на границу и записать в типе. Перестановка filter и map не всегда дешевле, пока p . f не упрощается до дешёвого предиката. Терминология легко запутывает: в запросах «вниз по дереву» означает «раньше», что соответствует «вверх» в самой идиоме. Часть «fors вниз», про стоимость, а не про эквивалентность, поэтому выигрыш зависит от того, как велика цена подготовки на вызов.
«Поднимай if вверх, а for опускай вниз.»
— Tiger Style, документ TigerBeetle