avva: (Default)
[personal profile] avva

Это будет интересно только программистам.

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

Предположим, у нас есть интерфейс Foo и класс Bar. Предположим, в классе есть определения всех методов, которые перечислены в интерфейсе, с правильными аргументами, типами возврата и другими атрибутами. Значит, компилятор может этот факт в любой момент проверить напрямую, сверив определения класса и интерфейса (причем класс может быть в скомпилированной форме). В любом месте, где мы хотим использовать объект класса Bar (или его подкласса), а ожидают интерфейс Foo - например, в списке аргументов какого-то метода - сейчас компилятор проверяет правильность подстановки, основываясь на том, что в определении Bar написано "implements Foo". Но если бы не было написано, он все равно мог бы проверить правильность подстановки, просто напрямую сравнив типы методов класса и интерфейса.

(Я что-то упускаю тут или правильно все описал?)

Если это ключевое слово не нужно, зачем же оно есть? Зачем дизайнеры языка заставляют программиста специально написать, что класс Bar воплощает интерфейс Foo, если это не дает компилятору новой информации? Я бы сказал - по двум причинам; во-первых, из идеологических соображений, чтобы заставить программиста иметь в голове стройную картинку того, кто что воплощает. Во-вторых, чтобы была дополнительная проверка совместимости класса с интерфейсом в момент определения класса, а не только в момент его использования в качестве интерфейса. Без этой проверки, скажем, ошибка в определении класса, делающая его несовместимым с интерфейсом, обнаружилась бы только в момент использования класса там, где требуется интерфейс, а не во время компиляции самого класса с интерфейсом.

Что было бы лучше, если бы не было слова implements? Предположим, у меня есть некий достаточно простой интерфейс с одним-двумя методами, воплощающий некую идею; и класс, который написал не я и контролирую не я (возможно, только в скомпилированном виде), который воплощает эту идею, с ровно теми же именами методов и типами, но его создатели ничего не знали про мой интерфейс. Сейчас, чтобы их состыковать, мне надо делать дополнительный класс посредине, который прячет в себе объект настоящего класса и делегирует ему все, что нужно. Если бы компилятор не опирался на implements, а проверял настоящие типы, мне бы это не нужно было делать. Оно бы просто работало само по себе.

См. также интересную статью JavaGI: Generalized Interfaces for Java, которая натолкнула меня на эту нехитрую мысль. JavaGI - интересная попытка внести в Джаву идею type classes из Haskell. Предположим, у вас есть интерфейс и класс, скорее всего из разных источников (возможно, только в скомпилированном виде), причем класс как бы воплощает все, что нужно интерфейсу, но не под теми же именами, и возможно с некоторыми тривиальными изменениями. Обычное решение, опять-таки - промежуточный класс, Adapter pattern. Вместо этого предлагается новый синтаксис, который совмещает класс и интерфейс вместе, "объясняя" компилятору, как воплотить методы интерфейса в терминах методов класса, так, что после этого компилятор может подставлять объект класса туда, где нужен интерфейс, как обычно. Нет умножения сущностей в виде ненужного по сути дела промежуточного класса; вместе с тем сохраняются все проверки типизации, обычные для Джавы. Довольно любопытная идея.

Date: 2007-01-02 11:54 am (UTC)
From: [identity profile] 0bs3rv3r.livejournal.com
как насчет разных интерфейсов с одинаковыми методами?

Date: 2007-01-02 11:56 am (UTC)
From: [identity profile] avva.livejournal.com
А в чем проблема? Если у класса есть эти методы, он может притвориться любым из них.

(no subject)

From: [identity profile] 0bs3rv3r.livejournal.com - Date: 2007-01-02 12:02 pm (UTC) - Expand

(no subject)

From: [identity profile] gdy.livejournal.com - Date: 2007-01-03 06:12 pm (UTC) - Expand

Date: 2007-01-02 12:06 pm (UTC)
From: [identity profile] http://users.livejournal.com/_kir/
Третья причина - невозможность неявной имплементации интерфейса во избежание ошибочной идентификации обьектов класса как обьектов интерфейса.
Самый простой пример - интерфейс, не содержащий методов, как, например, java.io.Serializable (http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html) (обратите внимание на его отличие от java.io.Externalizable (http://java.sun.com/j2se/1.4.2/docs/api/java/io/Externalizable.html)) и механизм сериализации, основанный на явной имплементации этих интерфейсов.
Другой пример - имплементация механизмов instanceof (а также всего, что связано с reflection).

Date: 2007-01-02 12:16 pm (UTC)
From: [identity profile] dimrub.livejournal.com
Кстати, использование пустых интерфейсов для декларации определенного поведения объектов класса - это хэк, как раз-таки и решаемый использованием type classes Хаскелля, как мне кажется. А instanceof это вещь сугубо противоречащая философии Джавы (как подмножества философии OO), как я ее понимаю (хотя зачастую без него ну совершенно не обойтись, и это, видимо, проблема языка - хотя во многих случаях дженерики, видимо, ее решают).

(no subject)

From: [personal profile] nine_k - Date: 2007-01-02 01:01 pm (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-02 01:08 pm (UTC) - Expand

(no subject)

From: [identity profile] madfire.livejournal.com - Date: 2007-01-02 02:47 pm (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-02 02:53 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-02 02:57 pm (UTC) - Expand

(no subject)

From: [identity profile] madfire.livejournal.com - Date: 2007-01-02 03:04 pm (UTC) - Expand

(no subject)

From: [identity profile] eugenius-nsk.livejournal.com - Date: 2007-01-03 05:26 pm (UTC) - Expand

Date: 2007-01-02 12:23 pm (UTC)
From: [identity profile] dejavit.livejournal.com
Насколько я понимаю, без implements совмещение интерфейсов и имплементаций будет стоить вам O(кол-во интерфейсов * кол-во классов) проверок.
То есть, сделать matching можно, но в большой системе это может оказаться запредельно дорого.

Date: 2007-01-02 12:37 pm (UTC)
From: [identity profile] arbat.livejournal.com
проверьте - сделайте ошибку. Если это слово позволяет Java не проверять - то она ошибку не заметит.

(no subject)

From: [identity profile] gde-moi-17-let.livejournal.com - Date: 2007-01-02 01:40 pm (UTC) - Expand

+1

Date: 2007-01-02 02:48 pm (UTC)
From: [identity profile] yurilax.livejournal.com
да, только хотел написать: всё проверять - дороговато будет для компилятора.

Date: 2007-01-02 12:54 pm (UTC)
From: [identity profile] faceted-jacinth.livejournal.com
> Зачем дизайнеры языка заставляют программиста специально написать, что класс Bar воплощает интерфейс Foo

Для того, чтобы компилятор мог проверить, что класс Bar действительно воплощает интерфейс Foo.

В реальности-то проверка типизации как правило оказывается реалтаймовой: есть у вас какая-то дырка, откуда лезут объекты, вы каждому из них делаете IFoo foo = someObject as IFoo (в шарповом синтаксисе) и работаете с теми, которые на самом деле поддерживают интерфейс Foo.
Соответственно, как разработчик того места, откуда в дырке появляются объекты, вы хотите защититься от случайного (ошибочного) изменения сигнатуры какого-нибудь из типов, в результате которого он перестанет поддерживать интерфейс. С явным указанием implements вы получаете ошибку на этапе компиляции, с гипотетическим неявным - в лучшем случае инвалид каст при исполнении, в худшем - непонятное поведение программы, у которой вдруг начинает что-то куда-то исчезать.

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

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

Ну и последнее, наверное: если б этого не было в синтаксисе, правильным программистам пришлось бы писать "implements IDisposable" в комментариях =)

Date: 2007-01-02 01:42 pm (UTC)
From: [identity profile] http://users.livejournal.com/_rowan_tree_/
1. Static compilation, type safety - выявить ошибки типов как можно раньше, т.е. на стадии компиляции.
2. Декларация типов - важный элемент software design, он позволяет программисту объяснить место каждого типа и их взаимоотношение друг с другом. Подробнее см., например, в "Agile software development: Principles, patterns, and
practices" by Robert C. Martin
(http://search.barnesandnoble.com/bookSearch/isbnInquiry.asp?r=1&isbn=0135974445)

Date: 2007-01-02 01:53 pm (UTC)
From: [identity profile] avva.livejournal.com
1. Мне кажется, вы не поняли. Все проверки проводятся ровно в то же время, во время компиляции. Просто компилятору не нужно знать, что класс специально заявил о том, что воплощает некий интерфейс - он может проверить это (во время компиляции) пройдясь по спискам методов интерфейса и класса.
2. Это да, это подпадает под "идеологию".

(no subject)

From: [identity profile] http://users.livejournal.com/_rowan_tree_/ - Date: 2007-01-02 02:19 pm (UTC) - Expand

(no subject)

From: [identity profile] http://users.livejournal.com/_rowan_tree_/ - Date: 2007-01-02 02:20 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-02 02:38 pm (UTC) - Expand

(no subject)

From: [identity profile] http://users.livejournal.com/_rowan_tree_/ - Date: 2007-01-02 02:56 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-02 03:05 pm (UTC) - Expand
(deleted comment)

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-04 11:24 am (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 06:33 am (UTC) - Expand
(deleted comment)

Date: 2007-01-02 02:41 pm (UTC)
From: [identity profile] avva.livejournal.com
1. Не знаю, как насчет Java, а во многих других языках интерфейсы - это не только компиляторная фишка, но и определенная бинарная совместимость. Бинарная совместимость нужна там, где никакими компиляторами уже давно и не пахнет.

Это да. В C++ это важно. В Джаве, если я правильно понимаю - нет.
Там классы "тяжелые", они и во время выполнения тащат за собой много всякой информации, и организованы и так посложнее, чем строгий vtable а-ля C++, где вызов метода - одна инструкция косвенного вызова.

Date: 2007-01-02 02:44 pm (UTC)
From: [identity profile] madfire.livejournal.com
duck typing

Date: 2007-01-02 02:45 pm (UTC)
From: [identity profile] avva.livejournal.com
Нет. Все проверки остаются в compile time.

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-02 02:48 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-02 02:53 pm (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-02 03:08 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-02 03:15 pm (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-02 03:22 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-02 03:34 pm (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 06:44 am (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-04 10:54 am (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 11:41 am (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-04 11:46 am (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 11:55 am (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-04 11:58 am (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 01:05 pm (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-04 12:47 pm (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 01:13 pm (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-04 01:49 pm (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-05 02:47 am (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-05 12:01 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-04 11:25 am (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 11:38 am (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-04 12:07 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-02 02:45 pm (UTC) - Expand

(no subject)

From: [identity profile] madfire.livejournal.com - Date: 2007-01-02 02:51 pm (UTC) - Expand

(no subject)

From: [identity profile] siludin.livejournal.com - Date: 2007-01-02 05:52 pm (UTC) - Expand

Постепенно просыпаясь...

Date: 2007-01-02 02:49 pm (UTC)
From: [identity profile] http://users.livejournal.com/_rowan_tree_/
Не, не будет работать. Представьте себе, что у вас есть интерфейсы Foo и Bar, оба декларирующие метод name. Представьте, что в некотором классе есть два метода:
m(Foo f)
m(Bar b).
Теперь представьте, что у вас есть класс с методом name и вызовите метод m с этим объектом в качестве параметра. Если у вас объявлено, который из интерфейсов он implements, то вызовется правильный m, а если не объявлено, то плохо дело.

Подозреваю также, что будут проблемы с bounded generics, но пример пока не приходит в голову.

Date: 2007-01-02 02:55 pm (UTC)
From: [identity profile] avva.livejournal.com
С добрым утром!

Да, здесь будет ошибка во время компиляции, требующая разрешить конфликт. Но чем это принципиально отличается от ситуации, когда сейчас у вас есть класс, который заявляет, что он implements и Foo, и Bar?

Место для конфликтов есть всегда :)

Date: 2007-01-02 03:13 pm (UTC)
From: [identity profile] onodera.livejournal.com
В новой версии Visual Basic (не знаю, будет ли такое в C#, там похуже с поздним связыванием), Orcas, собираются ввести динамические (в том числе и анонимные, кажется) интерфейсы.
То есть можно будет получить из дырки объект и повесить на него любой подходящий интерфейс при выполнении программы.

Date: 2007-01-04 06:59 am (UTC)
From: [identity profile] 109.livejournal.com
Orcas - это code name следующей версии Visual Studio, а не бейсика или там сишарпа. и нет, в дотнете 3.0 нет никаких динамических или "анонимных, кажется" интерфейсов. анонимный интерфейс - это что вообще такое?

вот что там есть релевантное данному посту, так это type inference of lambda expressions.

Date: 2007-01-02 03:24 pm (UTC)
From: [identity profile] faceted-jacinth.livejournal.com
Кстати, ещё никто не упомянул рефакторинг.

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

Date: 2007-01-02 03:27 pm (UTC)
From: [identity profile] cmm.livejournal.com
кроме упомянутого в комментариях выше, надобно также помнить что идентичность метода в языке типа жабы -- это два символа (класс/интерфейс + имя), а не один.

Date: 2007-01-02 03:30 pm (UTC)
From: [identity profile] avva.livejournal.com
Не понял. При чем тут идентичность метода?

Я бы как раз сказал, что дело в том, что идентичность класса/интерфейса основана на имени, а не на структуре. Т.е. если я напишу два класса с совершенно идентичными методами, но назову их по-разному, это два разных класса, и там, где хотят один из них, второй не пройдет.

(no subject)

From: [identity profile] cmm.livejournal.com - Date: 2007-01-02 03:39 pm (UTC) - Expand

Date: 2007-01-02 05:26 pm (UTC)
From: [identity profile] max630.livejournal.com
То есть как в динамических языках?

IMHO - для такого языка как Java слишком либерально. Интерфейс - это не только типы методов, но и семантика их работы. Когда человек пишет implements - он понимает что он делает и может за это отвечать. Когда кто-то меняет реализацию - он смотрит на список implements и знает из него, в каких пределах он это может делать.

Кстати, вероятность случайного совпадения типов методом кажется мне небольшой. Уж не делается ли у Вас интерфейс из существующего класса?

Date: 2007-01-02 05:48 pm (UTC)
From: [identity profile] shmel39.livejournal.com
А ведь можно пойти дальше! Например, убрать типизацию вообще, как это сделано в Smalltalk.

А то, что Вы предлагаете - это ИМХО половинчатое решение. В комментах было уже несколько раз сказано, что это и необходимость думать при написании, и рефакторинг, и отлов ошибок/опечаток, и бОльшая ясность при чтении, кстати (вот читая чужой код, как узнать что какой интерфейс реализует? по комментариям разве что, да их не все пишут :-( ), и многое другое.

Ну и плюс, это сложнее для компилятора. Так что, ИМХО, implements в Java быть. А для динамической типизации есть соответствующие языки. Все-таки compile-time errors - это лучше, чем ночь мучительного дебага.

Date: 2007-01-02 06:59 pm (UTC)
From: [identity profile] eugenius-nsk.livejournal.com
> Например, убрать типизацию вообще, как это сделано в Smalltalk.

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

(no subject)

From: [identity profile] salas.livejournal.com - Date: 2007-01-02 09:48 pm (UTC) - Expand

(no subject)

From: [identity profile] eugenius-nsk.livejournal.com - Date: 2007-01-03 05:49 am (UTC) - Expand

(no subject)

From: [personal profile] nine_k - Date: 2007-01-03 01:30 am (UTC) - Expand

(no subject)

From: [identity profile] eugenius-nsk.livejournal.com - Date: 2007-01-03 06:44 am (UTC) - Expand

(no subject)

From: [personal profile] nine_k - Date: 2007-01-03 01:31 am (UTC) - Expand

(no subject)

From: [identity profile] eugenius-nsk.livejournal.com - Date: 2007-01-03 06:45 am (UTC) - Expand

(no subject)

From: [identity profile] shmel39.livejournal.com - Date: 2007-01-03 02:53 am (UTC) - Expand

(no subject)

From: [identity profile] eugenius-nsk.livejournal.com - Date: 2007-01-03 06:32 am (UTC) - Expand

(no subject)

From: [identity profile] shmel39.livejournal.com - Date: 2007-01-03 07:51 am (UTC) - Expand

(no subject)

From: [identity profile] eugenius-nsk.livejournal.com - Date: 2007-01-03 05:49 pm (UTC) - Expand

(no subject)

From: [identity profile] faceted-jacinth.livejournal.com - Date: 2007-01-03 10:27 pm (UTC) - Expand

(no subject)

From: [identity profile] eugenius-nsk.livejournal.com - Date: 2007-01-04 01:09 pm (UTC) - Expand

(no subject)

From: [identity profile] shmel39.livejournal.com - Date: 2007-01-04 07:13 am (UTC) - Expand

Date: 2007-01-02 05:51 pm (UTC)
From: [identity profile] notbrainsurgery.livejournal.com
При работе с типами данных в runtime через reflection ваш подход потребует
сравнения сигнатур классов в момент исполенния программы что ударит по производительности.

Я часто использую так называемые tagging interfaces, которые не требуют реализации никаких методов, но позволяют классифицировать объекты по принадлежности к ним. Это удобно. Конечно это можно было бы сделать иначе, и interface тут чиста синтаксический сахар, но
это удобно.

Date: 2007-01-02 10:28 pm (UTC)
From: [identity profile] cmm.livejournal.com
> Это будет интересно только программистам.

думаю начать использовать эту фразу в качестве варианта "держите меня семеро".
из уважения к правам человека.

Date: 2007-01-02 10:35 pm (UTC)
From: [identity profile] avva.livejournal.com
:) Я могу на что-то другое переключиться! Есть идеи?

(no subject)

From: [identity profile] cmm.livejournal.com - Date: 2007-01-03 06:29 am (UTC) - Expand

offtopic

Date: 2007-01-03 01:28 am (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
А видали ли вы язык D? (http://www.digitalmars.com/d/index.html)
В нём, кажется, исправлено практически всё, что мне мешало в C, и введено много из других полезных языков, тем не менее, получается быстроходный native code.

Re: offtopic

Date: 2007-01-03 06:57 am (UTC)
From: [identity profile] yurilax.livejournal.com
да, performance очень даже ничего:

Image


http://shootout.alioth.debian.org/debian/benchmark.php?test=all?=dlang


стоит присмотреться.

Date: 2007-01-03 04:47 am (UTC)
From: [identity profile] igorlord.livejournal.com
Сейчас, чтобы их состыковать, мне надо делать дополнительный класс посредине, который прячет в себе объект настоящего класса и делегирует ему все, что нужно.

Делигировать ручками ничего не надо. Всё делегируется автоматически (кроме конструктора).

class CuteWidget extends Widget implements Cute
{
}

Date: 2007-01-03 11:26 pm (UTC)
From: [identity profile] cousin-it.livejournal.com
Клево, не знал

Date: 2007-01-03 11:26 pm (UTC)
From: [identity profile] cousin-it.livejournal.com
Yep it's ideology. Java doesn't have structural subtyping, type inference or duck typing. And no inference of exception specifications, while we're at it.

Date: 2007-01-04 06:13 am (UTC)
From: [identity profile] 109.livejournal.com
здесь сразу два момента. во-первых, при отсутствии декларации проверять частенько придётся в рантайме. что без отсутствия декларации придётся делать через рефлекшн, что медленно. во-вторых, теряется семантика - в целях каковой, например, существуют пустые интерфейсы. какой смысл в пустом интерфейсе, а?

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

Date: 2007-01-04 11:20 am (UTC)
From: [identity profile] avva.livejournal.com
здесь сразу два момента. во-первых, при отсутствии декларации проверять частенько придётся в рантайме.

Устал повторять, что те же проверки можно сделать во время компиляции.

например, существуют пустые интерфейсы. какой смысл в пустом интерфейсе, а?

Действительно, какой? Это хак, происходящий от отсутствия в языке traits, type classes, или прочих способов приписать какой-то custom атрибут типу. Интерфейс должен быть контрактом. Пустой контракт - не контракт, или в данном случае "контракт по имени". Контракт по имени - хак. Я умею делать X, ты умеешь делать X, давай дружить, что с того, что это ничего не значит.

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

Следите за языком, пожалуйста.

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 11:49 am (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2007-01-04 12:05 pm (UTC) - Expand

(no subject)

From: [identity profile] 109.livejournal.com - Date: 2007-01-04 01:01 pm (UTC) - Expand

January 2026

S M T W T F S
    1 2 3
4 5 6 7 8 910
11121314151617
18192021222324
25262728293031

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 11th, 2026 04:14 pm
Powered by Dreamwidth Studios