дженерики (программистское)
May. 22nd, 2010 12:26 am(эта запись может быть интересна разве что программистам)
Прочитал Java Generics FAQ. 500 страниц, между прочим, в PDF-версии. 500 страниц. Когда вы в последний раз читали FAQ на 500 страниц? Ну вот и я тогда же. С трудом верится даже теперь, по прочтении. Правда, там много места занимают бесконечные индексы и оглавления и заголовки, так что реально "мяса" страниц на 350. Но и этого довольно.
Теперь я понимаю, зачем в Джаве класс Enum определяется, как Enum<E extends Enum<E>>, что это в точности значит, и зачем это нужно. Не уверен, что мне нравится, что я это теперь понимаю.
Многие объяснения в этом фэке читаются, как один сплошной WTF. Ты понимаешь, в процессе чтения, почему это сделано так, а не иначе. Почему и тут исключение, и тут, и это надо делать через задницу, а то даже через задницу не сделать. Почему - один пример наугад из сотни - все обычные вещи с параметризованными типами можно делать, а вот создавать массивы из них нельзя. Кроме случая, когда они параметризованы неограниченными вопросиками. Ты понимаешь, почему это так получается, но не оставляет ощущение глубокого WTF на тему того, как мучительно и болезненно эти дженерики врастают в плоть языка. Сказать, что это leaky abstraction - ничего не сказать; эта лодка не протекает, она буквально состоит из воды.
Вот один из прекрасных вопросов из этого FAQ:
По-моему, внесение дженериков в Джаву было огромной ошибкой. Главная польза от них - строгая типизация коллекций - не оправдывает той огромной цены, которую пришлось заплатить: сложностью языка, читабельностью и понимабельностью кода. Учитывая то, что динамическая безопасность у коллекций и так была, необходимость безопасности на уровне компиляции была кем-то, по-видимому, сильно преувеличена. Да, конечно, намного удобнее и проще написать ArrayList<String>, и знать, что попытка всунуть туда что-то другое вообще не скомпилируется. Это удобнее, чем всунуть что-то не то в ArrayList и получить исключение в рантайме, когда это взяли и попытались привести к String. Баги ловятся быстрее. Но насколько быстрее, как часто на практике это оказывалось существенным, и оправдывает ли эта польза введение архитектуры, приводящей к Enum<E extends Enum<E>> или вопросам, подобным процитированному выше?
Одним из главных преимуществ Джавы по сравнению с C++ было именно grokability исходного кода средним программистом: посмотрев на исходник какого-то класса, который он раньше не видел, средний программист на Джаве - не гуру, не хватающий звезды с неба итд. - мог сказать, что делает каждая строка и зачем это нужно. Дженерики это свойство языка с помпой похоронили. Кто были люди, которые приняли решение так поступить, и радовались ли они красивому трюку само-референтных типов? И это только ущерб понятности кода, не говоря о всем этом огромном числе исключений и плохой совместимости с существующими частями языка - массивами, рефлекцией, исключениями, итд. итд. итд.
Можно ли было добиться той же пользы, что дали дженерики, менее разрушительными способами? Пусть не той же, но главной ее части, мне кажется, можно было. Скажем, какая-нибудь аннотация в момент создания коллекции, подсказывающая компилятору, что там должно быть. Просто сказать: в эту коллекцию я хочу класть строки, или такие-то пары, итп. И пусть компилятор проверяет, что сможет, и вставляет эксплицитное приведение к строкам или парам, когда мы из коллекции что-то достаем. И все. Без extends, без wildcards, для всех мест, где это действительно надо, пусть программист продолжает приводить эксплицитно. Конечно, такая аннотация не покрыла бы все случаи, но самые простые и важные, думаю, покрыла бы. Главное ведь то, что у дженериков почти нулевой эффект на рантайм, поэтому необязательно было вносить их глубоко в ткань языка, so to speak; подсказки компилятору достигают схожей цели. Простая (простая!) и удобная подсказка захватила бы, мне кажется, большинство багов с типами в коллекцях, которые до дженериков проявлялись только в рантайме.
(disclaimer: я редко и мало пишу на Джаве, и не эксперт в ней)
Прочитал 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: я редко и мало пишу на Джаве, и не эксперт в ней)
no subject
Date: 2010-05-21 09:39 pm (UTC)no subject
Date: 2010-05-21 11:03 pm (UTC)(no subject)
From:no subject
Date: 2010-05-21 09:43 pm (UTC)Наверное, мой узус гораздо уже того, что вы описываете. Дальше List<POJO> я не иду. Просто незачем. Хотя бы потому, что мои задачи всегда опираются на схему (базы) данных, а JPA оперирует именно этими POJO и List'ами из них.
no subject
Date: 2010-05-21 10:22 pm (UTC)no subject
Date: 2010-05-21 09:44 pm (UTC)no subject
Date: 2010-05-21 10:10 pm (UTC)Но там, хотя бы, все кунштюки с темплейтами можно оправдать тем, что с их помощью можно делать (всё же, полноценный механизм кодогенерации), в джаве же такого нет =(
(no subject)
From: (Anonymous) - Date: 2010-05-21 10:47 pm (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-22 11:43 am (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-21 11:47 pm (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2010-05-21 09:46 pm (UTC)Именно это генерики и делают.
Collection<String>
Вот, я сейчас сказал, что в эту коллекцию я хочу класть строки. Компилятор проверяет, что может, и вставляет эксплицитное приведение к строкам или парам, когда мы из коллекции что-то достаем. Хотите, чтобы это делалось проще? Предложите свой дизайн. Не бойтесь, завтра же начните разрабатывать, целый выходной впереди. (Hint: это займет пять лет, и получится хуже, чем существующий дизайн. Been there, done that.)
no subject
Date: 2010-05-21 10:17 pm (UTC)С одной стороны, дженерики говорят программисту: Collection<String> и Collection<Number> - два совершенно разных типа. С другой стороны, у рантайма есть свое представление о типе, в которое это различие не укладывается. Это рантаймовое представление в свою очередь тесно связано с фундаментальными возможностями языка. В такой ситуации расщеплять систему типов на две - для компилятора и для рантайма - не может не привести к сложной, запутанной системе, которую будет тяжело понять.
Если мы определим цель не как "расширить систему типов так, чтобы она могла выразить все, что мы хотим сказать о содержимом коллекций", а "дать подсказку для статического анализа, которая поймает основные баги с содержимым коллекций", то и расщепления, о котором я говорю выше, не будет, и соблазн вводить ? и автореферентные типы - даже и не появится.
(no subject)
From: (Anonymous) - Date: 2010-05-21 10:42 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-22 12:00 am (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-22 01:28 am (UTC) - Expand(no subject)
From: (Anonymous) - Date: 2010-05-22 01:50 am (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-23 02:30 pm (UTC) - Expand(no subject)
From: (Anonymous) - Date: 2010-05-21 11:57 pm (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2010-05-21 09:59 pm (UTC)no subject
Date: 2010-05-21 10:07 pm (UTC)В связи с чем философский вопрос — стоит ли подвергать простого программиста воздействию передовых идей пятнадцатилетней давности, или надо еще десять-пятнадцать лет выдержки добавить, чтобы все окончательно утряслось?
no subject
Date: 2010-05-21 11:28 pm (UTC)(no subject)
From: (Anonymous) - Date: 2010-05-22 12:17 am (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-22 09:16 am (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-22 03:26 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-23 08:02 pm (UTC) - Expandno subject
Date: 2010-05-21 10:42 pm (UTC)Если ты начинаешь путаться в генериках - значит, твой код дурно пахнет.
Особенно приясно старый 1.4 джава код "портировать" в 1.5, добавляя генерии к коллекциям. То тут то там выясняется, что в переменной хранится 2,3 а то и больше типов данных. И ты не понимаешь, в каком случае - какой. Ну что, добавляем переменную со строгой типизацией - и жить становится лучше.
no subject
Date: 2010-05-22 06:12 pm (UTC)Вообще, конечно, приятно что они много чего добавили в 5-ой версии и потом расширили в 6-ой.
no subject
Date: 2010-05-21 10:55 pm (UTC)Спасибо большое, тем, кто потратил время добавить их к Джаве. Чего бы это им не стоило.
no subject
Date: 2010-05-21 11:14 pm (UTC)The type system is very logical (including your Collection
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.
no subject
Date: 2010-05-22 02:32 pm (UTC)I don't really understand what you mean by this. The "extends" syntax is needed to allow you access to the parameter type's methods inside your generic class. It's not useful for distinguishing base vs derived types; in fact, you can have an "extends X" that will only match the exact type X and not a descendant (when X is final).
As for "how do you make a Collection of Strings or Pairs?", the answer is that you should not do that,
I kinda agree with that, but I don't think I was making a point anywhere that generics are bad because they don't allow you to do this.
no subject
Date: 2010-05-22 12:04 am (UTC)no subject
Date: 2010-05-22 07:45 am (UTC)no subject
Date: 2010-05-22 01:08 am (UTC)Причем, надо сказать, не помогает ведь. Я в основном пишу на C и C++, Java в руки мне попадает изредка - когда появились дженерики вроде стало так удобно ... а потом в коде полезли какие-то ужасы. Вот ведь доказательство поинта поста - смотрю на свой код написанный меньше года тому назад и не понимаю почему там именно Class<?>, а не просто Class. А ведь сам же и писал.
no subject
Date: 2010-05-22 01:47 am (UTC)(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-22 02:33 pm (UTC) - Expand(no subject)
From:no subject
Date: 2010-05-22 01:17 am (UTC)А то что сдуру можно и хер сломать, так это давно известно.
no subject
Date: 2010-05-22 03:32 am (UTC)no subject
Date: 2010-05-22 09:18 pm (UTC)no subject
Date: 2010-05-22 04:04 am (UTC)Дженерики появились в C# раньше, чем в Java, хотя первые версии C# были частично слизаны с Java. С тех пор C# шагнул сильно вперёд. Теперь очередь Java слизывать.
no subject
Date: 2010-05-22 07:46 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2010-05-22 04:56 am (UTC)no subject
Date: 2010-05-22 07:45 am (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2010-05-22 05:27 am (UTC)Полностью согласен. И практически все нововведения в язык после 1.2 уменьшают это преимущество. И autoboxing, и, теперь вот Closures засовывают
no subject
Date: 2010-05-22 06:25 pm (UTC)no subject
Date: 2010-05-22 06:19 am (UTC)Нужно различать сложность полной спецификации языка и сложность того подмножества, на котором пишется почти весь код. На практике почти всегда использование generics не идёт дальше чего-то вроде ArrayList<MyClass>, а это читабельности помогает, хотя и бывает уж очень многословно:
private final AtomicReference<Map<Key, List<Value>>> mymap = new AtomicReference<Map<Key, List<Value>>>(new HashMap<Key, List<Value>>());
А JLS полностью всё равно мало кто понимал. :)
Одним из главных преимуществ Джавы по сравнению с C++ было именно grokability исходного кода средним программистом: посмотрев на исходник какого-то класса, который он раньше не видел, средний программист на Джаве - не гуру, не хватающий звезды с неба итд. - мог сказать, что делает каждая строка и зачем это нужно. Дженерики это свойство языка с помпой похоронили.
Почти согласен с первым утверждением, но скорее сказал бы что Java была what-you-see-is-what-you-get, во всяком случае, в гораздо большей степени, чем C++ и C. Мне кажется, что это свойство одинаково важно как программистам "от сохи", так и изощрённым умам -- сюрпризы никому не нравятся.
Увы, это постепенно ослабляется, но generics тут играют не большую роль, чем такая банальная вещь, как autoboxing.
Можно ли было добиться той же пользы, что дали дженерики, менее разрушительными способами? Пусть не той же, но главной ее части, мне кажется, можно было. Скажем, какая-нибудь аннотация в момент создания коллекции, подсказывающая компилятору, что там должно быть.
Объявления типов суть такие аннотации. Ужасные wildcards нужны, чтобы обслужить ко- и контра-вариантность, это не зависит от того, оформлена ли спецификация типа джавской аннотацией, или ещё как. Или я Вас неправильно понял. Можно было бы отказаться от учёта ко- и контра-вариантности, тогда Джаву ругали бы за это. :)
(Интересно сравнить со Scala, где спецификация "вариантности" живёт в объявлении класса или трейта коллекции -- выглядит разумно, но я пока недостаточно кода написал, чтобы судить.)
Евгений.
no subject
Date: 2010-05-22 09:52 am (UTC)(no subject)
From: (Anonymous) - Date: 2010-05-22 11:56 am (UTC) - Expand(no subject)
From: (Anonymous) - Date: 2010-05-22 03:31 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-22 07:54 pm (UTC) - Expandno subject
Date: 2010-05-22 07:43 am (UTC)На самом деле рядовые программисты используют генерики простым путём, и понимать такие программы проще в сто раз, чем бесконечные касты.
В C# вместе с лямбдами и extension методами это позволило сделать типизированное указание каких-то вещей везде где захочется. (Синтаксис, правда, не всегда совсем уж приятный).
Вот такой код, например, выведет ссылку на метод Index класса-контроллера SearchController с текстом "CHANGE SEARCH". (это из ASP.NET MVC)
Html.ActionLink(sc => sc.Index(), "CHANGE SEARCH")
При этом всё типизированное - я могу сделать рефакторинг любой части, и код не сломается.
Кроме того, рядовой разработчик - понятие несколько абстрактное. Он в принципе может и не понимать, что сложный код делает, в тех редких случаях, когда его юзает.
Хороший пример - linq в c#. Для многих простых случаев очень удобно. Чтобы его использовать, знать, что там внутри строится дерево выражений, необязательно.
no subject
Date: 2010-05-22 07:44 am (UTC)вместо квадратных скобок угловые
Html.ActionLink[SearchController](sc => sc.Index(), Html.Resource("CHANGE SEARCH"))
no subject
Date: 2010-05-22 08:06 am (UTC)А то они к джаве прикручивают всякие фичи, но как-то неуверенно и криво. Генерики, замыкания, type inference.
С другой стороны, майкрософт сделала два языка в дотнете, и тот, что считается "проще" (VB.NET) стал просто second-class citizen - инфы меньше, инструментов меньше, новые платформы от в первую очередь делают поддержку C# и т.д. (Правда, по возможностям VB.NET не сильно отличается от C#, так что проще ли он - большой вопрос)
no subject
Date: 2010-05-22 12:38 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2010-05-22 09:54 am (UTC)Дженерики, всё-таки, это новая языковая возможность. Совсем новая. Скажем, шаблоны C++ язык не расширяют, это такой препроцессор на стероидах. А дженерики жабы - это уже настоящий параметрический полиморфизм. Неполный (очень), но честный. Уже огромная польза.
Я слышал, вообще хотели одно время сделать вариант жабы с дженериками и интерфейсами, но без наследования. ИМХО, это было бы очень интересно. Увы, не сложилось.
> зачем в Джаве класс Enum определяется, как Enum<E extends Enum<E>>
А там так сделано? Если да, то это очень правильно.
Причиной тому - ООП-шная манера складывать методы туда же, куда и данные. Соответственно, если какому-то методу интерфейса нужен, кроме this, ещё один аргумент того же типа (скажем, если тип - это вектор, а метод - скалярное произведение), приходится передавать ему этот тип в качестве аргумента дженерика.
Разумеется, гораздо проще написатьчем
interface Vector<A extends Vector<A>> {float scalarProduct(A second);}но реально это одно и то же.Кстати, на жабе я не пишу.
no subject
Date: 2010-05-22 01:57 pm (UTC)no subject
Date: 2010-05-22 01:06 pm (UTC)Вот я сейчас пишу на языке haXe, который компилируется в байткод для Flash. Во флеше нету параметризованных типов, а в haXe есть, и работают совершенно просто и понятно. Не знаю, почему в Java не могли сделать примерно то же самое.
no subject
Date: 2010-05-22 03:27 pm (UTC)Да, в отличии от Си++, где они дают выигрыш в производительности.
no subject
Date: 2010-05-22 03:36 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-23 01:51 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-23 02:44 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-23 03:57 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-23 08:00 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-24 02:37 am (UTC) - Expand(no subject)
From: (Anonymous) - Date: 2010-05-24 03:26 am (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-24 04:58 am (UTC) - Expand(no subject)
From: (Anonymous) - Date: 2010-05-24 02:42 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-25 03:32 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-26 03:11 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-27 08:25 am (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2010-05-24 04:46 am (UTC) - Expandno subject
Date: 2010-05-22 06:50 pm (UTC)fixed
no subject
Date: 2010-05-22 06:51 pm (UTC)no subject
Date: 2010-05-24 02:04 pm (UTC)