Skip to content

Instantly share code, notes, and snippets.

@j-cr
Created August 3, 2018 19:59
Show Gist options
  • Save j-cr/8a85bcc9089da5734e3efa2ab2ddae33 to your computer and use it in GitHub Desktop.
Save j-cr/8a85bcc9089da5734e3efa2ab2ddae33 to your computer and use it in GitHub Desktop.

(https://hardcode.fm/2018/07/28/episode001.html 00:40:57)

Часть подкаста про имена и смыслы - охуенная! Не знаю, куда написать фидбэк, поэтому пишу сюда.

Я тоже давно думал про эту дихотомию между многозначными (но знакомыми) и однозначными (но незнакомыми) именами. Мне кажется, это два разных стиля мышления: условно "математический" и условно "поэтический".

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

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

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

В чисто практических терминах разница в этих подходах в том, что на загрузку в мозг математики требуется время: надо взять и по кирпичикам выстроить в голове ту же семантическую сеть, которая декомпозирована автором в отдельные определения на бумаге (но зато можно быть уверенным, что коммуникация будет (почти) однозначной и сеть получится (почти) точно такой же); музыка же или (в меньшей мере, но все же) поэзия воспринимаются конкретно, прямо сейчас, потому что дергает уже существующую семантическую сеть (или не дергает, если автор промахнулся с ожиданиями относительно читателя - то есть это такой lossy compression).

Как это все относится к программированию? Казалось бы, очевидно, что математический путь нам должен быть ближе - но! На самом деле цели взаимодействия с построенной системой у разработчика и математика (или студента, например) разные: нам не нужно загружать всю систему себе в голову, нам (обычно) нужно локализовать какой-то конкретный дата\контрол флоу, внести какое-то локальное изменение, найти дающие нужный нам результат входы в систему. Никто не изучает легаси просто ради того, чтобы любоваться спрятанными в нем прекрасными структурами и абстракциями (хотя ладно, наверняка и такие мазохисты существуют); обычно нам нужно (не имея полной картины!) взять и сделать, чтоб работало (и притом еще вчера).

И вот здесь как раз в дело вступает контекст: это и соглашения по именованию, и общие идиомы, и паттерны, и распространенные архитектуры - наконец, мы пытаемся буквально использовать метафоры из реальной жизни (по сути весь ООП (был) построен на этой посылке (спойлер: такие метафоры обычно не работают)). Если X на самом деле Y, но очень похож на Z, то зачастую мы назовем его Z - просто потому, что это сэкономит время 95% юзеров, которые будут использовать его именно в этом качестве (ну, или это мы сейчас думаем, что будут).

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

  • Меньше LoC - меньше возможных багов. Меньше имен - меньше проблем с их придумыванием. Если вместо введения нового имени можно тривиально и очевидно соединить уже имеющиеся, то лучше так и сделать. В этом плане любопытен APL и tacit programming вообще (дисклеймер: я не предлагаю писать рейтрейсеры в 8 строчек на j и прочие прелестные извращения): + - сложить числа, /+ - суммировать список, и так далее. По факту имя является реализацией, а потому абсолютно точно, понятно и однозначно. Это хороший идеал (но непонятно, как его достичь).
  • Туда же: явное лучше неявного. Structural лучше, чем nominal. Композиция лучше, чем комбинаторный взрыв. Open maps лучше, чем records и javabeans. Если можно вместо именования данных указать сами данные, то лучше так и сделать.
  • Если let <имя> = <функция> 42 понятней, чем просто <функция> 42, то это плохая функция. То же и с аргументами. В идеале, функция должна однозначно определять свои аргументы и результат. numToWhichWeAdd = 1, numThatWeAdd = 2, numToWhichWeAdd.plus(numThatWeAdd) не понятнее, чем 1 + 2. Если от введения временных переменных перед передачей их в функцию код становится понятнее, чем при прямой передаче аргументов без временных имен, то с кодом что-то не так. Если функция берет больше 2-3 аргументов, с функцией что-то не так.
  • Паттерны нужно формализовывать и называть уникальными именами. Нужно больше монад и трансдьюсеров (но только если все о них знают).
  • Не бояться придумывать новые слова.

aeThaex3

@razum2um
Copy link

razum2um commented Aug 4, 2018

Очень хорошие мысли, в целом разделяю это + несколько комментариев по ходу:

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

Просто в дополнение: а adoption языков "математического" подхода в целом ниже. Возможно от того, что это следствие образа мышления как пишущих код, так и людей, чьи требования формализуются и тех, кто их формализует; возможно, следствие исторического развития прикладных систем большей частью "снизу", "от железа"; а еще возможно, этого "общего контекста индустрии" просто не существует: "write once run everywhere" регулярно возникает и по большей части фейлится.

нам (обычно) нужно локализовать какой-то конкретный дата\контрол флоу, внести какое-то локальное изменение, найти дающие нужный нам результат входы в систему

+1. Это очень важный момент для меня. Постоянно вспоминаю, что нам обещали с ligthtable (хотя может я тот видео-концепт понял по-своему) и хочу сделать что-то, что поможет увидеть в системе реальный execution flow системы имея конкретные данные на входе. (Может есть такие системы?) Также это должно сделать сверхэффективным прогон acceptance тестов. Change control flow -> run only tests which implicitly touch changed logic flow-node. И связанная идея, чтобы рантайм хоть немного осознавал себя (also see https://github.com/razum2um/cljsh)

И еще немного адвокатства дъявола, just fyi

Меньше LoC - меньше возможных багов

По-моему, именно loc слабо коррелирует с именованием (за исключением случаев, когда style guide вынуждает из-за длины строки) и числом имен (кроме случаев, когда point-free style сочетается с тем что assignment/binding != expression, как в python/js). Возможно, лучше говорить про соотношения объемной доли облачка на строчку. Забавный флешбек из прошлого http://razum2um-gentoo.blogspot.com/2010/04/python-one-line-scripts.html

Меньше имен - меньше проблем ... По факту имя является реализацией

С одной стороны отлично понимаю, что имеется в виду и согласен, но довести до абсурда, то в каждом имени должны просвечивать имена регистров итд.

Если от введения временных переменных перед передачей их в функцию код становится понятнее, чем при прямой передаче аргументов без временных имен, то с кодом что-то не так

Опять же понимаю и согласен, но с другой стороны, для полноты картины такой случай: есть (a + b) / (c - d) и есть sum / diff. Это же как раз пример где Structural лучше, чем nominal. По факту, скобки в первом как раз намекают на структуру, но оно остается nominal

явное лучше неявного. Structural лучше, чем nominal

Кстати, мне кажется, противоречие - nominal это как раз явное, а любая структура/иерархия -> абстракция/неявность

@j-cr
Copy link
Author

j-cr commented Aug 5, 2018

Просто в дополнение: а adoption языков "математического" подхода в целом ниже.

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

что-то, что поможет увидеть в системе реальный execution flow системы имея конкретные данные на входе

Ну, в лайттейбл это в принципе и есть (в cider, кстати, тоже добавили: #light над функцией или cider-enlighten-mode), но проблема там в том, что не всегда понятно, как именно этот реальный execution flow показать пользователю. Грубо говоря, (map my-fn (range 9000)) - что показывать для my-fn?

В этом свете, кстати, очень интересно выглядит spec - я помню, что видел где-то тулзу для динамической генерации спеков на основе данных, бегающих внутри программы. То есть если для my-fn редактор покажет что-то вроде :arg (s/and number? #(< 0 % 9000)) - а в реальном коде это будет скорее набор ключей с предикатами - то это будет уже кое-что интересное. А если сюда еще добавить эдакий аналог shrinking'а, чтобы ide сама искала и подсвечивала отдельные выбивающиеся из общей картины данные, мм...

(also see https://github.com/razum2um/cljsh)

Это прикольная идея, да! Какие-то тулзы для синхронизации состояния и текста точно нужны, правда лично мне чаще хочется обратного: чтобы состояние синхронизировалось с текстом - без полной перезагрузки неймспейса и полностью прозрачно. Типа, вырезал блок defn - соответствующая var удалилась (а точнее "затенилась", с бэкапом рантаймового состояния на ней), все инвалидированные вызовы подсветились. Вставил обратно - все вернулось назад.

Еще можно попробовать как-то визуализировать текущее состояние неймспейса - в либе вроде этого нет, но я так понимаю это должно быть несложно добавить. Хотя тут вопрос больше в том, как сделать удобный интерфейс для такой визуализации. Да, а еще можно вообще попробовать отойти от файликов в фс и делать save в какой-нибудь глобальный регистр тегированных функций - глобальный, распределенный и на блокчейне, разумеется ;) Ну если серьезно, то тут и вправду много интересных штук можно придумать.

По-моему, именно loc слабо коррелирует с именованием

Я тут плохо выразился. Имелось в виду по аналогии: меньше кода = меньше багов (условно), так же и меньше имен = меньше плохих имен.

С одной стороны отлично понимаю, что имеется в виду и согласен, но довести до абсурда, то в каждом имени должны просвечивать имена регистров итд.

Да, согласен, есть такое. Поэтому и "непонятно, как его достичь" ;)

есть (a + b) / (c - d) и есть sum / diff.

Тут зависит от контекста. Откуда sum и diff взялись? Идея в том, чтобы не было такого: sum = compute_sum(...), и так далее. Более того, даже если реализация нашего сompute_sum вроде бы совпадает с +, то лучше вынести его в отдельную функцию (и назвать ее как раз sum; в tacit-стиле, кстати, итоговое выражение останется тем же: sum / diff), потому что локальное имя - это непрозрачная конструкция, оно внутри black box'а объемлющей его функции. Что если нам понадобится добавить обработку ошибок, логирование, дополнительные констрейнты на значение, ну или просто посмотреть, во что вычисляется сумма на данных инпутах? Когда у нас есть отдельная функция, мы ее можем просто взять и заинструментить, например, а с локальным именем нам придется прямо туда добавлять дебагпринты, spy, ассерты и так далее. То есть мы как бы вроде бы и создаем отдельную абстракцию с помощью ввода нового имени, но при этом она остается намертво вмурована в окружающий ее контекст - получается мы как бы ее создаем и сразу выкидываем. А отдельная функция - это уже "материальная" абстракция, которую понимает наша среда, которая может расширяться независимо от остальных частей, которая имеет глобальное уникальное имя и т.п. В итоге получается более прозрачная система, которую проще просветить насквозь и потыкать палочкой (в каком-то смысле тут есть аналогия с лисп\смолток и сишным подходами).

nominal это как раз явное, а любая структура/иерархия -> абстракция/неявность

Я имел в виду, по аналогии с nominal/structural typing, что идентичность на основе структуры чаще гибче и понятнее. Ну и тут абстракция == введение нового имени. Грубо говоря, вот можно написать тип Reducer a,b, а можно написать a b -> a. У меня ощущение, что общим местом считается, что введение таких алиасов на каждый чих - это абсолютное добро. Но при этом Reducer a b, во-первых, тупо длиннее, а во-вторых - непонятнее, потому что мне нужно пойти и почитать документацию, чтобы узнать, что это такое. А a b -> a - это просто функция, любой читающий это сразу точно знает, что это такое и как оно работает. Аналогия с джавабинами и простыми мапами, "общий контекст индустрии". Или, не знаю, например нам в качестве аргумента нужна entity вида :user/email, :admin/group. Мы можем создать иерархию User->Admin (а потом нам понадобятся, например, боты-админы и все поломается), мы можем создать композицию с кривым именем типа UserThatIsAdmin (и так на каждую возможную комбинацию), а можем просто так и написать, что нам нужна "entity вида :user/email, :admin/group", без дополнительных имен. Пример так себе, но идея понятна, да?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment