Около 2 лет назад, после того как я писал императивный код около 16 лет, я начал изучать, исследовать и использовать более декларативные формы кодирования при создании корпоративного программного обеспечения, а также для решения алгоритмов конкурентного программирования. Для меня результат был более ясным, более коротким кодом, с гораздо меньшим количеством ошибок и проблем. Возьмем, к примеру, написание собственного кода в .NET с использованием циклов для упорядочивания списка в зависимости от возраста людей, которые он содержит, и использование операторов LINQ, чтобы сделать это за вас. В первом сценарии вы делаете все самостоятельно, используя циклы for, while или foreach. Во втором сценарии вы просто говорите, что хотите сделать, используя OrderBy или OrderByDescending, в одной строке кода, и .NET framework сделает всю работу за вас. Он лаконичнее, яснее и понятнее, и, поскольку Microsoft уже протестировала этих операторов, вы знаете, что они работают. Я помню, как однажды во время сеанса проверки кода мы смогли преобразовать метод с более чем 20 строками кода и двумя ошибками в 4 или 5 строк кода без каких-либо ошибок за считанные минуты, используя LINQ.

В JavaScript вы можете сделать нечто подобное, используя такие методы Array.prototype, как map, reduce и filter. Таким образом, вы можете избавиться от всего беспорядка, связанного со сложными вложенными циклами, где в каждой строке кода вы указываете среде выполнения, как что-то делать, вместо того, чтобы просто говорить, что вы хотите сделать.

Открытость и знание более декларативных подходов также может стать разницей между получением работы своей мечты или отказом. Я помню любопытную историю о том, как Google отверг Макса Хауэлла, потому что он не смог инвертировать двоичное дерево во время одного из интервью (https://www.quora.com/Whats-the-logic-behind-Google-rejecting-Max -Howell-the-author-of-Homebrew-for-not-be-can-to-invert-a-binary-tree ). Макс Хауэлл - создатель программы Homebrew, которую, как я слышал, используют многие разработчики в Google. Он должен быть выдающимся разработчиком, и я думаю, что Google мог бы извлечь из этого выгоду, и очень жаль, что двоичное дерево встало на его пути, чтобы получить эту работу.

Инвертировать двоичное дерево может быть сложно, если вы попытаетесь использовать итеративно-императивное решение, и вы не помните или не знаете, как это сделать правильно. Но если вы выберете декларативный подход, используя рекурсию, вы можете решить проблему за считанные минуты с помощью очень простых 6 или 7 строк кода на таких языках, как JavaScript и C #. Теперь вы можете подумать, что рекурсия - это зло, и она может поглотить ваши кадры стека, если вы не используете язык с поддержкой оптимизации хвостового вызова; это отдельное обсуждение, здесь я как раз говорил о том, чтобы получить работу своей мечты.

Поскольку инвертирование двоичного дерева - это просто и не дает возможности увидеть всю красоту декларативного мышления и программирования, я выбрал для этого более сложную задачу. По данным leetcode.com, это одна из наиболее часто задаваемых проблем во время собеседований в таких крупных компаниях, как Google, Facebook, Amazon, Microsoft, Apple, JP Morgan и т. Д. На самом деле меня попросили решить эту проблему во время собеседования около двух лет назад, и даже когда я смог это сделать, мне было немного стыдно за свое решение, содержащее 3 вложенных цикла for со всеми эти переменные i, j и k повсюду (это очень распространенное решение, которое вы найдете в Интернете).

Решение проблемы: комбинации букв в номере телефона (взяты с сайта leetcode.com, некоторые также называют T9) :

Проблема очень очевидна. Обратите внимание, что количество кнопок, которые вы нажимаете, определяет длину строк внутри окончательного массива. Для «23» есть 9 возможных комбинаций, а для «735» - 36 (pdj, pdk, pdl….), И, конечно, число увеличивается по мере того, как вы нажимаете больше кнопок.

Сначала я решу проблему на JavaScript (язык, который я люблю еще до появления ECMAScript 6), а затем на C # (язык, к которому я очень близок с 2004 года).

Решение на JavaScript:

Мы будем использовать объект, похожий на словарь, для сопоставления чисел и букв:

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

Теперь мы добавим строку кода для преобразования ввода типа «23» в массив массивов, содержащий все буквы, относящиеся к каждому числу, например: [['a', 'b', 'c'], ['d', 'f', 'g']].

И, наконец, я добавлю фрагмент кода, который генерирует все возможные комбинации (полная реализация функции):

Если вы не знакомы с принципами работы map и reduce, возможно, не будет очевидно, что происходит внутри этой функции. Их объяснение стоило бы целой статьи, а таких в Интернете уже много. Я не буду отслеживать код, поскольку моя цель не в том, чтобы вы знали, как решить конкретную проблему, а в том, чтобы противопоставить этот способ кодирования более традиционному императивному виду. Чтобы помочь в этом, я оставляю вам ссылку на один из них: https://repl.it/@nickangtc/javascript-Letter-Combinations-of-a-Phone-Number.

За очевидным более кратким и кратким кодом, показанным в этой статье, кроется самое большое из всех преимуществ - быть более декларативным, чем императивным, когда у вас под рукой есть клавиатура, маркер или карандаш, а также уменьшенное количество ошибок. Если вы к этому не привыкли, это может показаться сложнее сделать или понять, но поверьте мне, по мере того, как вы к этому привыкнете, вам будут все меньше и меньше нравиться циклы for / while / foreach.

Я хотел упомянуть три вещи; если вас беспокоит производительность map / reduce / filter по сравнению с традиционными циклами в JavaScript, как я уже упоминал в одном из моих в предыдущих статьях, посмотрите это https://www.youtube.com/watch?v=g0ek4vV7nEA, это, скорее всего, изменит ваше мнение о том, что, как вы думали, вы знали о производительности JavaScript. Во-вторых, даже когда мы используем проблему кода в этой статье, идеи, лежащие в ее основе, полностью действительны и применимы в корпоративном программном обеспечении, и одна любопытная вещь заключается в том, что после перестройки моего мозга на разные способы кодирования в JavaScript я также стал лучший разработчик C # /. NET. Наконец, я хотел сказать, что эту проблему можно решить с помощью рекурсии, если вы разработчик Haskell, то вы уже об этом знали; и если вы разработчик Python, вы, вероятно, знаете, что можете решить эту проблему с помощью одной строчки кода (не считая, конечно, словаря сопоставлений), используя аналогичный подход, и это хорошо для вас!

Затем я оставлю вам свою реализацию на C #. Я должен признать, что мне он не нравится так сильно, как JavaScript. Из-за строго типизированной природы языка требуется немного больше необходимого кода, беспорядка и ограничений, но все же я возьму его на себя в версии i, j, k.

Предполагая, что у нас есть следующий словарь как часть поля class:

Возможная реализация метода:

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

Удачного кодирования!