avva: (Default)
[personal profile] avva
(эта запись может быть интересна разве что программистам)

Прочитал Java Generics FAQ. 500 страниц, между прочим, в PDF-версии. 500 страниц. Когда вы в последний раз читали FAQ на 500 страниц? Ну вот и я тогда же. С трудом верится даже теперь, по прочтении. Правда, там много места занимают бесконечные индексы и оглавления и заголовки, так что реально "мяса" страниц на 350. Но и этого довольно.

Теперь я понимаю, зачем в Джаве класс Enum определяется, как Enum<E extends Enum<E>>, что это в точности значит, и зачем это нужно. Не уверен, что мне нравится, что я это теперь понимаю.

Многие объяснения в этом фэке читаются, как один сплошной WTF. Ты понимаешь, в процессе чтения, почему это сделано так, а не иначе. Почему и тут исключение, и тут, и это надо делать через задницу, а то даже через задницу не сделать. Почему - один пример наугад из сотни - все обычные вещи с параметризованными типами можно делать, а вот создавать массивы из них нельзя. Кроме случая, когда они параметризованы неограниченными вопросиками. Ты понимаешь, почему это так получается, но не оставляет ощущение глубокого WTF на тему того, как мучительно и болезненно эти дженерики врастают в плоть языка. Сказать, что это leaky abstraction - ничего не сказать; эта лодка не протекает, она буквально состоит из воды.

Вот один из прекрасных вопросов из этого FAQ:
What is the difference between a Collection<Pair<String,Object>>, a Collection<Pair<String,?>> and a Collection<? extends Pair<String,?>>?
Ответ начинается так: "All three types refer to collections that hold pairs where the first part is a String and the second part is of an arbitrary type. The differences are subtle." Потом на двух страницах объясняются эти subtle differences.

По-моему, внесение дженериков в Джаву было огромной ошибкой. Главная польза от них - строгая типизация коллекций - не оправдывает той огромной цены, которую пришлось заплатить: сложностью языка, читабельностью и понимабельностью кода. Учитывая то, что динамическая безопасность у коллекций и так была, необходимость безопасности на уровне компиляции была кем-то, по-видимому, сильно преувеличена. Да, конечно, намного удобнее и проще написать ArrayList<String>, и знать, что попытка всунуть туда что-то другое вообще не скомпилируется. Это удобнее, чем всунуть что-то не то в ArrayList и получить исключение в рантайме, когда это взяли и попытались привести к String. Баги ловятся быстрее. Но насколько быстрее, как часто на практике это оказывалось существенным, и оправдывает ли эта польза введение архитектуры, приводящей к Enum<E extends Enum<E>> или вопросам, подобным процитированному выше?

Одним из главных преимуществ Джавы по сравнению с C++ было именно grokability исходного кода средним программистом: посмотрев на исходник какого-то класса, который он раньше не видел, средний программист на Джаве - не гуру, не хватающий звезды с неба итд. - мог сказать, что делает каждая строка и зачем это нужно. Дженерики это свойство языка с помпой похоронили. Кто были люди, которые приняли решение так поступить, и радовались ли они красивому трюку само-референтных типов? И это только ущерб понятности кода, не говоря о всем этом огромном числе исключений и плохой совместимости с существующими частями языка - массивами, рефлекцией, исключениями, итд. итд. итд.

Можно ли было добиться той же пользы, что дали дженерики, менее разрушительными способами? Пусть не той же, но главной ее части, мне кажется, можно было. Скажем, какая-нибудь аннотация в момент создания коллекции, подсказывающая компилятору, что там должно быть. Просто сказать: в эту коллекцию я хочу класть строки, или такие-то пары, итп. И пусть компилятор проверяет, что сможет, и вставляет эксплицитное приведение к строкам или парам, когда мы из коллекции что-то достаем. И все. Без extends, без wildcards, для всех мест, где это действительно надо, пусть программист продолжает приводить эксплицитно. Конечно, такая аннотация не покрыла бы все случаи, но самые простые и важные, думаю, покрыла бы. Главное ведь то, что у дженериков почти нулевой эффект на рантайм, поэтому необязательно было вносить их глубоко в ткань языка, so to speak; подсказки компилятору достигают схожей цели. Простая (простая!) и удобная подсказка захватила бы, мне кажется, большинство багов с типами в коллекцях, которые до дженериков проявлялись только в рантайме.

(disclaimer: я редко и мало пишу на Джаве, и не эксперт в ней)
Page 1 of 7 << [1] [2] [3] [4] [5] [6] [7] >>

Date: 2010-05-21 09:39 pm (UTC)
From: [identity profile] solomon2.livejournal.com
А inner classes вас не раздражают?

Date: 2010-05-21 09:43 pm (UTC)
From: [identity profile] psr1913plus16.livejournal.com
Я сам начал пользоваться Java с той версии, в которой появились параметризованные типы. До этого старался с ней не сталкиваться; по возможности сваливать на подчиненных и т.д.

Наверное, мой узус гораздо уже того, что вы описываете. Дальше List<POJO> я не иду. Просто незачем. Хотя бы потому, что мои задачи всегда опираются на схему (базы) данных, а JPA оперирует именно этими POJO и List'ами из них.

Date: 2010-05-21 09:44 pm (UTC)
From: [identity profile] trurle.livejournal.com
В C++ проблем не меньше, и, что характерно, коллекции получились просто загляденье, но за это заплачено механизмом которые нередко приводит к сводящему с ума когду. У меня есть нехорошее чувство что разработчики параметризованных типов в обоих случаях думали "ах, какие замечательные библиотеки коллекций можно будет построить с применением наших генерализованных типов!"

Date: 2010-05-21 09:46 pm (UTC)
From: (Anonymous)
Просто сказать: в эту коллекцию я хочу класть строки, или такие-то пары, итп.

Именно это генерики и делают.

Collection<String>

Вот, я сейчас сказал, что в эту коллекцию я хочу класть строки. Компилятор проверяет, что может, и вставляет эксплицитное приведение к строкам или парам, когда мы из коллекции что-то достаем. Хотите, чтобы это делалось проще? Предложите свой дизайн. Не бойтесь, завтра же начните разрабатывать, целый выходной впереди. (Hint: это займет пять лет, и получится хуже, чем существующий дизайн. Been there, done that.)

Date: 2010-05-21 09:59 pm (UTC)
From: [identity profile] dynamo.livejournal.com
Я как только подумаю, что мне надо прочитать 1400 страниц Bluetooth Core в том самом PDF-e, так сразу вешаться хочется..

Date: 2010-05-21 10:07 pm (UTC)
From: (Anonymous)
Еще по поводу Enum<E extends Enum<E>> хочется высказаться. Этой идиоме вообще-то сто лет в обед (я о ней знал, когда Джава еще о дженериках и не помышляла). Она решает наболевший примерно 15 лет назад вопрос о типизации бинарных методов в ООП (бинарные методы принимают два аргумента одинакового типа — сравнение, арифметика всяческая и т.д. и т.п.) Это вопрос принципиальный. Что если нам нужно работать с множеством типов чисел, матриц, векторов, многочленов и прочей дребедени? В рамках традиционного ООП приписать сигнатуру общей для всех, наследуемой операции "+" невозможно. Либо мы заводим 50 никак не связанных между собой операций "+", 50 штук "*", 50 штук gauss_elimination, 50 штук newton_raphson, бла-бла... либо мы с этим что-то делаем нетрадиционное.

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

Date: 2010-05-21 10:10 pm (UTC)
From: [identity profile] pin-dragon.livejournal.com
В С++ много чего может привести к сводящему с ума коду.
Но там, хотя бы, все кунштюки с темплейтами можно оправдать тем, что с их помощью можно делать (всё же, полноценный механизм кодогенерации), в джаве же такого нет =(

Date: 2010-05-21 10:17 pm (UTC)
From: [identity profile] avva.livejournal.com
Они не делают именно это. Они встраиваются в систему типов языка; подсказка, которую я предлагаю, этого бы не делала.

С одной стороны, дженерики говорят программисту: Collection<String> и Collection<Number> - два совершенно разных типа. С другой стороны, у рантайма есть свое представление о типе, в которое это различие не укладывается. Это рантаймовое представление в свою очередь тесно связано с фундаментальными возможностями языка. В такой ситуации расщеплять систему типов на две - для компилятора и для рантайма - не может не привести к сложной, запутанной системе, которую будет тяжело понять.

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

Date: 2010-05-21 10:22 pm (UTC)
From: [identity profile] avva.livejournal.com
Конечно, всегда можно ограничить себя самыми простыми случаями. Но увы, читать и работать приходится не только над своим кодом.

Date: 2010-05-21 10:42 pm (UTC)
From: [identity profile] kingoleg.livejournal.com
Поверь, генерики упрощают жизнь.

Если ты начинаешь путаться в генериках - значит, твой код дурно пахнет.

Особенно приясно старый 1.4 джава код "портировать" в 1.5, добавляя генерии к коллекциям. То тут то там выясняется, что в переменной хранится 2,3 а то и больше типов данных. И ты не понимаешь, в каком случае - какой. Ну что, добавляем переменную со строгой типизацией - и жить становится лучше.

Date: 2010-05-21 10:42 pm (UTC)
From: (Anonymous)
1. Что такое рантайм, я в нормальном своем режиме не знаю и знать не хочу. Я думаю в терминах исходного языка и его типов, а не байткода и его типов. Как там реализованы генерики, какие типы остаются в рантайме, а какие стираются — простому программисту совершенно неважно. Да, иногда можно натолкнуться на тонкости, но это иногда. Поэтому расщепление, о котором вы говорите, проблемы для обычного программиста не представляет (а top gun, реализующий язык или генерирующий фреймворки для обычного программиста, как-нибудь справится).

2. В свете вышеизложенного становится непонятным, в чем разница между «типом» и «подсказкой компилятору для статического анализа». То есть что такое тип, я знаю; это такой языковой конструкт с определенными свойствами, а про подсказки компилятору мне непонятно, как они должны быть устроены. Если начать их устройство формально определять, не получим ли мы вторую систему типов? Создается впечатление, что да, получим; может быть, она окажется лучше первой, ну так тогда можно будет первую выкинуть совсем и пользоваться только второй.

Date: 2010-05-21 10:47 pm (UTC)
From: (Anonymous)
Полноценный в том смысле, что приходится платить за него полную цену (а куда деваться? другой альтернативы, как говорил М.С. Горбачев, у нас нет).

Date: 2010-05-21 10:55 pm (UTC)
From: [identity profile] anton-solovyev.livejournal.com
В прикладном коде дженерики чудесно работают, улучшают читабельность кода и вообще сильно облегчают жизнь. Практически.

Спасибо большое, тем, кто потратил время добавить их к Джаве. Чего бы это им не стоило.

Date: 2010-05-21 10:57 pm (UTC)
From: [identity profile] pin-dragon.livejournal.com
Разумеется. Но если брать контекст всего С++, то на общем фоне маразматичности языка цена не так уж и велика)

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

Date: 2010-05-21 11:02 pm (UTC)
From: [identity profile] avva.livejournal.com
Что такое рантайм, я в нормальном своем режиме не знаю и знать не хочу. Я думаю в терминах исходного языка и его типов, а не байткода и его типов. Как там реализованы генерики, какие типы остаются в рантайме, а какие стираются — простому программисту совершенно неважно.

Кроме тех случаев, когда хочется массив создать.
Или исключение кинуть.
Или воплотить (из вполне резонных и разумных соображений) Interface<Type1> и Interface<Type2> одновременно.
Или [список можно продолжить].

Да, иногда можно натолкнуться на тонкости, но это иногда.

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

2. В свете вышеизложенного становится непонятным, в чем разница между «типом» и «подсказкой компилятору для статического анализа». То есть что такое тип, я знаю; это такой языковой конструкт с определенными свойствами, а про подсказки компилятору мне непонятно, как они должны быть устроены. Если начать их устройство формально определять, не получим ли мы вторую систему типов?

Разница, полагаю, в том, что тип привязан к значению (value) и путешествует вместе с ним, а подсказка обычно локальна и действует в пределах данных строки/метода/класса/файла. Например, возьмите подсказку @Override: она никак не влияет на сигнатуру метода, а всего лишь помогает компилятору найти типичные ошибки при определении метода.

Date: 2010-05-21 11:03 pm (UTC)
From: [identity profile] avva.livejournal.com
Да нет в общем.

Date: 2010-05-21 11:10 pm (UTC)
From: [identity profile] tolko-ne.livejournal.com
Вспомните контекст! Шаблоны C++ вида list заменили абсолютно ужасные С-макросы (помните такие: #define LIST(X) ... -- и потом макро на несколько страниц); на эти макросы, к тому же, не было никакого страндарта и каждый писал их заново.

А то, что из темплайтов можно устроить "meta-programming", открыли почти случайно и лет через 10 (хотя
Степанов, наверное, догадывался раньше).

В итоге разработчики Java уже знали из опыта С++, что введение параметризованных типов -- это полная машина Тьюринга в compile time, такой встроеный почти Хаскель, но с безумным синтаксисом.

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


Date: 2010-05-21 11:14 pm (UTC)
From: [identity profile] igorlord.livejournal.com
Oh, come on!

The type system is very logical (including your Collection
[Error: Irreparable invalid markup ('<pair<string,object>') in entry. Owner must fix manually. Raw contents below.]

Oh, come on!

The type system is very logical (including your Collection<Pair<String,Object>> - like example).

Basically, there is a simle rule: generic types name exact types, and if you want to allow for derived types, you need to use "extends" syntax. Also, everything is strongly types at the "parsing unit" level (a statement when parsing a statement; a function declaration and function contents when parsing a function; class declaration and class contents when parsing a class).


Now, I DO consider it a mistake to require the "extends" syntax. It would be best if a derived type could ALWAYS be used when a base type is mentioned. Java's stength has always been at ignoring little "features" in favor of simplicity.

Java, including its generics, is a so much cleaner language than C++, for example, where templates are just humongously complicated preprocessor macros, and defeat the "programming by contract" model.


As for "how do you make a Collection of Strings or Pairs?", the answer is that you should not do that, because "a String or a Pair" is a terrible abstraction, and, luckily, the language resists it! If "a String or a Pair" does represent some abstraction in your program, you should make a class that encapsulates this abstraction and then have a Collection of those classes.

Date: 2010-05-21 11:28 pm (UTC)
From: [identity profile] nec-p1us-u1tra.livejournal.com
И как это в Хаскелле справились, подумать только. Не иначе колдуны.

Date: 2010-05-21 11:47 pm (UTC)
From: (Anonymous)
Хаскель с безумным синтаксисом - это отлично :)))))

черт, а нельзя сразу писать на Haskell? :)))) код получается в разы меньше :)


все вкусности хитрого наследования типов я прочувствовал именно в Haskel, где оно очень элегантно и вкусно сделано :)

Date: 2010-05-21 11:57 pm (UTC)
From: (Anonymous)
мне ДИКО понравился подход erlang.

они пошли другим путем. рантайм не имеет почти никакого понятия, чем он оперирует, но есть отдельных интрумент - такой супер-навороченный lint, который читает из комментариев в формате PHPdoc ну или подобном(т.е. просто коммент перед описанием функции), понимает что она принимает и что она выдает на выход.

после чего строит модель всего этого у себя и анализирует.

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

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

Date: 2010-05-22 12:00 am (UTC)
From: (Anonymous)
1. Массивы создавать просто не нужно, никогда. Это страшная ошибка догенерической Джавы (у меня, например, челюсть упала на пол, когда я прочитал их спецификацию в первой версии Джавы, около 1995 года). Нужно пользоваться соответствующими параметрическими коллекциями (ArrayedList, whatever).

Про исключения надо помнить, согласен, но соблазн кинуть исключение генерик-типа возникает редко, и удовлетворить его легко.

Реализовать в одном классе Interface<Type1> и Interface<Type2> — плохая идея уже на уровне исходного языка, независимо от того, есть erasure или нет.

Список можно продолжить, но продолжить его чем-то действительно важным будет непросто (хотя важность in the eye of the beholder).

В общем, никаких постоянных и рутинных вещей не наблюдаю. Именно что раз в год по праздникам. Ну или если приходится иметь дело с legacy кодом (массивы опять же). Тут да, тут надо держать ухо востро постоянно.

2. Типы, привязанные к значениям, не имеют значения ;) Типы привязаны в первую очередь к переменным и выражениям, именно эти типы нас интересуют, и они вполне локальны. У значений-то как раз генерики стерты, их усложнения вообще не касаются.

Date: 2010-05-22 12:03 am (UTC)
From: [identity profile] tolko-ne.livejournal.com
Знаете, одна из главных проблем в C++metaprogramming -- это дебагирование этих вложенных и рекурсивных темплейтов. Думаю, рано или поздно кто-нибудь напишет C++ компилятор на Хаскеле (а не наоборот, как сейчас), и тогда рекурсивные темпейты можно будет дебагировать.

Но пока религия не позволяет: Хаскели могли бы, но не снизойдут, а "реальные" программисты не потянут. Пока что последние предпочитают стандартизировать compiler error messages (которые просто трасинг для compile-time кода).

Date: 2010-05-22 12:04 am (UTC)
alexeybobkov: (Default)
From: [personal profile] alexeybobkov
Да, кстати о типах: дочитали ли вы книгу Пирса и что о ней думаете?

Date: 2010-05-22 12:17 am (UTC)
From: (Anonymous)
Я говорил о традиционном ОО-подходе, в Хаскеле все по-другому, его классы не соответствуют классам ОО. Ну и приводить в пример Хаскель, когда речь идет о системе типов, понятной простому парню от сохи... э... собственно, да, колдуны и есть.
Page 1 of 7 << [1] [2] [3] [4] [5] [6] [7] >>

December 2025

S M T W T F S
  123 4 56
78 9 10 11 1213
1415 1617181920
21 22 23 24 2526 27
28293031   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Dec. 30th, 2025 07:51 am
Powered by Dreamwidth Studios