/
Блог Хекслета
/
Код
/

Восхождение по бесконечной лестнице абстракций

Восхождение по бесконечной лестнице абстракций

13 октября 2017 г.

9 минут
2
Восхождение по бесконечной лестнице абстракций

Это перевод статьи Climbing the infinite ladder of abstraction от Алексис Кинг.

Я начала программировать ещё в начальной школе.

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

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

Кирпичная стена невыразительности

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

public String getName() { return this.name; } public void setName(String name) { this.name = name; }

Это немного дурацкий пример, учитывая, что геттеры и сеттеры Java на этом этапе — что-то вроде боксёрской груши, но я их писала и раздражалась из-за них! Я изучила объектно-ориентированные шаблоны разработки и зарылась в книги, форумные ветки, блоги и вопросы на Stack Overflow о том как структурировать код, чтобы избежать спагетти, но как бы я ни старалась, всё что я писала, выглядело подозрительно похожим одно на другое.

Всё было максимально выводящим из себя, потому что с какой бы стороны я не подходила к проблеме, в результате получался стереотипный, тяжёлый бардак. Основная причина по которой я начала программировать, была избавиться от подобных проблем, так что оставалось? Всё очевиднее становилось одно — Java нужно бросать и пробовать что-то другое. Я начала изучать два совершенно разных языка программирования, JavaScript и Objective-C — оба мне понравились по разным причинам.

Когда я изучила JavaScript, мне открылись замыкания и функции первого класса — они меня заворожили. Через jQuery я узнала о его способности собирать приятные в использовании API и избавилась от скучного, "тяжёлого" чувства, которое распространял везде Java. Благодаря Objective-C я узнала о силе более динамической объектной системы с интересным синтаксисом и способностью справляться с "передачей сообщения" на более высоком уровне, чем в Java.

Оба этих языка были неполноценны, как и все языки, но они укоренили во мне мысль, что языки программирования могут сильно влиять на способ мышления при решении задач. Они предложили мне квест: найти язык, который даст возможность навсегда избавиться от стереотипности.

Открытие Lisp

Последующие несколько лет я училась ценить маленькое, простое ядро JavaScript, несмотря на неприязнь к его объектной системе и слабые способности в интуитивно-понятном моделировании данных. Я тщательно разобралась в его истории и обнаружила, что на его дизайн сильно повлиял малоизвестный, ограниченный язык Scheme и ещё более неизвестный язык Self, и я стала иногда задумываться: что, если объединить идеи этих языков, исключив некоторые компромиссы на которые пошёл JavaScript.

Эта идея жила в моём сознании пару лет, и хоть я и пыталась играть со Scheme пару раз, он был слишком недоступен для меня. Я привыкла к языкам с мощными, простыми в использовании IDE и когда я оказалась наедине с исполняемым файлом в командной строке и довольно скудной документацией, я потерялась — с чего начать. Если даже я смогу выполнить вычисление в REPL, куда дальше идти? Я начала программирование с игр, потом — веб-сайтов. Что мне было делать со Scheme?

Язык (а вернее, его неудачная экосистема) оказался для меня в том возрасте слишком пугающим, но идея гомоиконности Lisp запала мне в душу. Я начала писать свой язык программирования, высокодинамичный Lisp с прототипичной объектной системой, который называется Sol. Я работала над ним около года, и когда закончила, у него был не хилый комплект функциональности: в нём были лямбды, макросы, полнофункциональная объектная модель и модульная система в стиле CommonJS, в комплекте с возможностью динамически импортировать произвольные расширения С. Это был самый большой проект, над которым я когда-либо работала, и когда я закончила, была вполне довольна.

К сожалению, он также был плачевно медленным.

Я пошла в местный университет чтобы найти людей, которые могли бы прокомментировать и, возможно, направить меня в нужную сторону. Тогда кто-то рассказал мне ещё об одном малоизвестном языке Racket. Почти в то же время мне подсказали совершенно непохожий язык Haskell. Это была неизведанная для меня территория, и на какой-то период я не особо углублялась в оба этих языка. Со временем я нырнула в них всерьёз и то, что я обнаружила, сильно изменило мой взгляд на программирование.

Путь в сложность

Приблизительно три года спустя, сегодня, я работаю на Haskell и провожу большую часть свободного времени программируя на Racket. Эти языки оставили во мне отпечаток и хоть я и изучила с того времени намного больше, постоянно нахожу себя противящейся основному потоку и возвращаюсь к функциональному программированию, гигиеничным макросам, и, возможно, самой мощной системе типов в мире в production-ready языке программирования.

Я начала понимать кое-что ещё: языки, к которым я привыкла — очень сложные.

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

Почему?

Иногда я говорю со своими коллегами и они ужасаются типам терминов, которые я использую. "Зачем вам может когда-либо понадобиться нечто названное монадой?" — спрашивают они в недоумении. "Макросы запутывают" — спорят они. "Явное — лучше".

Конечно, я не согласна, но почему? Чем я сдалась? Если мои друзья-программисты не понимают то, что я пишу, есть ли в этом какая-то польза?

Годами я искала язык программирования, который ликвидирует шаблонность, который позволит мне выражать идеи сжато и чисто, который позволит превратить сложные задачи в простые, и я открыла абсолютно разные подходы для решения этих вопросов. У Racket есть макросы, а у Haskell его навороченная система типов. Обе эти концепции на световые расстояния далеки от того, где я была десять лет назад, строча повторяющийся Java-код, который в результате производил очень мало, а мои задачи не были решены.

Racket так мало знает о моей программе — он не может понять, что я подразумеваю, основываясь на типе того, чем я оперирую, потому что он (почти) динамически типизированный. Я до сих пор должна разбираться сама и записывать те вещи, которые кажутся лишними, потому что компьютер не достаточно умный, чтобы выяснить "очевидное". Точно так же Haskell очень ограничен — компилятор не может проследить происхождение условий, которые я могу решить в уме за секунды, а его синтаксис не расширяемый, как у Racket. Каждый день я всматриваюсь в нагромождения монадных вычислений, и правда — чего я достигла?

Совершенствоваться, но никогда не владеть

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

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

С другой стороны, языки вроде Haskell и Racket склонны размывать границу. Мне кажется, что у меня довольно чёткое понимание ядра Haskell, но насколько я разбираюсь в ленивых вычисления? Осознаю ли я полноценно семейства типов? Что насчёт TypeInType? Я вынуждена признать, что не полноценно понимаю Haskell, а ещё слабее — большую часть расширенной теории категорий, с помощью которой сформированы некоторые из его самых мощных библиотек. Racket умудряется размывать границу между языком и библиотекой ещё сильнее, и хоть я и вижу себя приличным Racket-пользователем, я не имею никакого чёткого понимания всей запутанности макросов Racket-системы.

Это особенно очевидно для меня на работе, учитывая то, что я пишу на Haskell в команде. Точно так же, как я когда-то писала на Java, я заканчиваю решениями, которые не удовлетворяют меня. И я прихожу к нарастающе-мощным конструкциям, чтобы рассеять свои сомнения. Временами я ловлю себя на том, что взламываю DataKinds, и иногда это помогает справиться с проблемой, но цена у этого — я загоняю в тупик тех, с кем работаю.

Каждый раз, когда я взбираюсь на следующую ступень абстракции, все, кто чуть ниже меня (хотя мы все — на огромной высоте!) оказываются в замешательстве. В худшем случае люди могут считать, что причина их замешательства в их собственной неадекватности или недостатке опыта. Это ужасно, особенно когда я знаю об этом, а к тому времени, когда их непонимание вскроется, я уйду играть с новой игрушкой: комонадами, типами семейств или classy lenses. Цикл продолжится и никто никогда не будет по-настоящему удовлетворён — я всегда буду искать новую абстракцию, которая упростит задачу, и те, кто в паре шагов от меня будут еле справляться.

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

В конце-концов всё это имеет (хотя бы небольшое) значение

Было бы здорово подумать об этом и сказать: "Давайте, наконец, разорвём порочный круг. Давайте прекратим обманывать себя, что способы решения выдуманных нами проблем, решают что-то в действительности". Было бы отлично, если бы я могла себе это сказать… но, к сожалению, не могу.

Самый страшный момент — я думаю, что всё это действительно того стоит.

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

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

Большинство программистов, даже те, которые не видели BASIC, могут разобраться, что делает этот отрезок кода:

10 INPUT "What is your name: "; U$ 20 PRINT "Hello "; U$

С другой стороны некоторые, вероятней всего, не смогут понять этот:

-- | A class for categories. -- id and (.) must form a monoid. class Category cat where -- | the identity morphism id :: cat a a -- | morphism composition (.) :: cat b c -> cat a b -> cat a c

Меньшинство новых программ написаны на BASIC и множество на Haskell.

Даже один из самых популярных, быстрорастущих языков программирования в мире, JavaScript, язык, который рассматривается относительно доступным в сравнении с Haskell, вряд ли может быть понятным для программиста незнакомого с его синтаксисом:

export const composeWithProps = curry((a, parentProps, b) => { const composed = childProps => createElement(a, parentProps, createElement(b, omit(['children'], childProps), childProps.children)); // give the composed component a pretty display name for debugging composed.displayName = `Composed(${getDisplayName(a)}, ${getDisplayName(b)})`; return composed; });

Движение в сторону всё более специализированного синтаксиса не настолько плохо — оно может свидетельствовать о более чётком, предметно-ориентированном способе мышления, но в то время как это может наращивать продуктивность опытного программиста, оно будет сильно озадачивать новичка.

Именно эта деталь — суть моего страха: всегда ли мы думаем о том, для кого делаем оптимизацию? У меня нет моральной проблемы с написанием ёмкого кода для закалённых программистов, в конце-концов краткость — один из основных способов делать код более удобным для чтения (многословность — враг понимания). Однако, когда эта ёмкость стоит понимания новичков, всё обесцвечивается. Нет ничего неправильного в том, чтобы писать высоко-оптимизированный код для саморазвития и понимания, а группа способных на это людей может образовать очень продуктивную команду. Но важно понимать, что другие скорее всего не поймут и без желания тратить время и деньги на образование, умные исполнительные люди будут с трудом вникать в концепции и скорее всего мало будут в них заинтересованы.

Реактивный антиинтеллектуализм и поиск умеренности

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

Математическая нотация — не идеальна, не идеальней плотного Haskell, тяжело метапрограммного Руби, или полного IIFE JavaScript. Но она служит определённой цели и иногда подробное разъяснение не принесёт ни практически осуществимого, ни теоретического улучшения. Программисты не воспримут мягко, если их попросить написать весь код в стиле прозы, и им не понравится, если им скажут, что использование функций высокого порядка вроде map должно быть запрещено, потому что они слишком сбивают с толку и не достаточно самоочевидны.

Я рада, что люди фокусируются сегодня на юзабилити и доступности больше чем когда-либо, и мне кажется, что это одна из тех сфер в которых я сильнее всего заинтересована. Я хочу взять лучшее из обоих миров: я целюсь писать код в более лаконичном, точном стиле, но я пытаюсь и создаю интуитивно понятные интерфейсы с ошибками, которые люди способны будут понять. Враждебная для пользователя, но технически-функциональная библиотека в моём представлении дефектна, и я бы с удовольствием выводила баг-репорт о запутанном API или сообщение об ошибке.

Абстракция — это то, что делает программирование возможным и безусловно это то, что делает большую часть современных технологий возможными. Это то, что позволяет людям вести машину, не зная как работает двигатель внутреннего сгорания и что даёт возможность просматривать веб-страницы не разбираясь детально в работе сетевых протоколов. В программировании абстракция служит той же цели. Конечно, как и у всех инструментов, у абстракции могут быть разные цели: среднестатистический пользователь не освоит Photoshop за день, но продвинутый пользователь не удовлетворится Paint.

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

Честно, я не знаю стоят ли Racket и Haskell их запутанности. Возможно, писать простой, консистентный код, которые другие могут понимать — это главное. Я надеюсь, что в любой команде найдется место для более мощного языка. Но наверное не спроста некоторые языки популярнее других.

В целом, я просто пытаюсь всегда осмысливать компромиссы, на которые иду, плюсы, которые я получаю и влияние на тех, с кем работаю. Я продолжу искать абстракции в будущем и надеюсь, что это не пустая трата времени.

Natalia Bass

8 лет назад

2

Категории

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845