G
logo
Glow labs
Создание распределенной ОС для людей
G
logo
Glow labs

Самая большая ошибка D

Самая ‎большая‏ ‎ошибка ‎D ‎— ‎транзитивные ‎квалификаторы

Не‏ ‎стоит ‎заблуждаться,‏ ‎thread-local‏ ‎по ‎умолчанию ‎это‏ ‎замечательно! ‎Но‏ ‎shared ‎должен ‎был ‎быть‏ ‎квалификатором‏ ‎хранения, ‎а‏ ‎static ‎и‏ ‎простые ‎глобальные ‎переменные ‎остались ‎бы‏ ‎неявно‏ ‎с ‎квалификатором‏ ‎хранения ‎thread-local.

Прояснив‏ ‎этот ‎момент, ‎давайте ‎перейдем ‎к‏ ‎моим‏ ‎причинам‏ ‎для ‎утверждения,‏ ‎что ‎транзитивные‏ ‎квалификаторы ‎являются‏ ‎единственной‏ ‎причиной ‎большинства‏ ‎фундаментальных ‎проблем ‎языка ‎D.

Транзитивные ‎квалификаторы‏ ‎взятые ‎вместе‏ ‎с‏ ‎«wildcard» ‎квалификатором ‎inout‏ ‎являются ‎самым‏ ‎большим ‎ломающим ‎изменением ‎при‏ ‎переходе‏ ‎от ‎D1‏ ‎к ‎D2‏ ‎с ‎огромными ‎затратами, ‎которые ‎все‏ ‎еще‏ ‎«оплачиваются» ‎тк‏ ‎они ‎дают‏ ‎стабильный ‎поток ‎багов ‎компилятора, ‎хотя‏ ‎и‏ ‎в‏ ‎основном ‎решенный‏ ‎к ‎настоящему‏ ‎моменту. ‎Побочные‏ ‎эффекты‏ ‎транзитивности ‎не‏ ‎только ‎очевидны ‎в ‎пользовательских ‎базах‏ ‎кода, ‎они‏ ‎так‏ ‎же ‎(если ‎не‏ ‎хуже) ‎проникают‏ ‎в ‎виде ‎изменений ‎в‏ ‎компиляторе.

Продвигаемые‏ ‎как ‎требование‏ ‎для ‎поддержки‏ ‎функционального ‎программирования ‎и ‎гарантий ‎чистых‏ ‎(pure)‏ ‎функций. ‎Что‏ ‎ж, ‎давайте‏ ‎разобьем ‎это ‎утверждение. ‎Во-первых ‎я‏ ‎программировал‏ ‎на‏ ‎Scala, ‎часто‏ ‎используемой ‎как‏ ‎«заменитель» ‎Haskel‏ ‎на‏ ‎JVM, ‎при‏ ‎этом ‎многие ‎идиомы ‎прекрасно ‎переносятся.‏ ‎Смотрите ‎на‏ ‎Cats,‏ ‎Scalaz ‎и ‎связанные‏ ‎проекты ‎для‏ ‎примеров ‎пуристического ‎FP ‎на‏ ‎Scala.‏ ‎Здесь ‎как‏ ‎и ‎в‏ ‎других ‎языках, ‎которые ‎я ‎к‏ ‎сожалению‏ ‎не ‎знаю‏ ‎достаточно ‎хорошо,‏ ‎мутабельность ‎решается ‎очень ‎прагматично:

  • val ‎это‏ ‎обозначение‏ ‎тип‏ ‎переменной ‎по‏ ‎умолчанию, ‎означающее‏ ‎неглубокий ‎const‏ ‎(непереприсваиваемое)
  • var‏ ‎это ‎мутабельное/переприсваиваемоме‏ ‎обозначение, ‎сильно ‎порицаемое ‎инструментами ‎экосистемы‏ ‎(особенно ‎подсвечивается‏ ‎в‏ ‎редакторах ‎и ‎тд)
  • коллекции‏ ‎предоставляют ‎иммутабельность‏ ‎через ‎интерфейс ‎— ‎здесь‏ ‎просто‏ ‎нет ‎write/remove‏ ‎операций ‎в‏ ‎API ‎List-а.
  • Мутабельные ‎коллекции ‎предоставляют ‎надмножество‏ ‎иммутабельного‏ ‎API
  • Структуры ‎по‏ ‎построению ‎состоят‏ ‎из ‎val ‎или ‎var, ‎но‏ ‎case‏ ‎class-ы‏ ‎повседневная ‎сокращенная‏ ‎нотация ‎для‏ ‎просто ‎данных‏ ‎состоит‏ ‎только ‎из‏ ‎val-ов.

Если ‎мы ‎прибавим ‎к ‎этому‏ ‎явный ‎shared‏ ‎квалификатор,‏ ‎финальный ‎гвоздь ‎в‏ ‎гроб ‎багов‏ ‎concurrency ‎— ‎глобальные ‎переменные‏ ‎в‏ ‎основном ‎будут‏ ‎закрыты. ‎В‏ ‎Scala ‎это ‎было ‎бы ‎очень‏ ‎неудобно‏ ‎ввести ‎shared‏ ‎в ‎основном‏ ‎потому, ‎что ‎ей ‎надо ‎бесшовно‏ ‎интегрироваться‏ ‎с‏ ‎Java/JVM ‎и‏ ‎вводить ‎плохо‏ ‎переводимые ‎квалификаторы‏ ‎хранения‏ ‎/ ‎поведения‏ ‎вызвало ‎бы ‎расхождение ‎импеданса. ‎D‏ ‎не ‎ограничен‏ ‎подобными‏ ‎соображениями.

Не ‎полностью ‎уничтожены,‏ ‎но ‎с‏ ‎установленными ‎легкими ‎и ‎корректными‏ ‎умолчаниями,‏ ‎а ‎также‏ ‎с ‎остальным‏ ‎языком ‎и ‎инструментарием ‎подталкивающими ‎в‏ ‎правильном‏ ‎направлении ‎это‏ ‎делает ‎90%‏ ‎работы ‎без ‎ограничения ‎нишевых ‎случаев.‏ ‎Таких‏ ‎как,‏ ‎например, ‎lock-free‏ ‎программирование, ‎которое‏ ‎очень ‎болезненно‏ ‎писать‏ ‎с ‎транзитивным‏ ‎shared ‎в ‎D ‎— ‎никуда‏ ‎не ‎дется‏ ‎от‏ ‎привидения ‎типов ‎к‏ ‎thread-local ‎мутабельным‏ ‎данным. ‎Проблему ‎легко ‎изложить‏ ‎—‏ ‎многие ‎lock-free‏ ‎алгоритмы ‎держаться‏ ‎на ‎идее ‎получения ‎владения ‎чем-то‏ ‎через‏ ‎операцию ‎CAS‏ ‎(или ‎фиксацию‏ ‎ваших ‎локальных ‎изменений). ‎По ‎самому‏ ‎определению‏ ‎она‏ ‎переводит ‎shared‏ ‎в ‎thread-local.‏ ‎Однако ‎никто‏ ‎не‏ ‎уведомил ‎нашего‏ ‎друга ‎компилятора ‎о ‎подобных ‎договоренностях‏ ‎и ‎я‏ ‎не‏ ‎вижу ‎твердых ‎планов‏ ‎(и ‎никаких‏ ‎сообщений ‎об ‎их ‎потенциале)‏ ‎в‏ ‎этом ‎направлении.

Но‏ ‎что ‎насчет‏ ‎блокировок, ‎я ‎слышу ‎ваш ‎вопрос.‏ ‎Та‏ ‎же ‎история‏ ‎в ‎основном‏ ‎— ‎вы ‎берете ‎блокировку ‎shared‏ ‎объекта‏ ‎и‏ ‎… ‎ничего‏ ‎не ‎происходит,‏ ‎он ‎все‏ ‎еще‏ ‎shared ‎и‏ ‎только ‎CAS ‎является ‎допустимой ‎операцией.‏ ‎При ‎всей‏ ‎своей‏ ‎сложности ‎система ‎типаов‏ ‎D ‎недостаточно‏ ‎умна, ‎чтобы ‎выучить ‎передачу‏ ‎владения‏ ‎и ‎вставить‏ ‎«умные ‎приведения‏ ‎типов» ‎(как ‎Kotlin) ‎или ‎что-то‏ ‎в‏ ‎этом ‎роде.

Посетив‏ ‎эти ‎две‏ ‎ниши, ‎мы ‎видим ‎мало ‎доказательств,‏ ‎что‏ ‎транзитивный‏ ‎shared ‎может‏ ‎предотвратить ‎достаточное‏ ‎количество ‎плохих‏ ‎дизайнов,‏ ‎чтобы ‎подтвердить‏ ‎свой ‎вес, ‎и ‎в ‎то‏ ‎же ‎время‏ ‎мешает‏ ‎наиболее ‎прямым ‎техникам.

Обмен‏ ‎сообщениями ‎сам‏ ‎по ‎себе ‎(например ‎библиотека‏ ‎std.concurrency)‏ ‎не ‎решают‏ ‎проблему ‎транзитивности‏ ‎— ‎вы ‎все ‎еще ‎приводите‏ ‎типы‏ ‎на ‎стороне‏ ‎приемника ‎(ведь‏ ‎отправитель ‎должен ‎отпустить ‎этот ‎объект,‏ ‎не‏ ‎правда-ли?‏ ‎Владение ‎—‏ ‎снова ‎не‏ ‎отслеживается ‎и‏ ‎не‏ ‎может ‎быть‏ ‎использовано).

Так ‎в ‎транзитивным ‎shared ‎мы‏ ‎разочаровались, давайте ‎перейдем‏ ‎к‏ ‎дизайнам ‎открывающимся ‎засчет‏ ‎транзитивной ‎иммутабельности.‏ ‎Может ‎быть ‎здесь ‎мы‏ ‎найдем‏ ‎сокровища, ‎из-за‏ ‎которых ‎мы‏ ‎столько ‎страдали.

Увы, ‎нет, ‎все ‎в‏ ‎целом‏ ‎так ‎же‏ ‎— ‎решение‏ ‎в ‎поисках ‎проблемы. ‎И ‎она‏ ‎создают‏ ‎кучу‏ ‎своих ‎проблем:

  1. Копия‏ ‎immutable ‎(T)‏ ‎может ‎передаваться‏ ‎как‏ ‎(неявно) ‎head-mutable,‏ ‎но ‎только ‎если ‎это ‎указатель‏ ‎или ‎слайс.‏ ‎Определяемым‏ ‎пользователем ‎типам ‎не‏ ‎везет ‎и‏ ‎это ‎делает ‎написание ‎общего‏ ‎кода‏ ‎корректного ‎относительно‏ ‎const ‎квалификатора‏ ‎прелестной ‎пыткой.
  2. Обобщенный ‎код ‎должен ‎использовать‏ ‎const‏ ‎(T) ‎как‏ ‎надтип ‎T‏ ‎и ‎immutable ‎(T). ‎К ‎сожалению‏ ‎это‏ ‎все‏ ‎еще ‎имеет‏ ‎проблему ‎#1.
  3. Шаблоны‏ ‎без ‎радикальной‏ ‎предосторожности‏ ‎и ‎Unqual!‏ ‎T ‎повсюду ‎будут ‎инстанцированны ‎до‏ ‎3x ‎(!)‏ ‎раз.‏ ‎Это ‎может ‎быть‏ ‎причиной ‎почему‏ ‎время ‎компиляции ‎не ‎лучше‏ ‎на‏ ‎рынке ‎(хотя‏ ‎и ‎среди‏ ‎быстрейших, ‎упущенная ‎возможность ‎прямо ‎здесь).
  4. inout.‏ ‎Это.‏ ‎Я ‎был‏ ‎с ‎D‏ ‎с ‎2010-2018 ‎и ‎это ‎было‏ ‎прекрасное‏ ‎безумное‏ ‎приключение, ‎но.‏ ‎Я. ‎Никогда.‏ ‎Полностью. ‎Не‏ ‎понимал.‏ ‎Как ‎inout‏ ‎может ‎работать ‎за ‎приделами ‎простейших‏ ‎(предусмотренных?) ‎примеров.‏ ‎Также‏ ‎имейте ‎в ‎виду,‏ ‎что ‎это‏ ‎смешивается ‎с ‎проблемой ‎#3.
  5. Пожалуй‏ ‎единственные‏ ‎типы ‎хорошо‏ ‎работающие ‎с‏ ‎immutable ‎это ‎указатели ‎и ‎массивы,‏ ‎спасибо‏ ‎#1. ‎И‏ ‎честно ‎говоря,‏ ‎простой ‎оберточный ‎тип ‎запрещающий ‎модификацию‏ ‎легко‏ ‎решает‏ ‎этот ‎сценарий,‏ ‎покрывая ‎80+%‏ ‎применения ‎immutable‏ ‎«в‏ ‎природе».
  6. Сделать ‎свои‏ ‎типы ‎дружелюбными ‎к ‎immutable/const ‎это‏ ‎адская ‎боль‏ ‎в‏ ‎заднице ‎и ‎не‏ ‎пройдет ‎достаточно‏ ‎времени ‎прежде, ‎чем ‎ваши‏ ‎пользователи‏ ‎попробуют ‎const‏ ‎blah ‎=‏ ‎YourType ‎(…); ‎и ‎будут ‎громко‏ ‎жаловаться,‏ ‎что ‎вы‏ ‎не ‎поддерживаете‏ ‎корректность ‎относительно ‎const. ‎Реализовывать ‎сложные‏ ‎типы,‏ ‎которые‏ ‎работают ‎с‏ ‎транзитивным ‎const‏ ‎некрасиво ‎и‏ ‎более‏ ‎того ‎это‏ ‎перекладывает ‎нагрузку ‎не ‎на ‎то‏ ‎место. ‎Пользователи‏ ‎должны‏ ‎подвергаться ‎давлению, ‎чтобы‏ ‎использовать ‎const,‏ ‎но ‎не ‎делая ‎жизнь‏ ‎автора‏ ‎библиотеки ‎значительно‏ ‎труднее.

В ‎отличает‏ ‎от ‎транзитивного ‎const ‎поверхностный ‎const‏ ‎(~‏ ‎final ‎из‏ ‎Java) ‎тривиально‏ ‎применим ‎к ‎любому ‎определенному ‎пользователем‏ ‎типу,‏ ‎практически‏ ‎без ‎какой-либо‏ ‎дополнительной ‎работы‏ ‎со ‎стороны‏ ‎автора‏ ‎библиотеки. ‎Более‏ ‎того, ‎теперь ‎автор ‎может ‎предоставить‏ ‎типы, ‎которые‏ ‎иммутабельны‏ ‎за ‎счет ‎интерфейса‏ ‎(Коллекции! ‎Строки!).

Наконец.‏ ‎Существуеь ‎одно ‎(!) ‎хорошее‏ ‎взаимодействие‏ ‎с ‎транзитивностью‏ ‎и ‎это‏ ‎(честные) ‎чистые ‎(pure) ‎функции. ‎Однако‏ ‎D‏ ‎прославляет ‎себя‏ ‎как ‎прагматичный‏ ‎и ‎я ‎спорю ‎на ‎что‏ ‎угодно,‏ ‎что‏ ‎«нечестные» ‎(логически‏ ‎иммутабельные) ‎чистые‏ ‎функции ‎в‏ ‎Scala/Haskel/любом‏ ‎другом ‎функциональном‏ ‎языке ‎прекрасно ‎работают ‎и ‎гораздо‏ ‎проще ‎пишутся.

Каков‏ ‎же‏ ‎выход?

Я ‎не ‎знаю‏ ‎и ‎не‏ ‎имею ‎никакой ‎силы ‎изменить‏ ‎направление‏ ‎языка ‎D.‏ ‎Это ‎вдохновляющий‏ ‎современный ‎системный ‎язык ‎программирования, ‎но‏ ‎без‏ ‎решения ‎изъяна‏ ‎такого ‎масштаба‏ ‎будет ‎трудно ‎вырасти ‎за ‎приделы‏ ‎текущей‏ ‎(значительно‏ ‎большей ‎нуля‏ ‎кстати, ‎впечатляюще)‏ ‎аудитории.

Мое ‎предложение‏ ‎(одевая‏ ‎шляпу ‎BDDL‏ ‎языка ‎D) ‎будет ‎таким ‎…

Выбросить‏ ‎их! ‎И‏ ‎сжечь‏ ‎в ‎огне.

Шаги ‎будут‏ ‎следующими:

  1. Проверки ‎иммутабельности‏ ‎поверхностные, ‎но ‎компилятор ‎предупреждает‏ ‎о‏ ‎нарушении ‎транзитивного‏ ‎const/immutable. ‎С‏ ‎preview ‎опцией ‎вы ‎не ‎получаете‏ ‎таких‏ ‎предупреждений.
  2. Shared ‎является‏ ‎квалификатором ‎хранения‏ ‎и ‎preview ‎опция ‎предупреждает ‎о‏ ‎неверном‏ ‎применении,‏ ‎заглушаемые ‎флагом.‏ ‎Такого ‎применение‏ ‎игнорируется ‎пару‏ ‎релизов.
  3. 2&‏ ‎4 ‎со‏ ‎своим ‎ритмом ‎переводятся ‎в ‎умолчания,‏ ‎с ‎явными‏ ‎опциями‏ ‎для ‎отката.
  4. Никаких ‎опций‏ ‎больше ‎нет‏ ‎и ‎мы ‎покончили ‎с‏ ‎«черепахами‏ ‎до ‎самого‏ ‎конца» ‎и‏ ‎надеюсь ‎навсегда.


Предыдущий Следующий
Все посты проекта
0 комментариев

Статистика

1 подписчик
5 500 ₽ всего собрано

Контакты

Метки

Подарить подписку

Будет создан код, который позволит адресату получить бесплатный для него доступ на определённый уровень подписки.

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

Будет создан код, который позволит адресату получить сумму на баланс.

Разово будет списана указанная сумма и зачислена на баланс пользователя, воспользовавшегося данным промокодом.

Добавить карту
0/2048