tag:blogger.com,1999:blog-68273632805925329572024-02-08T08:24:51.703+02:00cat **/* | grep яAkshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.comBlogger88125tag:blogger.com,1999:blog-6827363280592532957.post-68789282238346070392012-09-14T13:05:00.001+03:002012-09-14T17:40:28.486+03:00Мысли о структуре данных приложения, проблеме "старого состояния" + ненавязчивое введение в ScalaЗдесь я попробую изложить имеющиеся мысли о такой нелёгкой задаче как построение гибкой модели предметной области,
а если более конкретно, то о множественных связях внутри такой модели. В качестве языка программирования будет
использован статически типизированный язык Scala. Scala выбрана мной, как и множеством именитых компаний,
из-за её мощной системы типов (впрочем не такой мощной как у Haskell, но намного мощнее чем, например, у Java)
и практичности использования.
Изложение попробую вести так, чтобы было понятно даже тем, кто Java помнит с трудом, не говоря уже о Scala.
Впрочем получится у меня или нет - посмотрим.<BR/>
Итак...
<h3>... в чем собственно проблема.</h3>
Пусть мы хотим смоделировать хранилище информации об исполнителях, альбомах и песнях. Следуя принципу DRY (не повторяйся),
планируем использовать модель как при реализации самого хранилища, так и при реализации софта, работающего с этим хранилищем.
Пусть, к примеру, для простоты, песня (Song) характеризуется названием (title), годом издания (year) и конечно же коллекцией
исполнителей (artists). Уже ясно, что в нашей модели есть ещё и исполнитель (Artist). Пусть у исполнителя есть имя (name)
и <a href="http://otar-muhtarov.ru/">частная домашняя личная персональная страничка (website).</a> Иногда песни собираются в альбомы
(или в более широком смысле - в компиляции). Компиляция (Compilation) характеризуется годом выпуска, песнями (songs), которые
в него входят, и конечно же названием (title). Итак, у нас вырисовались типы (с большой буквы - Song, Artist, Compilation)
и их свойства (с маленькой буквы - title, website, songs, ...), напишем это все в виде кода:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span><span style="background-color: #400000;"> </span><span style="color: #98fb98;">Artist</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>)</pre>
(серым цветом текст для тех, кто скалу не знает)<BR/>
<span style="color: #888;">
"case class" - ключевые слова указывающие, что это объявление case class'а. По своей сути, case class - это тот же
старый добрый class (прям как в Java), но с некоторым набором удобных плюшек. После ключевых слов идёт название класса
(Artist) и далее, в скобочках, аргументы конструктора(!) класса. В отличии от Java, тип данных указывается <b>после</b>
имени аргумента через двоеточие (для этого есть определённые причины, в частности, используя этот подход, вы с таким
же успехом можете подсказывать компилятору правильный тип где угодно в коде, даже посреди выражения, все достаточно
унифицировано). Тела у класса нет. Можно было бы конечно написать пустые фигурные скобки {}, как в Java,
но тут их можно опустить. Итого, у нас есть имя класса, аргументы конструктора и все! В чем полезность такого класса?
Это было бы действительно бесполезным определением, если бы не ключевое слово case. Благодаря его наличию, компилятор
генерирует:
<ul>
<li>конструктор, который принимает два аргумента типа java.lang.String и присваивает их значения полям внутри класса</li>
<li>для каждого аргумента метод, возвращающий значение из поля объекта, т.е. будут созданы getter'ы
(так как аргументы не объявлены с модификатором var, то методы для изменения значений объекты созданы не будут,
об этом позже)
)</li>
<li>осмысленные методы hashCode, equals, toString, использующие оба поля</li>
</ul>
Уже неплохо! Компилятор сделает за вас все то, что вы делаете сами, описывая свои модели (предметную область, доменные объекты) в Java.
Но это не все. Также будет создан метод copy (про него ниже) и ещё всякое полезное...
В том числе архиполезный метод для сопоставления с шаблоном (pattern matching), из-за которого case class и получил своё название...
Но про него я тут вообще писать ну буду. А т.к. скала компилирует исходники в байткод для виртуальный машины java (JVM), то мы без проблем
можем проверить все, что я тут написал, воспользовавшись любым (jad, jd-gui, ..) декомпилятором байткода:
<pre>
<span style="color: #00ffff;">public</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span> <span style="color: #00ffff;">implements</span> <span style="color: #98fb98;">Product</span>, <span style="color: #98fb98;">Serializable</span> {
<span style="color: #00ffff;">private</span> <span style="color: #00ffff;">final</span> <span style="color: #98fb98;">String</span> <span style="color: #eedd82;">name</span>;
<span style="color: #00ffff;">private</span> <span style="color: #00ffff;">final</span> <span style="color: #98fb98;">String</span> <span style="color: #eedd82;">website</span>;
<span style="color: #00ffff;">public</span> Artist(<span style="color: #98fb98;">String</span> <span style="color: #eedd82;">name</span>, <span style="color: #98fb98;">String</span> <span style="color: #eedd82;">website</span>) {
<span style="color: #00ffff;">this</span>.name = name;
<span style="color: #00ffff;">this</span>.website = website;
<span style="color: #00ffff;">super</span>();
}
<span style="color: #00ffff;">public</span> <span style="color: #98fb98;">String</span> <span style="color: #87cefa;">name</span>() { <span style="color: #00ffff;">return</span> <span style="color: #00ffff;">this</span>.name; }
<span style="color: #00ffff;">public</span> <span style="color: #98fb98;">String</span> <span style="color: #87cefa;">website</span>() { <span style="color: #00ffff;">return</span> <span style="color: #00ffff;">this</span>.website; }
<span style="color: #00ffff;">public</span> <span style="color: #98fb98;">Artist</span> <span style="color: #87cefa;">copy</span>(<span style="color: #98fb98;">String</span> <span style="color: #eedd82;">name</span>, <span style="color: #98fb98;">String</span> <span style="color: #eedd82;">website</span>) { <span style="color: #00ffff;">return</span> <span style="color: #00ffff;">new</span> <span style="color: #98fb98;">Artist</span>(name, website); }
<span style="color: #00ffff;">public</span> <span style="color: #98fb98;">String</span> <span style="color: #87cefa;">copy$default$2</span>() { <span style="color: #00ffff;">return</span> website(); }
<span style="color: #00ffff;">public</span> <span style="color: #98fb98;">String</span> <span style="color: #87cefa;">copy$default$1</span>() { <span style="color: #00ffff;">return</span> name(); }
<span style="color: #ff7f24;">/* </span><span style="color: #ff7f24;">... */</span>
}</pre>
</span>
<BR/>
Ну и объявим ещё два типа (ещё два класса):
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span>)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[Song]</span>)</pre>
<span style="color: #888;">
artists является множеством (т.е. элементы не повторяются) элементов типа Artist,
а songs представлено списком (т.е. песни идут в определённом порядке) элементов типа Song.
</span>
<BR/><BR/>
Немного подумав, для удобства добавим значения по умолчанию, и вспомнив, что у сохранённых в базе данных
объектов будет идентификатор (пусть он будет целым числом), учтем ещё и это:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span><span style="background-color: #400000;"> </span><span style="color: #98fb98;">Artist</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[Song]</span> = List.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)</pre>
<span style="color: #888;">
Очевидно, что код идущий после знака "равно" является значением по умолчанию для соответствующего аргумента.
Работает это просто - если при вызове метода/функции/конструктора вы не указываете аргумент, то Scala использует
значение по умолчанию. Выше я уже говорил, что для case class'ов Scala создает метод copy. И правда, в декомпилированном
коде выше, в котором Artist имеет только name и website, вы можете увидеть этот метод.
Там же, рядом, вы увидите два метода со странными названиями copy$default$1
и copy$default$2. Первый, как видно из кода, всегда возвращает первый аргумент (name), а второй... ну вы поняли.
Эти методы используются скалой для получения значений по умолчанию для аргументов метода copy! Это значит, что создать объект,
аналогичный данному за исключением каких-то полей - проще простого! Например:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">x </span>= <span style="color: #00ffff;">new</span> <span style="color: #98fb98;">Artist</span>(name = <span style="color: #ffa07a;">"Me"</span>, website = <span style="color: #ffa07a;">"http://127.0.0.1"</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">y </span>= <span style="color: #13ff12;">x</span>.<span style="color: #24BEE3;">copy</span>(name = <span style="color: #ffa07a;">"You"</span>)</pre>
отобразит
<pre>
Artist(Me, http://127.0.0.1, None)
Artist(You, http://127.0.0.1, None)</pre>
Идентификатор записи в базе данных у нас опциональный. Т.е. может быть (объект загружен из базы данных),
а может и не быть (мы его ещё только планируем создать).
Тип Option[T] является запечатанным абстрактным типом. В природе существует аж два класса, для которых Option
является родительским классом. Первый - это case class Some[T](value : T). Второй - это объект (синглетон) с именем None.
Так как Option[T] запечатан (sealed), то больше типов унаследовать от Option[T] вы не можете. Поэтому, если видите
Option[T], то вы всегда уверены, что перед вами либо Some(value), либо None. Может показаться, будто это какая-то
странная вариация на тему null. Но это не так. Если вы видите, что функция возвращает объект типа Option[String], то вы
всегда(!) знаете, что результат может быть None. А если вы (в Java) видите, что метод возвращает просто String - ну хорошо,
если поведение описано в документации, которую читают реже, чем пишут, замечательно, если стоит аннотация.. но в 99% случаев
можете только гадать: будет там null или не будет. И даже если сегодня вы точно знаете, что такой метод вам сейчас возвращает
всегда строку, то нет никакой гарантии, что завтра он не будет возвращать null! Ещё одно отличие то, что None является
полноценным объектом, у него есть методы и все такое. Иногда о типе Option[T] удобно думать как о коллекции, которая состоит либо
из одного элемента, либо вообще пустая, но это я уже отвлекся..
Да, я намеренно не стал делать website и year типами Option[T] дабы не загромождать повествование.
Пусть они будут обязательными, хотя в реальном мире они были бы также типом Option[T].
</span>
<BR/><BR/>
И для полной картины нашего примера не достает ещё одного класса. Добавим его:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[Song]</span> = List.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>(<span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty,
<span style="color: #13ff12;">songs:</span> <span style="color: #084EA8;">Set[Song]</span> = Set.empty,
<span style="color: #13ff12;">compilations:</span> <span style="color: #084EA8;">Set[Compilation]</span> = Set.empty)</pre>
т.е. я добавил класс Task, олицетворяющий задачу, над которой мы работаем. Например, задачу по добавлению
новых песен в коллекцию. Предположим, что пользователь с помощью какого-то интерфейса (чудом) набрал нам то, что по его мнению
необходимо добавить в базу данных. Много и сразу надо добавить. Некоторые песни не имеют исполнителей.
Какие-то исполнители без песен и т. д. В общем все не просто, но типично для жизни. И вот перед тем как добавить все в базу
данных, надо нам эти данные причесать: связаться с другими базами данных, гуглом и много чем ещё, проверить, может где ошибка,
опечатка, исправить, год подправить, дополнить, в общем надо нам с этим счастьем теперь работать и видоизменять до неузнаваемости. Или, если
в терминах приложения, то из одной Task'и нам надо сделать другую Task'у путём изменения свойств объектов.<BR/><BR/>
Все просто и вы не видите проблем? Тогда скорее всего вы думаете про...
<h3>... императивный подход</h3>
Для этого нашу модель следует сделать изменяемой (mutable), так как по умолчанию case class'ы неизменны (immutable).
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>(<span style="color: #00ffff;">var</span> <span style="color: #e06022;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #00ffff;">var</span> <span style="color: #e06022;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #00ffff;">var</span> <span style="color: #e06022;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>(<span style="color: #00ffff;">var</span> <span style="color: #e06022;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #00ffff;">var</span> <span style="color: #e06022;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #00ffff;">var</span> <span style="color: #e06022;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty, <span style="color: #00ffff;">var</span> <span style="color: #e06022;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>(<span style="color: #00ffff;">var</span> <span style="color: #e06022;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #00ffff;">var</span> <span style="color: #e06022;">songs:</span> <span style="color: #98fb98;">List[Song]</span> = List.empty, <span style="color: #00ffff;">var</span> <span style="color: #e06022;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>(<span style="color: #00ffff;">var</span> <span style="color: #e06022;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty,
<span style="color: #00ffff;">var</span> <span style="color: #e06022;">songs:</span> <span style="color: #084EA8;">Set[Song]</span> = Set.empty,
<span style="color: #00ffff;">var</span> <span style="color: #e06022;">compilations:</span> <span style="color: #084EA8;">Set[Compilation]</span> = Set.empty)</pre>
<span style="color: #888;">
Очевидно, что модификатор var перед именем аргумента конструктора означает... variable, т.е. переменная,
т.е. меняет своё значение! Scala поощряет неизменность состояний и по умолчанию подразумевает
модификатор val (т.е. value, т.е. значение) для case class'ов. Как только мы добавляем модификатор var, scala
генерирует метод (сеттер) для смены значения переменной. Оператор присваивания именно этот сеттер и вызывает.
(замечу, что и геттеры, и сеттеры можно определить явным способом, если(когда) сочтёте нужным)
</span><BR/><BR/>
Работа с изменяемыми объектами в Scala мало чем отличается от всех остальных императивных языков. В том числе,
вы получаете весь букет проблем присущий такому подходу:
<ul>
<li>
Вы не можете использовать ваши объекты в качестве ключей. Любое изменение состояния влечет смену hashcode'а объекта,
а по хэшкоду объект ищется в map'ах в первую очередь.
</li>
<li>
Передавая изменяемый объект в качестве параметра в метод, у вас нет НИКАКОЙ гарантии, что его там не искалечат из
добрых побуждений (отсортируют список в поле).
</li>
<li>
К примеру, вызывая метод transform и передавая в него task, вы вероятно получаете task уже в
другом состоянии по завершению работы метода. Единственный способ проверить, было ли изменение в таком случае -
клонировать (с глубоким копированием (deep clone)) всю task'у, а потом сравнить! Если вы попробуете это реализовать,
то упрётесь в проблемы похожие на те, которые я опишу в следующем подходе. В результате поимеете проблемы двух миров.
Единственным быстрым решением может быть только сериализация Task'и и последующая десериализация. Либо вариации на тему.
А что, если одним из полей является блоб фотографии исполнителя? Об эффективности можно забыть. Как вариант, вы можете попросить
transform возвращать взведённый флажок для случая, когда состояние изменилось, но это означает "до свидания красивый API".
</li>
<li>
А что, если новые изменения делаются глядя на старое состояние.. Тогда вам надо держать под рукой старое состояние task'и внутри
метода transform. Это в очередной раз означает клонирование ВСЕГО состояния.
</li>
<li>
Обновили часть состояния, получили эксепшн... у вас транзакционная память? Нет? Печалька:
теперь ваша структура в полурабочем виде.
</li>
<li>
И так далее, и тому подобное. А если кто-то думает, что всё это решается хорошим стилем
программирования, дисциплиной, документацией... То я вам завидую, вы живете в другом мире.
</li>
</ul>
Перечислять можно долго. После того, как я набрал вышесказанное, я попытался найти в интернете цитату Joshua Bloch
(автора Effective Java), которую когда-то где-то видел, чтобы привести её буквально, вот она:
"Classes should be immutable unless there's a very good reason to make them mutable....
If a class cannot be made immutable, limit its mutability as much as possible."
(перевод: Классы должны иметь неизменное состояние, если для обратного нет очень хорошей причины...
Если класс невозможно сделать неизменным, ограничьте его изменчивость настолько, насколько сможете)
Цитату нашёл по первой же <a href="http://www.javapractices.com/topic/TopicAction.do?Id=29">ссылке</a> и
обнаружил ещё больше аргументов на эту тему. В общем, кто сомневается - интернет в помощь. Эта тема разжёвана
весьма прилично (в отличии от той, ради которой я вообще это пишу).
Итак императивный подход нам не подходит. Посмотрим на альтернативу, на...
<h3>... функциональный подход</h3>
Вообще, строго говоря, изменчивость не является критерием функциональности или императивности.
Но частота, с которой неизменность данных применяется при этих подходах, отличается кардинально.
<BR/><BR/>
Итак наша модель:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[Song]</span> = List.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>(<span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty,
<span style="color: #13ff12;">songs:</span> <span style="color: #084EA8;">Set[Song]</span> = Set.empty,
<span style="color: #13ff12;">compilations:</span> <span style="color: #084EA8;">Set[Compilation]</span> = Set.empty)</pre>
Теперь наша функция transform принимает в качестве аргумента Task и возвращает результат как Task.<BR/>
<span style="color: #888;">
Несмотря на то, что функцию transform я не намерен здесь реализовывать, я покажу, как она может выглядеть изначально:
<pre>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">transform</span>(<span style="color: #ffff70;">original</span>: <span style="color: #98fb98;">Task</span>): <span style="color: #98fb98;">Task </span>= <span style="color: #ffff70;">original</span></pre>
это можно прочитать как:
<ul>
<li>функция transform принимает аргумент типа Task и возвращает результат типа Task.
Результатом будет значение из аргумента. (после знака равно следует определение функции)
</li>
</ul>
или это можно прочитать вот так:
<ul>
<li>значение transform является функцией из-значения-типа-Task-в-значение-типа-Task, т.е. Task => Task</li>
</ul>
впрочем, это и записать так можно:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">transform </span>= (<span style="color: #ffff70;">original</span> : <span style="color: #98fb98;">Task</span>) => <span style="color: #ffff70;">original</span></pre>
или так:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">transform </span>: <span style="color: #084EA8;">Function1[Task, Task]</span> = <span style="color: #ffff70;">original</span> => <span style="color: #ffff70;">original</span></pre>
хотя, например, Java программисту вот такая запись должна показаться очень даже родной (разве что public нет, но он тут по умолчанию):
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">transform </span>= <span style="color: #00ffff;">new</span> <span style="color: #084EA8;">Function1[Task, Task]</span> () {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">apply</span>(<span style="color: #ffff70;">original</span>: <span style="color: #98fb98;">Task</span>): <span style="color: #98fb98;">Task </span>= {
<span style="color: #00ffff;">return</span> <span style="color: #ffff70;">original</span>
}
}</pre>
и это как раз то, во что компилируется (грубо говоря) любая функция, которая не является методом класса. Для полноты
замечу, что return нужен только, если выход из функции осуществляется в середине её тела (например, по условию),
а результатом и так будет последнее выражение в теле функции или конструкции. Также можно не указывать тип возвращаемого значения.
Скала это выведет сама, зная лишь только тип аргумента метода apply. Ну и напоследок:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">transform </span>= <span style="color: #00ffff;">new</span> <span style="color: #084EA8;">Function1[Task, Task]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">apply</span>(<span style="color: #ffff70;">original</span>: <span style="color: #98fb98;">Task</span>) = <span style="color: #ffff70;">original</span>
}</pre>
Можно придумать, как записать функцию иначе, в каждом случае удобно по-разному, но хватит пока про функции. Далее это пригодится.
</span><BR/><BR/>
Неизменность состояния устраняет все минусы, имеющиеся в императивном подходе.
Но поговорим мы про сложности использования функционального подхода.
<ul>
<li>
Если вы привыкли к императивному подходу, то функциональный может показаться сложным или вообще не реализуемым
(некоторые сходу не могут список просуммировать без использования цикла)
</li>
<li>
Если один объект, свойство которого необходимо изменить, используется множество
раз в пределах модели, то появляется проблема "старого состояния", имеющая прямую связь с проблемой
эквивалентности и зависящая от намерений модификации состояния.
</li>
</ul>
<h3>Проблема "старого состояния"</h3>
Пускай у нас есть такой граф данных:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntroll </span>= Artist(name = <span style="color: #ffa07a;">"Finntroll"</span>, website = <span style="color: #ffa07a;">"www.finntroll.net"</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">svampfest </span>= Song(title = <span style="color: #ffa07a;">"Svampfest"</span>, artists = Set(finntroll), year = 0)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">skog </span> = Song(title = <span style="color: #ffa07a;">"Skog"</span>, artists = Set(finntroll), year = 0)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">songs </span> = List(<span style="color: #026DF7;">skog</span>, <span style="color: #13ff12;">svampfest</span>)</pre>
И ВНЕЗАПНО мы хотим поменять www.finntroll.net на http://www.finntroll.net. Сделать так:
<pre>
<span style="background-color: #400000;">finntroll.website = "http://www.finntroll.net"</span></pre>
мы не можем: все наши объекты неизменяемые. Очевидно, что надо сделать так:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">newFinntroll </span>= finntroll.copy (website = <span style="color: #ffa07a;">"http://www.finntroll.net"</span>)</pre>
И вот на руках у нас есть newFinntroll, именующий (т.е. ссылающийся на) объект идентичный
тому, который именует (т.е. ссылается) имя finntroll за исключением значения в поле website.
Но песни svampfest и skog в нашем списке как ссылались на старую версию исполнителя, так и ссылаются.
Давайте создадим новые экземпляры песен, которые будут использовать новую версию исполнителя:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">newSvampfest </span>= svampfest.copy (artists = Set(newFinntroll))
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">newSkog </span> = skog.copy (artists = Set(newFinntroll))
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">newSongs </span> = List(newSkog, newSvampfest)</pre>
Теперь, наконец-то, у нас есть newSong, в которой граф полностью соответствует тому, что мы хотим.
Но есть одна проблема. Пример больно уж простой. В реальности при попытке обновить граф у нас будет:
<ul>
<li>songs</li>
<li>finntroll</li>
<li>newFinntroll</li>
</ul>
и на основе этого нам надо получить newSongs. Вобщем-то это не сложно: надо всего лишь пробежаться
по всем песням списка и для каждой песни создать (возможно) обновлённую версию, после чего положить её в новый список:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">newSongs</span> = <span style="color: #13ff12;">songs</span>.<span style="color: #24BEE3;">map</span> (<span style="color: #ffff70;">song</span> => <span style="color: #ffff70;">song</span>.<span style="color: #24BEE3;">copy</span>(artists = <span style="color: #ffff70">song.artists.replace</span>(finntroll, newFinntroll)))</pre>
(Можно было бы и избежать ненужных копирований,
но это, для данного случая, усложнило бы код ради экономии на спичках. Также я использовал функцию replace,
которой на самом деле на Set'ах нет. Но, во-первых, не трудно добавить, а, во-вторых, не относится к делу.)<BR/>
<span style="color: #888;">
Если код выше понятен, то этот текст серым цветом можно пропустить. Если нет, то поясняю:
<pre>
<span style="color: #ffff70;">song</span> => <span style="color: #ffff70;">song</span>.<span style="color: #24BEE3;">copy</span>(artists = <span style="color: #ffff70">song.artists.replace</span>(finntroll, newFinntroll))</pre>
это анонимная функция, где "song" является аргументом функции, а всё, что после стрелочки =>
является телом функции. То есть анонимная функция принимает одну переменную и возвращает
значение, вычисленное вот этим выражением:
<pre>
<span style="color: #ffff70;">song</span>.<span style="color: #24BEE3;">copy</span>(artists = <span style="color: #ffff70">song.artists.replace</span>(finntroll, newFinntroll))</pre>
Про метод copy я уже писал выше. Если кратко, то наша анонимная функция
создает копию песни, переданной ей в виде аргумента, но с
полем artists таким, что в коллекции исполнителей этой песни все finntroll
заменены на newFinntroll.
Определить такую же анонимную функцию на Java можно примерно так:
<pre>
<span style="color: #00ffff;">new</span> <span style="color: #98fb98;">Function1</span><Song, Song>() {
<span style="color: #00ffff;">public</span> <span style="color: #98fb98;">Song</span> <span style="color: #eedd82;">apply</span>(<span style="color: #98fb98;">Song</span> <span style="color: #eedd82;">song</span>) {
<span style="color: #00ffff;">return</span> song.artists.replace(finntroll, newFinntroll);
}
}</pre>
(при условии, что наша song.artists.replace не изменяет оригинальный список).
Итак, у нас есть анонимная функция, которая возвращает новую версию песни.
Т. е. с новым исполнителем, если он там был. Эта анонимная функция передается в метод map
списка songs. Метод map вызывает функцию для каждого элемента списка, генерируя
новый список, состоящий из результатов работы переданной в map функции. Т.е.
<pre>
List(7, 2, 5, 9).<span style="color: #24BEE3;">map</span>(<span style="color: #ffff70;">x</span> => <span style="color: #ffff70;">x</span> * 10)</pre>
эквивалентно
<pre>
List(7*10, 2*10, 5*10, 9*10)</pre>
</span>
С одной стороны ничего сложного, обошлись одной строчкой, а, с другой стороны, для модели с очень
маленькой вложенностью нам пришлось копировать... всю модель! И так для любого поля любого объекта.
Более того, надо всегда помнить, в каком месте какие поля куда ссылаются, чтобы случайно
не забыть обновить нужное значение. А если рассмотреть нашу модель целиком?
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[Song]</span> = List.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>(<span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Artist]</span> = Set.empty,
<span style="color: #13ff12;">songs:</span> <span style="color: #084EA8;">Set[Song]</span> = Set.empty,
<span style="color: #13ff12;">compilations:</span> <span style="color: #084EA8;">Set[Compilation]</span> = Set.empty)</pre>
При модификации исполнителя обновлять придётся всё, вплоть до корневого объекта (т.е. до Task).
Кстати, в природе, в Haskell мире, существует подход с названием
"Scrap your boilerplate", который позволяет независимо от структуры данных, очень грубо говоря,
обновлять все поля всех объектов, в пределах заданного графа, определённым образом при выполнении определённого условия
(обновлять здесь и далее подразумевает создание новых данных, а не модификация существующих).
В scala такой подход реализовать тоже возможно, но без помощи derive это очень уныло.. Но я отвлекся.
Существует ещё одна проблема. Считать ли newFinntroll эквивалентным finntroll? Интуитивно - да.
А практически - это два разных объекта, если мы будем сравнивать по полям (website у них отличается).
Как быть? Использовать какой-то id'и тем более, что он у нас есть в качестве поля каждого объекта.
По такому принципу собственно и работают ссылки. В качестве id объекта выступает адрес объекта
(грубо говоря номер байта в массиве памяти) и, соответственно, в переменных у нас хранятся адреса объектов. И потому, когда
в императивном программировании мы пишем song.website = "blah", мы используем адрес объекта из переменной
song для того, чтобы найти в памяти структуру объекта и записать по определённому смещению в структуре новый адрес,
ведущий на объект "blah"...
А когда делаем song2 = song, то мы не копируем объект, а копируем адрес этого объекта (я не говорю обо всех языках).
Аналогично и базы данных
с их ключами. Объекты валяются по таблицам, а граф строится с помощью ключей.
Получается ссылки у нас есть всегда и везде. Попробуем посмотреть на то, как работает база данных.
Пусть мы, как и в примере выше, обновляем исполнителя. При этом на этого исполнителя имеются ссылки из таблицы
песен. Что происходит? (очень грубое и приближённое описание с морем допущений и упрощений)
<ul>
<li>Начинается транзакция.</li>
<li>Берем строку таблицы Artists, соответствующую записи finntroll.</li>
<li>Создаем полную копию этой строки и добавляем в свободное место пространства таблицы
(мы ведь не хотим обновлять данные там где они сейчас, ибо в случае падения не откатимся)
</li>
<li>
(1) Делаем требуемые модификации в новом месте.
</li>
<li>
(2) У нас есть массив соответствия id и положения строки для этого id в пространстве таблицы.
Модифицируем массив так, чтобы id для finntroll указывала на новую запись.
</li>
<li>
Транзакция окончена.
</li>
</ul>
Если посмотреть внимательно, то при таком подходе лишь в одном месте реально происходит смена состояния (то, чего
мы избегаем). Это шаг 2. Шаг 1 это копирование с подменой (наш copy метод). В шаге 2 у нас меняется соответствие между
id объекта и физической ссылкой на объект. Или другими словами, мы создаем новое соответствие! Вот оно!
Из одной Map мы делаем другую Map. Взяв идею из баз данных, переложим её на нашу модель:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[Int]</span> = Set.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[Int]</span> = List.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)</pre>
В модели выше нет коллекций объектов, в ней нет способа найти нужный объект по id. Для этого нужна "база данных".
Вот она:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>(<span style="color: #13ff12;">artistMap:</span> <span style="color: #084EA8;">Map[Int, Artist]</span> = Map.empty,
<span style="color: #13ff12;">songMap:</span> <span style="color: #084EA8;">Map[Int, Song]</span> = Map.empty,
<span style="color: #13ff12;">compilationMap:</span> <span style="color: #084EA8;">Map[Int, Compilation]</span> = Map.empty) {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withArtist</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(Int, Artist)</span>) = <span style="color: #00ffff;">this</span>.<span style="color: #24BEE3;">copy</span>(artistMap = <span style="color: #13ff12;">artistMap</span> + <span style="color: #ffff70;">assoc</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withSong</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(Int, Song)</span>) = <span style="color: #00ffff;">this</span>.copy(songMap = songMap + assoc)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withCompilation</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(Int, Compilation)</span>) = <span style="color: #00ffff;">this</span>.copy(compilationMap = compilationMap + assoc)
}</pre>
<span style="color: #888;">
Выше я определил удобные методы для класса Task. Так, например, метод withArtist принимает
в качестве аргумента пару (кортеж), первый элемент которой типа Int, а второй типа Artist.
Тоже самое можно было бы записать вот так:
<pre>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withArtist</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">Tuple2[Int, Artist]</span>) = <span style="color: #00ffff;">this</span>.<span style="color: #24BEE3;">copy</span>(artistMap = <span style="color: #13ff12;">artistMap</span> + <span style="color: #ffff70;">assoc</span>)</pre>
Кстати, в scala, существует красивый способ создать кортеж с помощью оператора стрелочка: ->
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">x </span>= <span style="color: #ffa07a;">"a"</span> -> 2
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">y </span>= (<span style="color: #ffa07a;">"a"</span>, 2)</pre>
В примере выше результат созданий кортежей будет одинаковым, т.е. x == y.
Выражение вида artistMap + assoc является красивостью для вызова artistMap.+(assoc), т.е для вызова
метода с именем 'плюс' у объекта artistMap. В некоторых случаях точку можно опускать. Скобки можно опускать
только, если у метода всего один аргумент. Метод + из класса Map возвращает новую Map имеющую все те же
ассоциации между ключами и значениями плюс новая ассоциация. Операция эта относительно дешёвая:
копируется намного меньше, чем вы думаете.
</span><BR/><BR/>
Итак, вспомним всё ту же проблему, только теперь у нас есть task типа Task, а также finntroll и newFinntroll.
Только теперь всё проще:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">newTask </span>= <span style="color: #13ff12;">task</span> <span style="color: #24BEE3;">withArtist</span> (finntroll.<span style="color: #24BEE3;">id</span>.<span style="color: #24BEE3;">get</span> <span style="color: #24BEE3;">-></span> newFinntroll)</pre>
<span style="color: #888;">
Обращаю внимание, что finntroll.id.get, а не finntroll.id, т.к. id у нас типа Option[Int], а не Int. Делать так плохо, но об этом ниже.
</span><BR/>
Казалось бы все хорошо и жили они долго и счастливо, но есть ряд проблем:
<ul>
<li>1. Id может не быть. То есть вообще не быть. Совсем. Объект ещё не лежит в базе данных и вероятно не собирается туда ложиться.</li>
<li>2. В качестве ключей используются любые числа типа Int. Никто не мешает использовать ключ от Song, занося объект в Artist..</li>
<li>3. Когда мы обрабатываем текстовый файл с описанием музыкальной коллекции, логично позволять пользователю обозначать
элементы коллекции строковыми идентификаторами такими, какими ему будет удобно пользоваться при создании связей между элементами.
Все-таки человек не компьютер и запомнит слово finntroll лучше, чем число 1721.
</li>
</ul>
Попробуем устранить все эти проблемы и начнём с конца. Третья проблема подразумевает, что неплохо бы нам использовать строки. Но ведь
нам иногда удобно использовать числа в качестве ключей! Иногда числа.. иногда строки. Напрашивается абстракция. Пусть наша модель
будет параметризована типом ключа.<BR/>
<span style="color: #888;">
Чем функция отличается от значения? Много чем, и в тоже время вообще ничем не отличается. Все зависит от того, в чем мы заинтересованы.
По сути функция является отображением множества своих аргументов в множество результатов. Отображение чего-то во что-то можно
рассматривать как значение (мы же рассматриваем объекты типа Map как значения). Соответственно функции от значений ничем не отличаются.
С другой стороны, если мы заинтересованы не в самом отображении, а в результате отображения, то само отображение не катит. Другими
словами, для получения результата отображения нам необходимы аргументы. Получается функция - это значение, параметризованное аргументом,
как бы странно это не звучало. В общем, к чему я клоню... а клоню я к тому, что у нас есть просто типы данных, а есть параметризованные
типы данных. Параметризованный тип данных - это как функция. Только функция отображает значения в другие значения, а параметризованные
типы отображают одни типы в другие. Например, List[T] - это тип List (зовётся конструктором типа), параметризованный типом T.
Отображает множество типов T (таких как Int, String, ...) в множество типов List[T] (таких как List[Int], List[String], ...).
Как известно, для функции мы умеем сужать возможную область аргументов (используем типы аргументов), а также мы умеем сужать
отображаемую область (результат). Для параметризованных типов все тоже самое. Мы можем наложить ограничение на тип T, или даже на результат
аппликации T к List. Запись между функцией и параметризованным типом похожа (напишу я тут не слишком канонично, по канонам оно будет дальше):
<pre>
<span style="color: #13ff12;">f:</span> <span style="color: #084EA8;">Int => String</span></pre>
-- это функция f с аргументом Int и результатом String (проецирует числа в строки)
<pre>
<span style="color: #13ff12;">List:</span> <span style="color: #084EA8;">T => List[T]</span></pre>
-- это конструктор типа List проецирующий множество типов T в множество типов List[T].
</span><BR/>
Получается что-то такое:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>[<span style="color: #98fb98;">R</span>](<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>[<span style="color: #98fb98;">R</span>](<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[R]</span> = Set.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>[<span style="color: #98fb98;">R</span>](<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[R]</span> = List.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>[<span style="color: #98fb98;">R</span>](<span style="color: #13ff12;">artistMap:</span> <span style="color: #084EA8;">Map[R, Artist[R]]</span> = Map.empty,
<span style="color: #13ff12;">songMap:</span> <span style="color: #084EA8;">Map[R, Song[R]]</span> = Map.empty,
<span style="color: #13ff12;">compilationMap:</span> <span style="color: #084EA8;">Map[R, Compilation[R]]</span> = Map.empty) {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withArtist</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(R, Artist[R])</span>) = <span style="color: #00ffff;">this</span>.<span style="color: #24BEE3;">copy</span>(artistMap = <span style="color: #13ff12;">artistMap</span> + <span style="color: #ffff70;">assoc</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withSong</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(R, Song[R])</span>) = <span style="color: #00ffff;">this</span>.copy(songMap = songMap + assoc)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withCompilation</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(R, Compilation[R])</span>) = <span style="color: #00ffff;">this</span>.copy(compilationMap = compilationMap + assoc)
}</pre>
<span style="color: #888;">
Как видно, я параметризовал все по R (первая буква слова Reference - ссылка). Теперь там, где был
тип Int, у нас какой-то тип R. С этого момента мы не можем говорить Artist, говоря о конкретном типе данных. Теперь
мы обязаны говорить Artist[T], когда говорим про конкретный тип. Ибо Artist теперь является конструктором
типа и чтобы стать типом (с нулевым конструктором), ему нужен аргумент (так же как функции, чтобы стать значением,
нужен аргумент).
К примеру, Map[K, V] (где K - Key, ключ, а V - Value, значение) это тип, а Map - конструктор типа.
Следует отметить, что K и V - это какие-то типы. А раз типы, то мы не можем написать Map[R, Artist], ибо
Artist - конструктор типа, а не тип! Поэтому мы пишем Map[R, Artist[R]]. Впрочем, это все пока
ясно на уровне интуиции, но лучше понять это глубже, чем интуиция, а то...
</span><BR/>
Вобщем-то параметризация по типу решает проблему номер (3) из нашего списка. Но как быть, если мы хотим
параметризовать тип ссылки, чтобы ссылка на исполнителя являлась несовместимой по типу со ссылкой на
песню. Очевидно, что нет другого выхода, кроме как параметризовать саму ссылку по типу, на
который ссылка ссылается.<BR/>
<span style="color: #888;">
Итого, функция без аргументов вообще (с нулевым количеством аргументов) является значением, константой.
Функции, которые принимают другие функции в качестве аргументов или возвращают функции как результат,
называются функциями высшего порядка. С типами все тоже самое. Есть просто типы (с нулевым конструктором),
а есть типы параметризованные другим типом (т.е. являющиеся конструктором типа).
Аналогично, конструктор типа, который использует в качестве параметра другой конструктор типа, является типом высшего порядка.
Так, например, функция, которая возвращает Nothing, а принимает в качестве аргумента функцию, которая принимает
в качестве аргумента Int, а возвращает String, запишется вот так:
<pre>
<span style="color: #13ff12;">f:</span> <span style="color: #084EA8;">(Int => String) => Nothing</span></pre>
Соответственно, если есть функция g и значение v:
<pre>
<span style="color: #13ff12;">g:</span> <span style="color: #084EA8;">Int => String</span>
<span style="color: #13ff12;">v:</span> <span style="color: #98fb98;">Int</span></pre>
то мы можем писать, что
<pre>
<span style="color: #13ff12;">f(g):</span> <span style="color: #98fb98;">Nothing</span>
<span style="color: #13ff12;">g(v):</span> <span style="color: #98fb98;">String</span></pre>
То совершенно аналогично и с типами. К примеру в выражении
<pre>
<span style="color: #13ff12;">MyList:</span> <span style="color: #084EA8;">(R => Container[R)) => List[Container[T]]</span></pre>
мы описали конструктор типа List, который в качестве аргумента принимает другой конструктор типа. Причём мы описали форму того
конструктора, который принимает наш конструктор List. А именно мы говорим, что форма у нас такая: R => Container[R], где R - это тип,
а Container - собственно какой-то конструктор типа. Ещё один способ это осознать, это записать все вот так (где звёздочка
у нас олицетворяет конкретный тип, а (* => *) это конструктор типа):
<pre>
<span style="color: #13ff12;">MyList:</span> <span style="color: #084EA8;">(* => *) => *</span></pre>
Это очень важно понять и уметь отличать где тип, а где конструктор типа, иначе будут ошибки компиляции при попытке использовать
конструктор типа там, где ожидается тип, и наоборот.
</span><BR/>
Получается вот такое:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[R[Artist[R]]]</span> = Set.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[R[Song[R]]]</span> = List.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">artistMap:</span> <span style="color: #084EA8;">Map[R[Artist[R]], Artist[R]]</span> = Map.empty,
<span style="color: #13ff12;">songMap:</span> <span style="color: #084EA8;">Map[R[Song[R]], Song[R]]</span> = Map.empty,
<span style="color: #13ff12;">compilationMap:</span> <span style="color: #084EA8;">Map[R[Compilation[R]], Compilation[R]]</span> = Map.empty) {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withArtist</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(R[Artist[R]], Artist[R])</span>) = <span style="color: #00ffff;">this</span>.<span style="color: #24BEE3;">copy</span>(artistMap = <span style="color: #13ff12;">artistMap</span> + <span style="color: #ffff70;">assoc</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withSong</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(R[Song[R]], Song[R])</span>) = <span style="color: #00ffff;">this</span>.copy(songMap = songMap + assoc)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withCompilation</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">(R[Compilation[R]], Compilation[R])</span>) = <span style="color: #00ffff;">this</span>.copy(compilationMap = compilationMap + assoc)
}</pre>
Красиво? Да, только в глазах рябит от R. Дальше упростим ;-)<BR/>
<span style="color: #888;">
На самом деле параметризация Artist вообще здесь чисто для красоты, так как R в определении класса Artist не используется.
Но чтобы все было унифицировано, пусть будет. Посмотрим на класс Song. Сразу после имени класса идёт перечисление
параметров типа. Если раньше единственным параметром типа был параметр R, то теперь параметр этот поменял свою форму
и теперь он выглядит как R[_], что говорит нам о том, что теперь он не тип, а конструктор типа! Теперь посмотрим на
вот эту запись Set[R[Artist[R]]]. Почему так? Потому что класс Set параметризован типом элементов, которые лежат в
этом контейнере. В нашем случае в контейнере лежат ссылки на исполнителя. Ссылка у нас R. Но просто R написать мы не можем,
так как, во-первых, у нас конкретная ссылка - ссылка на исполнителя, а, во-вторых, R - это конструктор типа, а Set параметризована
типом, а не конструктором. Соответственно пишем Set[R[Artist... а дальше? Все? Нет. R параметризована типом, а Artist у нас не тип
а конструктор типа. Поэтому, чтобы удовлетворить R, надо указать тип. А как из Artist сделать тип? Подставить параметр в
конструктор типа, параметром для Artist является ссылка, т.е. мы пишем Set[R[Artist[R.. Все? Смотрим: конструктор типа Artist
параметризован конструктором типа и в тоже время R является конструктором типа, совпало! Можно закрывать скобочки.
</span><BR/>
Упрощаем:
<pre>
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">RefMap</span>[R[_], T[_[_]]] = <span style="color: #084EA8;">Map[R[T[R]], T[R]]</span>
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">Assoc</span>[R[_], T[_[_]]] = <span style="color: #084EA8;">(R[T[R]], T[R])</span>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>, <span style="color: #13ff12;">artists:</span> <span style="color: #98fb98;">Set[R[Artist[R]]]</span> = Set.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>, <span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[R[Song[R]]]</span> = List.empty, <span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">artistMap:</span> <span style="color: #084EA8;">RefMap[R, Artist]</span> = Map.empty,
<span style="color: #13ff12;">songMap:</span> <span style="color: #084EA8;">RefMap[R, Song]</span> = Map.empty,
<span style="color: #13ff12;">compilationMap:</span> <span style="color: #084EA8;">RefMap[R, Compilation]</span> = Map.empty) {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withArtist</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">Assoc[R, Artist]</span>) = <span style="color: #00ffff;">this</span>.<span style="color: #24BEE3;">copy</span>(artistMap = <span style="color: #13ff12;">artistMap</span> + <span style="color: #ffff70;">assoc</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withSong</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">Assoc[R, Song]</span>) = <span style="color: #00ffff;">this</span>.copy(songMap = songMap + assoc)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withCompilation</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">Assoc[R, Compilation]</span>) = <span style="color: #00ffff;">this</span>.copy(compilationMap = compilationMap + assoc)
}</pre>
<span style="color: #888;">
Ключевое слово type, очевидно, используется для присвоения какому-то типу имени. Так я присвоил имя RefMap[...]
типу Map[R[T[R]], T[R]]. Мне кажется, не нужно быть гением, чтобы прочитать первую строчку нового определения как
"определяем тип RefMap, параметризованный R (конструктором типов) и T (конструктором конструкторов типа), эквивалентный
типу Map, в котором типом ключа является R[T[R]], а типом значения является T[R]". Аналогично определяем тип Assoc[..].
Это позволяет нам упростить тип Task[_[_]] и сделать его понятным даже тем, кто считает параметризованные типы
аналогом шаблонов C++!
</span><BR/>
Попробуем использовать:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">IntRef</span>[<span style="color: #98fb98;">T</span>](<span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Int</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntroll </span> = <span style="color: #026DF7;">Artist</span>[<span style="color: #98fb98;">IntRef</span>](name = <span style="color: #ffa07a;">"Finntroll"</span>, website = <span style="color: #ffa07a;">"www.finntroll.net"</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntrollRef </span> = <span style="color: #026DF7;">IntRef</span>[<span style="color: #98fb98;">Artist[IntRef]</span>](1)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">svampfest </span> = <span style="color: #026DF7;">Song</span>(title = <span style="color: #ffa07a;">"Svampfest"</span>, artists = Set(finntrollRef), year = 0)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">svampfestRef </span> = <span style="color: #026DF7;">IntRef</span>[<span style="color: #98fb98;">Song[IntRef]</span>](1)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">skog </span> = <span style="color: #026DF7;">Song</span>(title = <span style="color: #ffa07a;">"Skog"</span>, artists = Set(finntrollRef), year = 0)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">skogRef </span> = <span style="color: #026DF7;">IntRef</span>[<span style="color: #98fb98;">Song[IntRef]</span>](2)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task </span> = <span style="color: #026DF7;">Task</span>(artistMap = <span style="color: #13ff12;">Map</span>(<span style="color: #13ff12;">finntrollRef</span> -> <span style="color: #13ff12;">finntroll</span>),
songMap = <span style="color: #13ff12;">Map</span>(<span style="color: #13ff12;">svampfestRef</span> -> <span style="color: #13ff12;">svampfest</span>),
compilationMap = <span style="color: #13ff12;">Map</span>[<span style="color: #98fb98;">IntRef[Compilation[IntRef]]</span>, <span style="color: #98fb98;">Compilation[IntRef]</span>]())
<span style="color: #24BEE3;"> </span> .withSong(<span style="color: #13ff12;">skogRef</span> -> <span style="color: #13ff12;">skog</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntroll2 </span>= finntroll.copy[IntRef](website = <span style="color: #ffa07a;">"http://www.finntroll.net"</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task2 </span>= <span style="color: #13ff12;">task</span>.<span style="color: #24BEE3;">withArtist</span>(<span style="color: #13ff12;">finntrollRef</span> -> <span style="color: #13ff12;">finntroll2</span>)
<span style="color: #24BEE3;">println</span>(<span style="color: #13ff12;">task</span>)
<span style="color: #24BEE3;">println</span>(<span style="color: #13ff12;">task2</span>)</pre>
Выдаст (я поставлю переносы строк где надо, а то оно без переносов и отступов выдает):
<pre>
Task(Map(IntRef(1) -> Artist(Finntroll,www.finntroll.net,None))
Map(IntRef(1) -> Song(Svampfest,0,Set(IntRef(1)),None),
IntRef(2) -> Song(Skog,0,Set(IntRef(1)),None))
Map())
Task(Map(IntRef(1) -> Artist(Finntroll,http://www.finntroll.net,None))
Map(IntRef(1) -> Song(Svampfest,0,Set(IntRef(1)),None),
IntRef(2) -> Song(Skog,0,Set(IntRef(1)),None)),
Map())</pre>
Неплохо. Работает. Но может быть лучше. В частности меня не устраивает,
что ссылки на объект находятся отдельно от объекта. В итоге, имея на руках Song,
сказать, как на него ссылаться, нельзя. Попробуем сделать лучше. Давайте объявим
трэйт с названием UsingRef (ИспользующийСсылку):
<pre>
<span style="color: #00ffff;">trait</span> <span style="color: #98fb98;">UsingRef</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>], <span style="color: #98fb98;">T</span>[<span style="color: #98fb98;">_</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]]</span> {
<span style="color: #13ff12;">self</span>: <span style="color: #98fb98;">T[R]</span> =>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">ref</span> : <span style="color: #026DF7;">R[T[R]]</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withRef</span> : <span style="color: #98fb98;">(R[T[R]], T[R])</span> = (<span style="color: #00ffff;">this</span>.<span style="color: #24BEE3;">ref</span>, <span style="color: #00ffff;">this</span>)
}</pre>
<span style="color: #888;">
Если коротко, то трэйт является mix-in'ом или чем-то похожим на интерфейс в джава в том смысле,
что, в отличии от класса, мы можем унаследовать несколько трэйтов, но в тоже время в отличии от
интерфейса джавы трэйт может иметь реализации методов и много чего ещё (при этом не имея
проблемы множественного наследования). Трэйт UsingRef параметризован ссылкой R[_] и типом, на
который эта ссылка ссылается T[_[_]]. В то время, как R является конструктором типа, а T является
конструктором конструкторов типа, T[R] является вполне себе каким-то конкретным типом! А если
конкретно, то типом, в качестве ссылки в котором используется конструктор типа R. Если это кажется сложным, то
запишите всё через "звёздочки" и просветление не заставит себя ждать. Следует также отметить, что запись вида:
<pre>
<span style="color: #13ff12;">self</span>: <span style="color: #98fb98;">T[R]</span> =></pre>
накладывает ограничение на то, где данный трэйт может быть использован. А именно мы говорим, что
накладывать его можно только на типы, которые являются (под)типами T[R]. Что нам такое ограничение дает?
Много что. Но конкретно в этом случае наш this внутри методов трэйта становится типом T[R]. Это вполне
логично, т.к. мы сказали, что использовать наш трэйт можно только вместе с типом T[R]. Смотрим дальше.
Дальше мы объявляем абстрактный метод БЕЗ АРГУМЕНТОВ (то есть вообще, даже без пустых скобочек).
Это значит, что внешне метод неотличим от значения. Это дает нам одно преимущество: при написании
конкретной реализации такого абстрактного метода мы можем реализовать её как: метод (def), значение (val),
переменная (var) или ленивое значение (lazy val), которое будет вычислено при первом обращении к нему.
В следующей строке наш трэйт определяет конкретный метод withRef, возвращающий кортеж (пару)
из ссылки на себя (используя абстрактный метод ref) и собственно себя.
</span><BR/>
Смысл этого трэйта в том, чтобы показать, что наш объект имеет ссылку ref и имеет удобный метод withRef, возвращающий
кортеж из ссылки и указателя на себя, что идеально подходит для добавления объекта в Map. Неплохо бы определить ещё
один трэйт:
<pre>
<span style="color: #00ffff;">trait</span> <span style="color: #98fb98;">RefBuilder</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span>: <span style="color: #98fb98;">Artist[R]</span>): <span style="color: #026DF7;">R[Artist[R]]</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">compilation</span>: <span style="color: #98fb98;">Compilation[R]</span>) : <span style="color: #026DF7;">R[Compilation[R]]</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span>: <span style="color: #98fb98;">Song[R]</span>): <span style="color: #026DF7;">R[Song[R]]</span>
}</pre>
Трэйт RefBuilder определяет интерфейс всех классов, которые умеют возвращать ссылки для объектов. Идея та же, что и у
type class'ов в Haskell, т.е. мы собираемся ввести категорию ссылок R[_], для которых определён билдер. Трэйт
параметризован типом ссылки, которую данный билдер умеет возвращать. Сразу же определим архиудобный билдер IdRefBuilder.
Удобный он тем, что связывает объекты по их айди из базы данных и нужен для случаев, когда объекты загружены из БД
и мы точно знаем, что айди там есть. Итак:
<pre>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">object</span> <span style="color: #eedd82;">idRefBuilder</span> <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">RefBuilder[IntRef]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span>: <span style="color: #98fb98;">Artist[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">artist</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">compilation</span>: <span style="color: #98fb98;">Compilation[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">compilation</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span>: <span style="color: #98fb98;">Song[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">song</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
}</pre>
<span style="color: #888;">
Да, в scala можно сразу же создавать объект (синглетон). Такая запись эквивалентна:
<pre>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">lazy</span> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">idRefBuilder</span> = <span style="color: #00ffff;">new</span> <span style="color: #084EA8;">RefBuilder[IntRef]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span>: <span style="color: #98fb98;">Artist[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">artist</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">compilation</span>: <span style="color: #98fb98;">Compilation[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">compilation</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span>: <span style="color: #98fb98;">Song[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">song</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
}</pre>
... за исключением того, что объекты могут являться жителями пакета (package), в то время как значения
жителями пакета быть не могут.
</span><BR/>
Определим ещё два билдера. Первый билдер будет создавать уникальные ссылки для каждого объекта.
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">UuidRef</span>[<span style="color: #98fb98;">T</span>](<span style="color: #13ff12;">id </span>: <span style="color: #98fb98;">String </span>= java.util.UUID.randomUUID.toString)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">object</span> <span style="color: #eedd82;">UuidRefBuilder</span> <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">RefBuilder[UuidRef]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span> : <span style="color: #98fb98;">Artist[UuidRef]</span>) = <span style="color: #026DF7;">UuidRef</span>()
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">compilation</span> : <span style="color: #98fb98;">Compilation[UuidRef]</span>) = <span style="color: #026DF7;">UuidRef</span>()
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span> : <span style="color: #98fb98;">Song[UuidRef]</span>) = <span style="color: #026DF7;">UuidRef</span>()
}</pre>
А второй билдер будет использовать сами объекты в качестве ссылок o_O
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">ValueRef</span>[<span style="color: #98fb98;">T</span>](<span style="color: #13ff12;">value </span>: <span style="color: #026DF7;">T</span>)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">object</span> <span style="color: #eedd82;">ValueRefBuilder</span> <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">RefBuilder[ValueRef]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span> : <span style="color: #98fb98;">Artist[ValueRef]</span>) = <span style="color: #026DF7;">ValueRef</span>(<span style="color: #ffff70;">artist</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">comp</span> : <span style="color: #98fb98;">Compilation[ValueRef]</span>) = <span style="color: #026DF7;">ValueRef</span>(<span style="color: #ffff70;">comp</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span> : <span style="color: #98fb98;">Song[ValueRef]</span>) = <span style="color: #026DF7;">ValueRef</span>(<span style="color: #ffff70;">song</span>)
}</pre>
Итак, у нас есть все, чтобы улучшить Artist:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">name</span>: <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">website</span>: <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">id</span>: <span style="color: #98fb98;">Option[Int]</span> = None)(
<span style="color: #00ffff;">implicit</span> <span style="color: #13ff12;">rb</span>: <span style="color: #084EA8;">RefBuilder[R]</span>) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">UsingRef[R, Artist]</span> {
<span style="color: #00ffff;">lazy</span> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">ref</span> = <span style="color: #24BEE3;">rb</span> <span style="color: #24BEE3;">makeArtistRef</span> <span style="color: #00ffff;">this</span>
}</pre>
... Song:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">title</span>: <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">year</span>: <span style="color: #98fb98;">Int</span>,
<span style="color: #13ff12;">artists</span>: <span style="color: #084EA8;">Set[R[Artist[R]]]</span> = Set.empty,
<span style="color: #13ff12;">id</span>: <span style="color: #98fb98;">Option[Int]</span> = None)(
<span style="color: #00ffff;">implicit</span> <span style="color: #13ff12;">rb</span>: <span style="color: #084EA8;">RefBuilder[R]</span>) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">UsingRef[R, Song]</span> {
<span style="color: #00ffff;">lazy</span> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">ref</span> = <span style="color: #24BEE3;">rb</span> <span style="color: #24BEE3;">makeSongRef</span> <span style="color: #00ffff;">this</span>
}</pre>
... Compilation:
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">title</span>: <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">songs</span>: <span style="color: #98fb98;">List[R[Song[R]]]</span> = List.empty,
<span style="color: #13ff12;">id</span>: <span style="color: #98fb98;">Option[Int]</span> = None)(
<span style="color: #00ffff;">implicit</span> <span style="color: #13ff12;">rb</span>: <span style="color: #084EA8;">RefBuilder[R]</span>) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">UsingRef[R, Compilation]</span> {
<span style="color: #00ffff;">lazy</span> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">ref</span> = <span style="color: #24BEE3;">rb</span> <span style="color: #24BEE3;">makeCompilationRef</span> <span style="color: #00ffff;">this</span>
}</pre>
<span style="color: #888;">
Если вы читаете это не по диагонали, то наверное заметили, что во все классы добавилась
новая группа аргументов. Да, в Scala групп аргументов может не быть, может быть одна
(как это обычно бывает), а может быть более, чем одна. Это дает громадное количество
новых возможностей. Перенос аргументов (curring), блоки... В нашем случае, новая
группа везде помечена модификатором implicit (неявный). Тем самым мы говорим компилятору,
что возможно мы хотим опустить эти аргументы полностью и в таком случае просим компилятор
найти нам такой объект в области видимости вызова (то есть в области видимости того места,
где мы создаем экземпляры класса Song, Compilation, Artist, а не там, где они объявлены).
Если в области видимости присутствует значение (или метод, или объект), который имеет
нужный тип и также помечен как implicit, то scala будет использовать его в качестве
отсутствующего аргумента. Если такого объекта нет или есть, но больше, чем один, то компилятор
ругнется и не даст скомпилировать такую программу. Ещё одним важным моментом является то,
что ref имеет модификатор lazy. Это позволяет нам: пользоваться объектом, копировать методом
copy, модифицировать и т.д. При этом до тех пор, пока мы не обратимся к методу ref, выражение для
ref не будет вычислено (т.е. объект вероятно умрёт, а обращение к ref не произойдет совсем).
Это очень важный момент. Это дает нам экономию ресурсов процессора и
позволяет писать достаточно интересные билдеры ссылок.
</span><BR/>
Сделаем ещё одну удобную штуку. Сделаем так, что на основе любой последовательности объектов,
имеющих трэйт UsingRef, можно было легко создать Map из-ключей-этих-объектов-в-сами-объекты:
<pre>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">RichUsingRefIterable</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>], <span style="color: #98fb98;">T</span>[<span style="color: #98fb98;">_</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]]</span>(<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">iterable</span> : <span style="color: #084EA8;">Iterable[UsingRef[R, T]]</span>) <span style="color: #00ffff;">extends</span> <span style="color: #98fb98;">AnyVal</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">toRefMap</span>: <span style="color: #084EA8;">RefMap[R, T]</span> =
<span style="color: #13ff12;">Map</span>(<span style="color: #13ff12;">iterable</span>.<span style="color: #24BEE3;">map</span>(<span style="color: #ffff70;">_</span>.<span style="color: #24BEE3;">withRef</span>).<span style="color: #24BEE3;">toSeq</span> : _*)
}</pre>
<span style="color: #888;">
Я не буду вдаваться во все тонкости value class'ов и т.д., но предположим у нас есть
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">list </span>= List(<span style="color: #026DF7;">song1</span>, <span style="color: #13ff12;">song2</span>, <span style="color: #13ff12;">song3</span>)</pre>
если я попробую вызвать list.toRefMap, то казалось бы ничего не должно получиться. У класса List нет метода
toRefMap. Но все не так просто. Обломавшись с классом List, Scala поищет в текущей области видимости
метод, помеченный как implicit (неявный) и умеющий из нашего List сделать другой объект, в котором будет
метод toRefMap! В нашем примере таким методом является конструктор класса RichUsingRefIterable, который
создаст одноименный класс. Получается, что встретив list.toRefMap, Scala скомпилирует вместо этого код
RichUsingRefIterable(list).toRefMap (без тонкостей value class'ов). Такая возможность позволяет нам расширять существующие типы данных.
Запись вида (_.withRef) - это короткий вариант (x => x.withRef), что является анонимной функцией, получающей
в качестве аргумента x и возвращающей в качестве результата x.withRef (кортеж: ключ, объект). Соответственно
iterable.map(_.withRef) вернет последовательность кортежей (ключ, объект). Вызов toSeq сделает из
неизвестной последовательности (просто Iterable) последовательность типа Seq. С помощью : _* мы можем
применить последовательность в качестве аргументов для конструктора Map (то есть передать не
последовательность как один аргумент, а элементы последовательности в качестве аргументов... это если не вдаваться
в то, как это реализовано). Как результат у нас будет Map из ключа в объект.
</span><BR/>
И, наконец, улучшения добрались и до класса Task. Давайте добавим туда методы artists, songs, compilations, которые
будут просто удобным способом получить коллекции объектов из соответствий (Map), лежащих в классе Task. Так же создадим
очень удобный метод using, который будет делать из класса Task, параметризованного одним типом ссылок, другую Task
параметризованную другим типом ссылок! В результате получаем вот такой код:
<pre>
<span style="color: #00ffff;">trait</span> <span style="color: #98fb98;">RefBuilder</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span>: <span style="color: #98fb98;">Artist[R]</span>): <span style="color: #026DF7;">R[Artist[R]]</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">compilation</span>: <span style="color: #98fb98;">Compilation[R]</span>): <span style="color: #026DF7;">R[Compilation[R]]</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span>: <span style="color: #98fb98;">Song[R]</span>): <span style="color: #026DF7;">R[Song[R]]</span>
}
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">IntRef</span>[<span style="color: #98fb98;">T</span>](<span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Int</span>)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">object</span> <span style="color: #eedd82;">IdRefBuilder</span> <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">RefBuilder[IntRef]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span>: <span style="color: #98fb98;">Artist[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">artist</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">compilation</span>: <span style="color: #98fb98;">Compilation[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">compilation</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span>: <span style="color: #98fb98;">Song[IntRef]</span>) = <span style="color: #026DF7;">IntRef</span>(<span style="color: #ffff70;">song</span>.<span style="color: #13ff12;">id</span>.<span style="color: #24BEE3;">get</span>)
}
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">UuidRef</span>[<span style="color: #98fb98;">T</span>](<span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">String </span>= java.util.UUID.randomUUID.toString)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">object</span> <span style="color: #eedd82;">UuidRefBuilder</span> <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">RefBuilder[UuidRef]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span>: <span style="color: #98fb98;">Artist[UuidRef]</span>) = <span style="color: #026DF7;">UuidRef</span>()
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">compilation</span>: <span style="color: #98fb98;">Compilation[UuidRef]</span>) = <span style="color: #026DF7;">UuidRef</span>()
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span>: <span style="color: #98fb98;">Song[UuidRef]</span>) = <span style="color: #026DF7;">UuidRef</span>()
}
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">ValueRef</span>[<span style="color: #98fb98;">T</span>](<span style="color: #13ff12;">value:</span> <span style="color: #026DF7;">T</span>)
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">object</span> <span style="color: #eedd82;">ValueRefBuilder</span> <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">RefBuilder[ValueRef]</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeArtistRef</span>(<span style="color: #ffff70;">artist</span>: <span style="color: #98fb98;">Artist[ValueRef]</span>) = <span style="color: #026DF7;">ValueRef</span>(<span style="color: #ffff70;">artist</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeCompilationRef</span>(<span style="color: #ffff70;">comp</span>: <span style="color: #98fb98;">Compilation[ValueRef]</span>) = <span style="color: #026DF7;">ValueRef</span>(<span style="color: #ffff70;">comp</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">makeSongRef</span>(<span style="color: #ffff70;">song</span>: <span style="color: #98fb98;">Song[ValueRef]</span>) = <span style="color: #026DF7;">ValueRef</span>(<span style="color: #ffff70;">song</span>)
}
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">RefMap</span>[R[_], T[_[_]]] = <span style="color: #084EA8;">Map[R[T[R]], T[R]]</span>
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">Assoc</span>[R[_], T[_[_]]] = <span style="color: #98fb98;">(R[T[R]], T[R])</span>
<span style="color: #00ffff;">implicit</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">RichUsingRefIterable</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>], <span style="color: #98fb98;">T</span>[<span style="color: #98fb98;">_</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]]</span>(<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">iterable:</span> <span style="color: #084EA8;">Iterable[UsingRef[R, T]]</span>) <span style="color: #00ffff;">extends</span> <span style="color: #98fb98;">AnyVal</span> {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">toRefMap</span>: <span style="color: #084EA8;">RefMap[R, T]</span> =
<span style="color: #13ff12;">Map</span>(<span style="color: #13ff12;">iterable</span>.<span style="color: #24BEE3;">map</span>(<span style="color: #ffff70;">_</span>.<span style="color: #24BEE3;">withRef</span>).<span style="color: #24BEE3;">toSeq</span>: _*)
}
<span style="color: #00ffff;">trait</span> <span style="color: #98fb98;">UsingRef</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>], <span style="color: #98fb98;">T</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]]</span> {
<span style="color: #13ff12;">self</span>: <span style="color: #98fb98;">T[R]</span> =>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">ref</span>: <span style="color: #026DF7;">R[T[R]]</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withRef</span>: <span style="color: #98fb98;">(R[T[R]], T[R])</span> = (<span style="color: #00ffff;">this</span>.<span style="color: #24BEE3;">ref</span>, <span style="color: #00ffff;">this</span>)
}
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Artist</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">name:</span> <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">website:</span> <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)(
<span style="color: #00ffff;">implicit</span> <span style="color: #13ff12;">rb</span>: <span style="color: #084EA8;">RefBuilder[R]</span>) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">UsingRef[R, Artist]</span> {
<span style="color: #00ffff;">lazy</span> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">ref</span> = <span style="color: #24BEE3;">rb</span> <span style="color: #24BEE3;">makeArtistRef</span> <span style="color: #00ffff;">this</span>
}
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Song</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">year:</span> <span style="color: #98fb98;">Int</span>,
<span style="color: #13ff12;">artists:</span> <span style="color: #084EA8;">Set[R[Artist[R]]]</span> = Set.empty,
<span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)(
<span style="color: #00ffff;">implicit</span> <span style="color: #13ff12;">rb</span>: <span style="color: #084EA8;">RefBuilder[R]</span>) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">UsingRef[R, Song]</span> {
<span style="color: #00ffff;">lazy</span> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">ref</span> = <span style="color: #24BEE3;">rb</span> <span style="color: #24BEE3;">makeSongRef</span> <span style="color: #00ffff;">this</span>
}
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Compilation</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">title:</span> <span style="color: #98fb98;">String</span>,
<span style="color: #13ff12;">songs:</span> <span style="color: #98fb98;">List[R[Song[R]]]</span> = List.empty,
<span style="color: #13ff12;">id:</span> <span style="color: #98fb98;">Option[Int]</span> = None)(
<span style="color: #00ffff;">implicit</span> <span style="color: #13ff12;">rb</span>: <span style="color: #084EA8;">RefBuilder[R]</span>) <span style="color: #00ffff;">extends</span> <span style="color: #084EA8;">UsingRef[R, Compilation]</span> {
<span style="color: #00ffff;">lazy</span> <span style="color: #00ffff;">val</span> <span style="color: #13ff12;">ref</span> = <span style="color: #24BEE3;">rb</span> <span style="color: #24BEE3;">makeCompilationRef</span> <span style="color: #00ffff;">this</span>
}
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Task</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #13ff12;">artistMap:</span> <span style="color: #084EA8;">RefMap[R, Artist]</span>,
<span style="color: #13ff12;">songMap:</span> <span style="color: #084EA8;">RefMap[R, Song]</span>,
<span style="color: #13ff12;">compilationMap:</span> <span style="color: #084EA8;">RefMap[R, Compilation]</span>) {
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withArtist</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">Assoc[R, Artist]</span>) = <span style="color: #00ffff;">this</span>.<span style="color: #24BEE3;">copy</span>(artistMap = <span style="color: #13ff12;">artistMap</span> + <span style="color: #ffff70;">assoc</span>)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withSong</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">Assoc[R, Song]</span>) = <span style="color: #00ffff;">this</span>.copy(songMap = songMap + assoc)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">withCompilation</span>(<span style="color: #ffff70;">assoc</span>: <span style="color: #98fb98;">Assoc[R, Compilation]</span>) = <span style="color: #00ffff;">this</span>.copy(compilationMap = compilationMap + assoc)
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">artists</span>: <span style="color: #084EA8;">Iterable[Artist[R]]</span> = <span style="color: #13ff12;">artistMap</span>.<span style="color: #24BEE3;">values</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">songs</span>: <span style="color: #084EA8;">Iterable[Song[R]]</span> = <span style="color: #13ff12;">songMap</span>.<span style="color: #24BEE3;">values</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">compilations</span>: <span style="color: #084EA8;">Iterable[Compilation[R]]</span> = <span style="color: #13ff12;">compilationMap</span>.<span style="color: #24BEE3;">values</span>
<span style="color: #00ffff;">def</span> <span style="color: #87cefa;">using</span>[<span style="color: #98fb98;">NR</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]</span>(<span style="color: #00ffff;">implicit</span> <span style="color: #ffff70;">refBuilder</span>: <span style="color: #084EA8;">RefBuilder[NR]</span>): <span style="color: #98fb98;">Task[NR]</span> = {
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">newArtistByOldKey</span> = <span style="color: #13ff12;">artistMap</span>.<span style="color: #24BEE3;">mapValues</span>(<span style="color: #ffff70;">_</span>.<span style="color: #24BEE3;">copy</span>[<span style="color: #026DF7;">NR</span>]()).<span style="color: #24BEE3;">iterator</span>.<span style="color: #24BEE3;">toMap</span>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">newSongByOldKey</span> =
<span style="color: #13ff12;">songMap</span>.<span style="color: #24BEE3;">mapValues</span> {
<span style="color: #ffff70;">song</span> =>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">newSongArtistRefs</span> = <span style="color: #ffff70;">song</span>.<span style="color: #13ff12;">artists</span> <span style="color: #24BEE3;">map</span> (<span style="color: #73ff73;">newArtistByOldKey</span>(<span style="color: #ffff70;">_</span>).<span style="color: #e06022;">ref</span>)
song.copy[NR](artists = newSongArtistRefs)
}.<span style="color: #24BEE3;">iterator</span>.<span style="color: #24BEE3;">toMap</span>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">newCompilationByOldKey</span> =
<span style="color: #13ff12;">compilationMap</span>.<span style="color: #24BEE3;">mapValues</span> {
<span style="color: #ffff70;">comp</span> =>
<span style="color: #00ffff;">val</span> <span style="color: #73ff73;">newCompilationSongRefs</span> = <span style="color: #ffff70;">comp</span>.<span style="color: #13ff12;">songs</span> <span style="color: #24BEE3;">map</span> (<span style="color: #73ff73;">newSongByOldKey</span>(<span style="color: #ffff70;">_</span>).<span style="color: #e06022;">ref</span>)
comp.copy[NR](songs = newCompilationSongRefs)
}.<span style="color: #24BEE3;">iterator</span>.<span style="color: #24BEE3;">toMap</span>
<span style="color: #026DF7;">Task</span>[<span style="color: #026DF7;">NR</span>](
artistMap = <span style="color: #73ff73;">newArtistByOldKey</span>.<span style="color: #24BEE3;">values</span>.<span style="color: #24BEE3;">toRefMap</span>,
songMap = <span style="color: #73ff73;">newSongByOldKey</span>.<span style="color: #24BEE3;">values</span>.<span style="color: #24BEE3;">toRefMap</span>,
compilationMap = <span style="color: #73ff73;">newCompilationByOldKey</span>.<span style="color: #24BEE3;">values</span>.<span style="color: #24BEE3;">toRefMap</span>
)
}
}</pre>
<span style="color: #888;">
Я думаю к этому моменту повествования добавлять больше деталей синтаксиса и семантики смысла нет. Единственное, что
стоит сказать... метод using получает билдер либо явно, либо не явно, а методы copy, используемые в методе using,
находят этот билдер неявным способом. Стоит обратить внимание на метод mapValues у Map, который применяет функцию
ко всем значениям соответствия. Подвох в том, что функция применяется не сразу, а при попытке получить
значение по ключу. Это значит, что если вы 5 раз получаете одно и то же значение из соответствия, то функция вычисляется
5 раз. Это не было бы проблемой, если бы язык был бы полностью ленивым! Это не было бы проблемой, если бы у нас
была обязательная монада IO для операций с побочным эффектом. У нас же ленивость опциональная, да и монада опциональная,
потому будет правильным принудительно из вьюшки поверх соответствия сделать нормальное, честное соответствие.
Для этого, как раз я и написал .iterator.toMap.
</span><BR/>
Попробуем в деле?
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntroll </span>= <span style="color: #026DF7;">Artist</span>[<span style="color: #98fb98;">IntRef</span>](name = <span style="color: #ffa07a;">"Finntroll"</span>, website = <span style="color: #ffa07a;">"www.finntroll.net"</span>, id = Some(1))
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">svampfest </span>= Song(title = <span style="color: #ffa07a;">"Svampfest"</span>, artists = Set(finntroll.ref), year = 0, id = Some(1))
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">skog </span>= Song(title = <span style="color: #ffa07a;">"Skog"</span>, artists = Set(finntroll.ref), year = 0, id = Some(2))
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task </span>= <span style="color: #026DF7;">Task</span>(artistMap = List(<span style="color: #13ff12;">finntroll</span>).<span style="color: #24BEE3;">toRefMap</span>,
songMap = List(<span style="color: #13ff12;">svampfest</span>, <span style="color: #13ff12;">skog</span>).<span style="color: #24BEE3;">toRefMap</span>,
compilationMap = <span style="color: #13ff12;">Map</span>(): <span style="color: #084EA8;">RefMap[IntRef, Compilation]</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntroll2 </span>= finntroll.copy[IntRef](website = <span style="color: #ffa07a;">"http://www.finntroll.net"</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task2 </span>= <span style="color: #13ff12;">task</span> <span style="color: #24BEE3;">withArtist</span> <span style="color: #13ff12;">finntroll2</span>.<span style="color: #24BEE3;">withRef</span></pre>
Лучше? Я считаю, что намного! А работает? Попробуем:
<pre>
<span style="color: #24BEE3;">println</span>(<span style="color: #13ff12;">task</span>)
<span style="color: #24BEE3;">println</span>(<span style="color: #13ff12;">task2</span>)
Task(Map(IntRef(1) -> Artist(Finntroll,www.finntroll.net,Some(1))),
Map(IntRef(1) -> Song(Svampfest,0,Set(IntRef(1)),Some(1)),
IntRef(2) -> Song(Skog,0,Set(IntRef(1)),Some(2))),
Map())
Task(Map(IntRef(1) -> Artist(Finntroll,http://www.finntroll.net,Some(1))),
Map(IntRef(1) -> Song(Svampfest,0,Set(IntRef(1)),Some(1)),
IntRef(2) -> Song(Skog,0,Set(IntRef(1)),Some(2))),
Map())</pre>
Попробуем ещё раз, но уже так:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntroll </span>= <span style="color: #026DF7;">Artist</span>[<span style="color: #98fb98;">UuidRef</span>](name = <span style="color: #ffa07a;">"Finntroll"</span>, website = <span style="color: #ffa07a;">"www.finntroll.net"</span>, id = Some(1))
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">svampfest </span>= Song(title = <span style="color: #ffa07a;">"Svampfest"</span>, artists = Set(finntroll.ref), year = 0, id = Some(1))
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">skog </span> = Song(title = <span style="color: #ffa07a;">"Skog"</span>, artists = Set(finntroll.ref), year = 0, id = Some(2))
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task </span> = <span style="color: #026DF7;">Task</span>(artistMap = List(<span style="color: #13ff12;">finntroll</span>).<span style="color: #24BEE3;">toRefMap</span>,
songMap = List(<span style="color: #13ff12;">svampfest</span>, <span style="color: #13ff12;">skog</span>).<span style="color: #24BEE3;">toRefMap</span>,
compilationMap = <span style="color: #13ff12;">Map</span>() : <span style="color: #084EA8;">RefMap[UuidRef, Compilation]</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task2 </span>= <span style="color: #13ff12;">task</span>.<span style="color: #24BEE3;">using</span>[<span style="color: #98fb98;">UuidRef</span>]
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task3 </span>= <span style="color: #13ff12;">task2</span>.<span style="color: #24BEE3;">using</span>[<span style="color: #98fb98;">IntRef</span>]
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task4 </span>= <span style="color: #13ff12;">task3</span>.<span style="color: #24BEE3;">using</span>[<span style="color: #98fb98;">ValueRef</span>]</pre>
println(task) выдаст (если отформатировать для красоты):
<pre>
Task(Map(UuidRef(32180846-89ed-4fe7-a9c1-11b4f4562a0a) -> Artist(Finntroll,www.finntroll.net,Some(1))),
Map(UuidRef(94331b40-b9bb-4e58-b840-32fbfa639048) -> Song(Svampfest,0,Set(UuidRef(32180846-89ed-4fe7-a9c1-11b4f4562a0a)),Some(1)),
UuidRef(0e717a3e-ea6b-437a-acd6-267f01110c98) -> Song(Skog,0,Set(UuidRef(32180846-89ed-4fe7-a9c1-11b4f4562a0a)),Some(2))),
Map())</pre>
println(task2) выдаст уже другие ключи. Это связано с тем, что UuidTaskBuilder генерирует ключи без использования объекта.
Используя побочные эффекты (случайные числа). Это плохо. Но проблема в том, что если ключ нельзя взять из объекта, если ключ
нельзя взять из воздуха (через побочный эффект), то откуда его вообще тогда брать?! Неоткуда. Смотрим на test2:
<pre>
Task(Map(UuidRef(166b78d7-53a3-44d7-a34b-af7c176d1a70) -> Artist(Finntroll,www.finntroll.net,Some(1))),
Map(UuidRef(e70a4bd9-3a29-47ec-93f2-885105c2973f) -> Song(Svampfest,0,Set(UuidRef(166b78d7-53a3-44d7-a34b-af7c176d1a70)),Some(1)),
UuidRef(78a353c7-1515-4d08-8c6d-4b995f03e2bc) -> Song(Skog,0,Set(UuidRef(166b78d7-53a3-44d7-a34b-af7c176d1a70)),Some(2))),
Map())</pre>
println(task3) выдаст уже знакомую нам картинку:
<pre>
Task(Map(IntRef(1) -> Artist(Finntroll,www.finntroll.net,Some(1))),
Map(IntRef(1) -> Song(Svampfest,0,Set(IntRef(1)),Some(1)),
IntRef(2) -> Song(Skog,0,Set(IntRef(1)),Some(2))),
Map())</pre>
println(task4) может пригодится в определённых ситуациях, хоть и выглядит странным:
<pre>
Task(Map(ValueRef(Artist(Finntroll,www.finntroll.net,Some(1))) -> Artist(Finntroll,www.finntroll.net,Some(1))),
Map(ValueRef(Song(Svampfest,0,Set(ValueRef(Artist(Finntroll,www.finntroll.net,Some(1)))),Some(1))) ->
Song(Svampfest,0,Set(ValueRef(Artist(Finntroll,www.finntroll.net,Some(1)))),Some(1)),
ValueRef(Song(Skog,0,Set(ValueRef(Artist(Finntroll,www.finntroll.net,Some(1)))),Some(2))) ->
Song(Skog,0,Set(ValueRef(Artist(Finntroll,www.finntroll.net,Some(1)))),Some(2))),
Map())</pre>
А что обновления изменения? Попробуем. Пускай наш task имеет тип Task[Uuid] и соответственно все остальные объекты
аналогично параметризованы:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntroll2 </span>= finntroll.copy[UuidRef](website = <span style="color: #ffa07a;">"http://www.finntroll.net"</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task2 </span>= <span style="color: #13ff12;">task</span> <span style="color: #24BEE3;">withArtist</span> <span style="color: #13ff12;">finntroll2</span>.<span style="color: #24BEE3;">withRef</span>
<span style="color: #24BEE3;">println</span> (<span style="color: #13ff12;">task</span>)
<span style="color: #24BEE3;">println</span> (<span style="color: #13ff12;">task2</span>)</pre>
Пробуем и видим:
<pre>
Task(Map(UuidRef(fb0b501d-90d9-4347-8cea-fbaf5bf70771) -> Artist(Finntroll,www.finntroll.net,Some(1))),
Map(UuidRef(9a029528-46e2-4178-bf1e-bf9e35969e26) -> Song(Svampfest,0,Set(UuidRef(fb0b501d-90d9-4347-8cea-fbaf5bf70771)),Some(1)),
UuidRef(4f7ec03f-6113-4f29-896f-357483af4f39) -> Song(Skog,0,Set(UuidRef(fb0b501d-90d9-4347-8cea-fbaf5bf70771)),Some(2))),
Map())
Task(Map(UuidRef(fb0b501d-90d9-4347-8cea-fbaf5bf70771) -> Artist(Finntroll,www.finntroll.net,Some(1)),
UuidRef(ee008f8c-dfdf-4aa0-a446-b95eb4644226) -> Artist(Finntroll,http://www.finntroll.net,Some(1))),
Map(UuidRef(9a029528-46e2-4178-bf1e-bf9e35969e26) -> Song(Svampfest,0,Set(UuidRef(fb0b501d-90d9-4347-8cea-fbaf5bf70771)),Some(1)),
UuidRef(4f7ec03f-6113-4f29-896f-357483af4f39) -> Song(Skog,0,Set(UuidRef(fb0b501d-90d9-4347-8cea-fbaf5bf70771)),Some(2))),
Map())</pre>
Что за бред? Дело в том, что finntroll.copy как и положено, создает новый объект, используя в конструкторе параметры из оригинального объекта
в качестве параметра по умолчанию. Но ref не является параметром конструктора. Неявный билдер тоже не относится к первой группе параметров.
Соответственно в новом объекте ref будет заново создана билдером. Если бы билдер был предсказуемой чистой функцией (без побочных эффектов),
то и результат был бы более предсказуемым. Не обязательно какой надо, ибо даже чистый билдер может возвращать ссылку, используя поменявшееся
значение поля объекта (например новый id в БД), но это было бы куда более ожидаемым. Похожая проблема имеется и у ValueRef
билдера. Мы, конечно, можем написать вот так:
<pre>
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">finntroll2 </span>= finntroll.copy[UuidRef](website = <span style="color: #ffa07a;">"http://www.finntroll.net"</span>)
<span style="color: #00ffff;">val</span> <span style="color: #13ff12;">task2 </span>= <span style="color: #13ff12;">task</span> <span style="color: #24BEE3;">withArtist</span> (<span style="color: #13ff12;">finntroll</span>.<span style="color: #e06022;">ref</span> -> <span style="color: #13ff12;">finntroll2</span>)</pre>
Но это лишь замаскирует проблему:
<pre>
Task(Map(UuidRef(220d2212-243f-4de1-9a61-909a21279d9b) -> Artist(Finntroll,www.finntroll.net,Some(1))),
Map(UuidRef(9d25c068-22b4-41f6-9168-7a53ad0255fa) -> Song(Svampfest,0,Set(UuidRef(220d2212-243f-4de1-9a61-909a21279d9b)),Some(1)),
UuidRef(15f00aee-1f64-4c03-8034-01a01b633832) -> Song(Skog,0,Set(UuidRef(220d2212-243f-4de1-9a61-909a21279d9b)),Some(2))),
Map())
Task(Map(UuidRef(220d2212-243f-4de1-9a61-909a21279d9b) -> Artist(Finntroll,http://www.finntroll.net,Some(1))),
Map(UuidRef(9d25c068-22b4-41f6-9168-7a53ad0255fa) -> Song(Svampfest,0,Set(UuidRef(220d2212-243f-4de1-9a61-909a21279d9b)),Some(1)),
UuidRef(15f00aee-1f64-4c03-8034-01a01b633832) -> Song(Skog,0,Set(UuidRef(220d2212-243f-4de1-9a61-909a21279d9b)),Some(2))),
Map())</pre>
Внешне все как бы хорошо. Но объект finntroll, известный в структуре под старым именем из старой ссылки,
будет называть себя совершенно другим именем... Конечно же, мы можем сделать все чуть более
правильно. Мы можем убрать UsingRef с объекта и вместо него использовать вот это, приукрасив это вкусными и удобными методами.
<pre>
<span style="color: #00ffff;">case</span> <span style="color: #00ffff;">class</span> <span style="color: #98fb98;">Identity</span>[<span style="color: #98fb98;">R</span>[<span style="color: #98fb98;">_</span>], <span style="color: #98fb98;">T</span>[<span style="color: #98fb98;">_</span>[<span style="color: #98fb98;">_</span>]<span style="color: #eedd82;">]]</span>(<span style="color: #13ff12;">ref </span>: <span style="color: #026DF7;">R[T[R]]</span>, <span style="color: #13ff12;">value </span>: <span style="color: #026DF7;">T[R]</span>)</pre>
Тем самым мы снова отделим наш доменный объект от ссылки, но при этом будем иметь возможность быстро и с минимум кода
манипулировать им вместе со ссылкой (Identity это логический аналог Tuple2 с той лишь разницей, что имеет чёткую
специализацию). Ещё одним вариантом может быть использование Tuple2 со специализацией, добавленной через value class'ы
и implicit методы. Но об это как-нибудь в другой раз, если вообще... Полученное решение весьма удобно, достаточно рабочее,
а количество текста и так уже перевалило за 1000 строк....
<BR/>
Подводя итог, я считаю, что универсального решения не получится, но это не значит, что его не стоит искать.
В каждом конкретном случае удобно по-разному. И хорошо, когда есть возможность легко и быстро выбирать ;-)<BR/><BR/>
Буду рад услышать ваше мнение об этой проблеме и подходах к её решению!<BR/>
<BR/>
P.S. Как же все-таки тяжело писать на русском языке. Я так и не понял, какой в русском аналог для proper types (собственные типы?),
nullary constructor и т.д...<BR/>
P.P.S. Ещё можно смотреть в сторону путезависимых типов. Вешать ковариантность, контравариантность на типы я не стал ради упрощения повествования.<BR/>
P.P.P.S. В работе, кстати, весьма пригождается вот такое:
<pre>
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">AnyTask</span> = <span style="color: #98fb98;">Task[R] forSome { type R[_] }</span>
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">AnyArtist</span> = <span style="color: #98fb98;">Artist[R] forSome { type R[_] }</span>
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">AnySong</span> = <span style="color: #98fb98;">Song[R] forSome { type R[_] }</span>
<span style="color: #00ffff;">type</span> <span style="color: #98fb98;">AnyCompilation</span> = <span style="color: #98fb98;">Compilation[R] forSome { type R[_] }</span>
</pre>
<!-- LocalWords: софта DRY Java Haskell Scala http otar-muhtarov ru
-->
<!-- LocalWords: Compilation Song name year artists Artist title id
-->
<!-- LocalWords: website songs case class String Int Set Option var
-->
<!-- LocalWords: List domain equals toString hashCode getter copy
-->
<!-- LocalWords: архи pattern matching JVM java байткод jad jd-gui
-->
<!-- LocalWords: декомпилятором байткода опциональный None empty ов
-->
<!-- LocalWords: Some value синглетон sealed null какая-то NPE this
-->
<!-- LocalWords: default каких-то public Serializable private super
-->
<!-- LocalWords: return color val You Me Task compilations Какие-то
-->
<!-- LocalWords: гуглом mutable immutable variable ul хэшкоду map
-->
<!-- LocalWords: transform task deep clone API Bloch Joshua new def
-->
<!-- LocalWords: original Function apply type RefMap трэйтов трэйт
-->
<!-- LocalWords: из-значения-типа-Task-в-значение-типа-Task джава
-->
<!-- LocalWords: джавы UsingRef ИспользующийСсылку proper types БД
-->
<!-- LocalWords: nullary constructor self trait lazy трэйта withRef
-->
<!-- LocalWords: билдер IdRefBuilder айди RefBuilder artist song rb
-->
<!-- LocalWords: compilation ref object idRefBuilder extends IntRef
-->
<!-- LocalWords: makeArtistRef makeCompilationRef makeSongRef scala
-->
<!-- LocalWords: IdRefBuilderHiddenType get implicit curring AnyVal
-->
<!-- LocalWords: toRefMap RichUsingRefIterable билдеры toSeq list
-->
<!-- LocalWords: одноименный using Iterable Seq iterable implements
-->
<!-- LocalWords: Product final artistMap songMap compilationMap NR
-->
<!-- LocalWords: withArtist withSong withCompilation assoc values
-->
<!-- LocalWords: refBuilder newArtistByOldKey mapValues comp IO bf
-->
<!-- LocalWords: newSongByOldKey newSongArtistRefs finntroll fb www
-->
<!-- LocalWords: newCompilationByOldKey newCompilationSongRefs net
-->
<!-- LocalWords: UuidRef билдера ValueRef cea-fbaf архиполезный If
-->
<!-- LocalWords: блоб эксепшн Печалька кто-то когда-то где-то ffff
-->
<!-- LocalWords: Effective possible background-color ff полурабочем
-->
<!-- LocalWords: сериализация десериализация транзакционная Classes
-->
<!-- LocalWords: should be unless there make very good reason to as
-->
<!-- LocalWords: them cannot made limit its mutability much skog gt
-->
<!-- LocalWords: по-разному svampfest newFinntroll наконец-то BEE
-->
<!-- LocalWords: newSong newSongs Вобщем-то копирований replace Key
-->
<!-- LocalWords: во-первых во-вторых Scrap your boilerplate blah
-->
<!-- LocalWords: какой-то derive чего-то что-то канонично Reference
-->
<!-- LocalWords: какие-то Nothing какому-то чем-то mix-in каким-то
-->
<!-- LocalWords: архиудобный package iterator toMap println test
-->
<!-- LocalWords: из-ключей-этих-объектов-в-сами-объекты Tuple
-->
<!-- LocalWords: как-нибудь
-->
Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com1tag:blogger.com,1999:blog-6827363280592532957.post-7143043971323260892009-08-02T14:05:00.003+03:002009-08-02T14:19:34.034+03:00Richfaces / Trinidad совсемстно с FaceletsПроапргрейдил существующий проект с Java 1.4 до Java 1.5, JSF 1.1 до JSF 1.2, реализацию JSF с Myfaces на Sun RI... и решил использовать в проекте какой-нибудь фреймворк предоставляющий AJAX для JSF. Первым попробовал Richfaces. Но оно как-то нестабильно заработало - то работает, то нет. Потратил два дня пытаясь понять почему, но безрезультатно. Затем попробовал Trinidad.. и с тем же успехом. Фреймворк работал нестабильно. Например, я открывал JSF страницу, и если кликал на AJAX'овую кнопку сразу после загрузки страницы, то все было нормально, запрос шел и обрабатывался, а если спустя пару секунд кликал, то запрос не обрабатывался и нужная часть страницы не обновлялась. Через какое-то время обратил внимание на странное и уже привычное сообщение в логах "INFO: Facelet[/page/blah.xhtml] was modified @ 14:23:24 AM, flushing component applied...". Оказалось, что это и есть причина проблемы. Время в виртуальной машине было на три часа меньше времени на компе на котором я собирал WAR файлы. Соответственно время создания файлов выглядила для websphere'ы на три часа из будущего. И похоже, что странный алгоритм Facelets обнаружения изменений в файлах начал сбоить по этой причине и перечитывать файл сбрасывая при этом дерево компонент! Решил проблему, установив facelets.REFRESH_PERIOD контекстный параметр в web.xml. Теперь оба фреймворка работают нормально. Осталось только определится какой я хочу..<ad></ad>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com1tag:blogger.com,1999:blog-6827363280592532957.post-64383110558506913152009-07-06T12:36:00.004+03:002009-07-06T19:23:13.811+03:00Простое DSL на ScalaНедавно я начал использовать Scala для одного из моих домашних хобби проектов. Ранее я слышал, что Scala позволяет писать код таким образом, что он выглядит как DSL встроенный в саму Scala'у. Решил попробовать использовать эту фичу. Мне было необходимо придумать красивое API для шедулинга сообщений актерам (actors). Вот что из этого получилось:<br /><pre><br />final class TimeSpec[T] (number : Long, action : Long => T) {<br /> def nanoseconds = action (number)<br /> def microseconds = action (number * 1000L)<br /> def miliseconds = action (number * 1000L * 1000L)<br /> def seconds = action (number * 1000L * 1000L * 1000L)<br /> def minutes = action (number * 1000L * 1000L * 1000L * 60L)<br /> def hours = action (number * 1000L * 1000L * 1000L * 60L * 60L)<br /> def days = action (number * 1000L * 1000L * 1000L * 60L * 60L * 24L)<br />}<br /><br />final class Trigger (actor : MyActor, payload : Any) {<br /> def in (number : Long) = new TimeSpec[Unit] (number, scheduleIn)<br /> def every (number : Long) = new TimeSpec[Unit] (number, scheduleEvery)<br /><br /> private def scheduleIn (nanos : Long) = Scheduler.inNano (actor, payload, nanos)<br /> private def scheduleEvery (nanos : Long) = Scheduler.everyNano (actor, payload, nanos)<br />}<br /><br />final class ActorSchedule (actor : MyActor) {<br /> def payload (payload : Any) = new Trigger (actor, payload)<br />}<br /><br />trait MyActor ..... {<br /> protected val schedule = new ActorSchedule (this)<br /> ....<br />}</pre><ad></ad><br /><br />В результате получил возможность писать вот такой код:<pre><br />object TestActor extends MyActor {<br /> schedule payload `Hi in 10 nanoseconds<br /><br /> schedule payload `HowAreYou every 5 seconds<br /><br /> schedule payload `Bye in 5 days<br /><br /> def act () = {<br /> case `Hi => println ("Hello!")<br /> case `HowAreYou => println ("I am fine")<br /> case `Bye => println ("Bye-bye")<br /> }<br />}</pre><br />Идея в том, что schedule - это объект класса ActorSchedule. Этот класс предоставляет один метод payload имеющий один аргумент (собственно то, что будет послано актеру). В действительности "schedule payload `Hi" это вызов "schedule.payload(`Hi)". Этот вызов создаст объект класса Trigger. У класса Trigger есть два публичных метода in(Long) и every(Long). Так как эти методы не могут ничего полезного сделать до тех пор пока не будет известна единица измерения времени, то эти методы создают объекты TimeSpec передавая им в качестве параметра собственный метод, который будет вызван с количеством наносекнуд одним из методов класса TimeSpec.<br /><br />Как мне кажется получился достаточно простой и удобный API без особых усилий.<ad2></ad2>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-23963477900823888672009-03-22T15:22:00.002+02:002009-03-22T15:48:40.596+02:00CeBootLin - автозагрузка Linux/Android из WinCE на Loox 5XX (560/550)Написал "bootloader" для загрузки Linux/Android из WinCE автоматически при включении КПК. Это не настоящий bootloader, так как он не имеет ничего общего с загрузочными секторами, это просто WinCE приложение, которое начинает свою работу как можно раньше, чтобы загрузить Linux. Я сделал это именно так, а не иначе, из-за того, что не хотел убить свою КПК изменением бут секторов или иметь сложный механизм перепрошивок, обновлений и т.д. Автозагрузка из WinCE мне показалась досточно неплохим решением, имеющим разве что 3-4 секундный проиграш, если сравнивать с нормальным bootloader'ом, который еще помучиться реализовать надо. Также, такая рализация позволяет достаточно легко пропустить загрузку Linux и продолжить загрузку WM5 или WM6.. Код основан на утилите Haret (спасибо им), но я удалил ненужные мне части. Так как загрузчик использует аппаратные регистры для работы с клавиатурой и подсветкой (GPIO/CPLD) Fujitsu Siemens Loox N560/C550, то без изменений кода данный загрузчик не заработает на другой модели КПК...<br />Когда КПК включается и начинается загрузка WinCE, то ядро WinCE читает список приложений из HKEY_LOCAL_MACHINE\init и запускает их. Таким способом загружается CeBootLin. CeBootLin начинает мигать подсветкой клавиатуры и ждет одну секунду. В то время, пока клавиатура подмигивает, пользователь может нажать и отпустить какую-нибудь кнопку КПК - это сигнал для CeBootLin не продолжать загрузку Linux. Если никакие кнопки нажаты не были, то CeBootLin попытается открыть \CeBootLin\default.txt и будет использовать его в качестве скрипта Haret для загрузки Linux'а.<br /><br />Итого, чтобы поставить и настроить CeBootLin, надо проделать такие шаги:<br />1. <a href="http://downloads.akshaal.info/linux-n560-c550/CeBootLin-1.zip?attredirects=0">Скачать</a> CeBootLin.<ad2></ad2><br />2. Распаковать содержимое архива в корень основной памяти КПК. CeBootLin.exe должен быть доступен по пути \CeBootLin\CeBootLin.exe.<br />3. Поместить default.txt в каталог \CeBootLin. Файл default txt может например выглядеть так (для загрузки Andorid'а, например):<pre><br />set MTYPE 1454<br />set KERNEL "\CeBootLin\zImage"<br />set CMDLINE "root=179:3 mem=62M rootdelay=3 boot_delay=0 init=/init console=tty0 fbcon=rotate:0 androidboot.console=tty0 android.checkjni=1"<br />set RAMADDR 0xA0200000<br />bootlinux<br /></pre><br />4. Поместить zImage в каталог \CeBootLin\.<br /><a href="http://picasaweb.google.com/lh/photo/11wJSVlZKWbWAN3DhaJXSw?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibMt1fqjMm9uy6EMLPY4mwHyfLCOW185T9ijHbvRoLy8Tie-w2iGxszcs56nBWhFItjaSQrtHOE3QU12YELZ74f5LkatyqTaZAScb2U-scZwwV-38A8-2hp8p93up16fgbuqGxGlvoesYb/s400/CeBootEdit-1.png" /></a><br />5. Добавить в HKEY_LOCAL_MACHINE\init значение для Launch82 ="CeBootLin.exe" и Depend82 = 14 00.<br /><a href="http://picasaweb.google.com/lh/photo/00DSupXz9qiOXs43h5f5cA?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA9oV7TAoWKEU_GCw6OXVRJ1BK9hnUP69xa-WuhYHXtKmWV9GY2JCefQgcwiKWIu47XplDAwIOoXSaL9e-R2LxHO7QtgdDxeHcrp0hKaYMT-l436PcYqDbS8anEKXGXnqUqC2gLfGdgFq_/s400/CeBootEdit-3.png" /></a><br /><a href="http://picasaweb.google.com/lh/photo/0Rc2C1G2xiQKk5R0RmvaZg?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKsra4QotvdaOFNtqhXwTExBB-6IzxtGbNG8k_5wEhLbub444YRB3SwXYALep1YHUJIKiHW0QP0JQhOGh46UAY-QDl3DEREPLrKaVHqYCSZawYbzzMOF8364TZ8tK2e9Ng10CO4jEqgmYj/s400/CeBootEdit-4.png" /></a><br />6. Добавить в HKEY_LOCAL_MACHINE\Loader\SystemPath путь \CeBootLin\<br /><a href="http://picasaweb.google.com/lh/photo/hbLfmcNrlr6RpUPvLX_-5A?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhecsc4aDa4i188OdbQynB4cMKKWsC0NBX7w_e2xk_xhGfYBNFqDdAd1U09x7brqDSKQxtPdW-v1-H6HrzWhSZJ6mmcdSaVaCdmDU-icOJBm9vu99JhQ6iDiNAkFEyaLR6c7qnXgrHTHq_K/s400/CeBootEdit-2.png" /></a><br />7. Подождать 5 минут или типа того (WinCE нужно время, прежде чем она сохранит обновление в registry).<br />8. Перезагрузить КПК<br /><br />Если возникнут какие-то проблемы, то первым делом стоит проверить, а грузит ли CeBootLin.exe вообще ядро... для этого достаточно просто его запустить.<br /><br />В дальнейшем при желании можно сделать нормальный cab с setup'ом внутри, который, например, будет ставить андройд..<br /><br /><a href="http://downloads.akshaal.info/linux-n560-c550/CeBootLin-1.tar.gz?attredirects=0">Здесь</a> находятся исходные коды CeBootLinux. CeBootLinux лицензирован под GNU GPL.<ad></ad>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com3tag:blogger.com,1999:blog-6827363280592532957.post-43833809951685196992009-03-15T16:06:00.002+02:002009-03-15T16:09:43.071+02:00Фотографии Android и Linux на Fujitsu-Siemens Loox N560<a href="http://picasaweb.google.com/lh/photo/CUrNwUv_XICGqKQKeMx6rA?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYTyWCI0G04tQ2C5IB7hMG8hB8xxTS9R4Wqzh2l5gUz1SxqrACiQnHKXEkK1oniuJe7DVPy7lb3atRpOTaJf2sVqxp3b_UTCk0K3FBi4L5SKtHutDlSUJTMy3Mg6i73mJ2Irg3Q3JQKwtN/s400/n560-1s.jpg" /></a><br />Сегодня судьба WM5 - загрузить Linux<br /><br /><a href="http://picasaweb.google.com/lh/photo/Wdt450DQpEcjKa1aIu3iUA?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKLGJW-BwuQJWgtqbHTt_mgBxtwqwcAkLNUIYXz5HE6l3SRO51kY0jTCwsBOUCS5wYb4MFXt-ioMlLUlMnbZFjCvV-zwPOweGrWUVtYGVhpyK_8kE4rbI7_SxbNdYI5AwAUaUBIYvhxJCU/s400/n560-2s.jpg" /></a><br />Haret.. все что нужно - нажать Run<br /><br /><a href="http://picasaweb.google.com/lh/photo/LQ7LXZhb1hXw6cwFARC4pg?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoAhm1XwOODQ7HJVbEqCZKUpubw8hFU3wqcZR-J2TkJ6Y4CoLBwQv8yBJoLDMptwc_bb1siIfqb8v5Eal2KEIZLlhxpBmsIZY068X3KPBSL-o1GVEp_8HA2i9QGKnu-Pgugxw5yK-krTOz/s400/n560-3s.jpg" /></a><br />Android на Loox N560<br /><br /><a href="http://picasaweb.google.com/lh/photo/K2EytIW2FJWybumV11UTRg?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfPExtvRBTFgBGjrq8_5nVNThzgVPma48WWa0yeTET00SrhbQahYqiHcnYNqr54nrmAVvVzJ7nHF5736a4Ca0xFut8SLpJgW8Gj1JjqEzXExv_viafVVvSyPGthxhvTye8zaVI1IeZ3nJz/s400/n560-4s.jpg" /></a><br /><a href="http://rus.akshaal.info/2009/03/emdebian-pda-c550n560.html">Установка Debian'а</a> на Loox N560<br /><br /><a href="http://picasaweb.google.com/lh/photo/ReuuEUcxhZ4y-Es7lRGneQ?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDSxbzLLIncA482Nd5wTtRvYB0AyYMcHu3-V9Yayr5Ja8XtilIhk2u0mQ5lmEr-Cws_tXK-Qf_x2AJp5EKqvn4N1ac3vqeuUZgAh3gM0T8LBoXzBzj5IWMr3g1BdF3rB0zrdpb-uow69Xw/s400/n560-5s.jpg" /></a><br /><a href="http://rus.akshaal.info/2009/03/emdebian-pda-c550n560.html">Установка Debian'а</a> на Loox N560Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com6tag:blogger.com,1999:blog-6827363280592532957.post-85406637443782264322009-03-15T14:54:00.004+02:002009-03-15T15:08:07.134+02:00Тестирование памяти на Loox N560/C550Недавно набросал "утилиту" для тестирования памяти на Fujitsu-Siemens Loox N560/C550. Нужна она тем, кто перепаевает память (с 64М на 128М) и хочет проверить ее работоспособность "на месте" без переустановки WM5. Итак, для того, чтобы протестировать память КПК:<br />1. Качаем <a href="http://downloads.akshaal.info/other-soft-for-pda">LooxMem128-v0.zip</a> (чтобы тестировать 128Мб памяти) или <a href="http://downloads.akshaal.info/other-soft-for-pda">LooxMem64-v0.zip</a> (чтобы тестировать 64Мб памяти).<br />2. Заливаем распакованный архив на SD карточку.<br />3. Карточку вставляем в Loox и перегружаем КПК.<br />4. Не давая КПК заснуть, запускаем haret.exe из архива, что распаковывали на SD карточку.<br />5. После запуска haret.exe нужно нажать на Run.<br />6. Загрузится linux и появится предупреждение.<br />7. Читаем предупреждение и либо нажимаем на RESET (на попе у КПК) либо нажимаем enter..<ad2></ad2><br />ВНИМАНИЕ: Накачественная память при тестировании может сгореть (собственно "утилита" для этого и нужна, чтобы выявлять такую память). Ни я, ни авторы memtester ни кто иной кроме вас никакой ответственности за это нести не будет. Вы тестируете память на свой страх и риск!!<br /><br />Замечание: При тестировании необходимо раз в 3-5 минут нажимать какую-нибудь кнопку КПК, чтобы КПК не заснула...<br />(<i>Для портирования "утилиты" на другую платформу достаточно заменить ядро zImage и поправить default.txt.</i>)<ad2></ad2>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com1tag:blogger.com,1999:blog-6827363280592532957.post-6141997722341529132009-03-14T19:49:00.003+02:002009-03-14T20:36:07.533+02:00Ядро linux для Loox N560/C550 с рабочим CPLD и светодиодамиДобавил поддержку CPLD и светодиодов. Для CPLD был заюзан драйвер htc-gpio с перечислением адресов регистров CPLD на Fujitsu-Siemens Loox 5XX (N560/C550). Теперь CPLD доступен для других драйверов.<br /><br />Примечательно, как в Linux'е реализована работа с CPLD. По сути, CPLD это чип, содержащий очень простую логику (за счет чего он очень быстр по сравнению с CPU), имеющий входные и выходные "ножки" (pins). Входные и выходные ножки - регистры - проецируются на определенную область памяти CPU. С логической точки зрения работа с CPLD заключается в переключении битов в регистрах CPLD в 0 или 1 (можно еще и читать оттуда, но я об этом тут не буду рассказывать..). Таким же образом выглядит работа с GPIO - общим вводом/выводом CPU: биты регистров CPU таким же образом принимают значение 0 или 1. В ядре Linux уже имеется драйвер GPIO, предоставляющий функции для работы с GPIO разработчикам платформенных драйверов. Также драйвер GPIO имеет механизм расширения, позволяющий другим драйверам/модулям проецировать заданные интервалы портов GPIO таким образом, что при обращении к такому порту произойдет вызов указанных при проецировании функций. Этот механизм используется CPLD драйвером htc-egpio для проецирования CPLD регистров на GPIO порты. Таким образом работа с CPLD идет через обыкновенные методы типа gpio_set_value.<ad2></ad2><br />На основе работающиго CPLD драйвера для Loox N560/C550 реализовал драйвер управления светодиодами КПК. Драйвер экспортирует методы и константы которые другие драйвера (wifi, gps, bluetooth, pm, ...) должны использовать для управления светодиодами. Также драйвер позволяет контролировать светодиоды из userspace пространства путем манипуляций с файлами sysfs. <br /><br />Ниже перечисленны файлы и значения которые могут находиться:<br /><b>/sys/devices/platform/loox5xx-leds.1/keyboard</b>: on, off, any<br />- подсветка клавиатуры<br /><br /><b>/sys/devices/platform/loox5xx-leds.1/left_green</b>: on, off, any<br />- зеленый светодиод с левой стороны. WM5 использует для индикации активности WiFi<br /><br /><b>/sys/devices/platform/loox5xx-leds.1/left_blue</b>: on, off, any<br />- голубой светодиод с левой стороны. WM5 использует для индикации активности Bluetooth<br /><br /><b>/sys/devices/platform/loox5xx-leds.1/left_orange</b>: on, off, any<br />- оранжевый светодиод с левой стороны. WM5 использует для индикации активности GPS на Loox N560<br /><br /><b>/sys/devices/platform/loox5xx-leds.1/right_green</b>: on, off, any<br />- зеленый светодиод на правой стороне КПК. WM5 использует как светодиод доступный приложениям (нотификации и т.д.)<br /><br /><b>/sys/devices/platform/loox5xx-leds.1/right_orange</b>: on, off, blink, any<br />- оранжевый светодиод с правой стороны КПК. WM5 использует для индикации процесса зарядки<br /><br />Где:<br /><b>on</b> - светодиод включен независимо от желаний ядра,<br /><b>off</b> - светодиод выключен независимо от желаний ядра,<br /><b>blink</b> - светодиод мигает независимо от желаний ядра,<br /><b>any</b> - светодиод контролируется ядром.<br /><br />Например, если установить right_orange в состояние on, то светодиод зарядки будет гореть независимо от того, заряжается КПК или нет. Это будет продолжаться до тех пор, пока в файл right_orange не будет записано значение any. После чего, первое же событие (установка КПК в крэдл и т.д.) переключит светодиод в соответствующее состояние. Sysfs интерфейс для светодиодов позволяет использовать светодиоды из программ для нотификации о событиях (пришла почта, кончается заряд и т.д.) с помощью всех доступных на устройстве светодиодов - насколько хватит фантазии. Например следующий shell код мигает подсветкой клавиатуры:<pre><br />while true;<br /> do echo on > /sys/devices/platform/loox5xx-leds.1/keyboard;<br /> sleep 0.1;<br /> echo off > /sys/devices/platform/loox5xx-leds.1/keyboard;<br /> sleep 0.1;<br /> # Condition for break...<br />done<br />echo any > /sys/devices/platform/loox5xx-leds.1/keyboard;</pre><br /><br />Актуальный патч на cupcake ядро андройда качать <a href="http://downloads.akshaal.info/linux-n560-c550/c550-android-kernel-2009-03-14.patch.gz?attredirects=0">здесь</a>.<br /><ad></ad>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com6tag:blogger.com,1999:blog-6827363280592532957.post-78349665113851074082009-03-10T10:07:00.006+02:002009-03-10T10:18:42.568+02:00Полезные команды для межплатформенной работыНесмотря на то, что debian/emdebian уже содержат громадное количество пакетов под armel, иногда нужно собрать или пересобрать что-то из исходников.<br /><br />Сборка пакетов в emdebian происходит например так:<pre>emsource --arch armel -b memtester</pre><br />Установка собранного пакета НЕ на целевом устройстве, а на хостовом, выполняется например вот так:<pre><br />dpkg-cross -a armel -i zlib1g-dev_1.2.3.3.dfsg-13em1_armel.deb<br /></pre>При этом команда <i>dpkg -l | grep zlib1g</i> выдаст:<pre><br />ii zlib1g-dev-armel-cross 1:1.2.3.3.dfsg-13em1<br /></pre><ad2></ad2><br />(dpkg-cross автоматически переназвала установленный пакет и разместила его содержимое в /usr/arm-linux-gnueabi)<br /><br />Чтобы пользоваться командой <i>emsource</i> без привелегий root'а, надо настроить <i>~/.apt-cross/emsource</i> например вот так:<pre><br />workingdir: /home/akshaal/.apt-cross-working-dir<br /></pre><br />Сборка пакета под armel из исходников, которые уже развернуты и имеют каталог debian производится командой:<pre><br />dpkg-buildpackage -aarmel</pre>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-52969838341258099952009-03-09T15:29:00.003+02:002009-03-09T15:39:22.028+02:00Установка emdebian на PDA (C550/N560)Последовательность действий, выполнив которую я без проблем установил debian на свой Fujitsu-Siemens Loox N560. Я предполагаю, что emdebian-tools уже установлен, а также стоят всякие кросскомпиляторы под arm платформу.<br /><br />Первым делом необходимо собрать архив базовой системой:<pre>cd /tmp;<br />mkdir grip/<br />sudo debootstrap --arch=arm --foreign lenny grip/ http://www.emdebian.org/grip/<br />cd grip/<br />sudo tar -czf /tmp/emdebian-grip-arm-debootstrap.tgz .</pre>Теперь необходимо переразбить SD карточку выделив на ней раздел под linux:<pre><br />cfdisk /dev/sdX<br />mkfs.ext3 /dev/sdXy</pre><br />где sdX - это устройство с SD картой, а sdXy это раздел который предполагается использовать под linux. Теперь монтируем раздел и разворачиваем туда emdebian-grip-arm-debootstrap.tgz:<pre><br />mount /dev/sdXy /mnt<br />cd /mnt<br />tar zxpvf /tmp/emdebian-grip-arm-debootstrap.tgz<br />ln -s bin/sh init<br />cd /tmp<br />umount /mnt<br /></pre><br />Теперь, когда раздел подготовлен, загружаем с него linux и выполняем следующие команды:<pre>cd /debootstrap<br />export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />run ./debootstrap --second-stage<br />dpkg --configure -a<br />rm /init<br />cd /<br />ln -s sbin/init init<br /></pre><ad2></ad2>Теперь перегружаем linux еще раз. В этот раз должно появится приглашение залогинится. Используем root без пароля. И конфигурируем основные настройки:<pre><br />echo 'nameserver 1.1.1.1' > /etc/resolv.conf<br />echo '127.0.0.1 localhost' > /etc/hosts<br />echo '172.16.0.2 myhost 172.16.0.2' >> /etc/hosts<br />echo 'myhost' > /etc/hostname<br /></pre><br />Вместо 1.1.1.1 нужно использовать IP DNS сервера. И снова перегружаем КПК. Теперь конфигурирем сеть:<pre><br />ifconfig usb0 172.16.0.1 netmask 255.255.255.0<br />route add default gw 172.16.0.1</pre>Убеждаемся, что сеть работает. Апгрейдим пакеты:<pre><br />echo 'deb http://www.emdebian.org/grip/ sid main' > /etc/apt/sources.list<br />echo 'deb http://ftp.debian.org/debian/ sid main contrib non-free' >> /etc/apt/sources.list<br />apt-get update<br />apt-get dist-upgrade<br />apt-get install ...что угодно...</pre><br />И теперь настраеваем сеть так как это положено (собственно это можно было зделать и раньше, но тут можно уже залогинится через ssh и нормально все сделать с помощью copy&paste с ББ...):<pre>cat > /etc/network/interfaces<br />auto lo<br />iface lo inet loopback<br />allow-hotplug usb0<br />iface usb0 inet static<br /> address 172.16.0.2<br /> netmask 255.255.255.0<br /> network 127.16.0.0<br /> broadcast 172.16.0.255<br /> gateway 172.16.0.1<br /> dns-nameservers 1.1.1.1<br /> dns-search your.domain<br /></pre><ad></ad>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-72887756924752396952009-03-07T22:51:00.005+02:002009-03-08T12:49:32.445+02:00Ядро linux для Loox N560/C550 с рабочим usbnet'омПосле 3х дней мучений все-таки собрал ядро linux для Fujitsu-Siemens Loox N560/C550. Ядро брал с <a href="http://www.asm32.ru">http://www.asm32.ru</a>. Потратив еще несколько часов разобрался как запустить usbnet (сеть через обычный usb шнурок). Для этого пришлось дополнительно пропатчить pxa27x_udc.c. Сделал diff между текущей версией ядра андройда и теми исходниками (за 02-03-09), что выложены на asm32. В дальнейшем при обновлении ядра в cupcake'овской ветке можно будет просто накладывать этот патч сверху имея в результате и изменения под c550/n560 и исправления/дополнения внесенные в cupcake ветку. <a href="http://downloads.akshaal.info/linux-n560-c550/c550-android-kernel-2009-03-07.patch.gz?attredirects=0">Вот итоговый патч</a> в который также включены изменения для работы usbnet. В патче уже имеется .config, но там не включена поддержка usbnet. <a href="http://downloads.akshaal.info/linux-n560-c550/linux-config-2009-03-07?attredirects=0">Вот другой config</a>, он отличается от того, что в патче (а там тот, что выложен на asm32) поддержкой usbnet, выключенной power management (зачем не знаю, потом надо будет включить обратно), и настройкой для более крупного шрифта в консоли.. может еще чем-то.<br /><br />Собирать ядро можно с помощью любого toolchain'а. Я пробовал собирать с помощью: crosstool-ng, emdebian, а также того, что идет вместе с ядром андройда. Остановился на emdebian'е. Для сборки ядра emdebian'ом необходимо выполнить:<pre>make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-</pre>Предварительно emdebian конечно же нужно установить, например так:<pre><br />apt-get install emdebian-tool<br />emsetup --arch arm</pre>Добавить:<pre><br />deb http://www.emdebian.org/debian/ unstable main</pre><br />в /etc/apt/sources.list и выполнить:<br /><pre>apt-get install linux-kernel-headers-arm-cross gcc-4.2-arm-linux-gnu</pre><br /><br />Готовое ядро запускается haret'ом с конфигом:<pre><br />set MTYPE 1454<br />set KERNEL zImage<br />set CMDLINE "root=179:2 mem=60M rootdelay=3 boot_delay=0 init=/init console=tty0 fbcon=rotate:0 androidboot.console=tty0 android.checkjni=1"<br />set RAMADDR 0xA0200000<br />bootlinux</pre><ad></ad><br />И самое главное! После того, как ядро закинуто на sd карточку, эту sd карточку нужно вставить в КПК и ребутнуть КПК. А уже только после этого грузить haret'ом ядро! Ибо ядро не грузится (либо грузится с вероятностью 10%), если:<br />1. КПК заснула, а потом ее разбудили.<br />2. Вы только что вставили в КПК карточку.<br />Наверное это бага драйвера wince...<br /><br />P.S. Cupcake на который накладывается выложенный патч качается отсюда <a href="http://source.android.com/download"> http://source.android.com/download</a><br /><br />P.P.S. При сборке с поддержкой usbnet опцию CONFIG_USB_ETH_RNDIS<br /> включать не следует! Ибо в этом случае ПК не может нормально соеденится с КПК.. ни cdc_ether ни cdc_subset не видят usb устройство.Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-81237875653937678422009-03-06T18:29:00.005+02:002009-03-08T14:03:10.681+02:00Related Posts / Похожие темыНаписал расширение (widget) для тем blogger'а. Расширение отображает список постов похожих по меткам. Данное расширение отличается от всех прочих Related Posts Widget'ов тем, что <b>очень легковесно и не замедляет загрузку страницы браузером</b>, так как выполняет дополнительные запросы к серверу параллельно (асинхронно) с загрузкой страницы. Более того, записи сортируются не только с учетом даты написания постов, но и <b>с учетом релевантности (по меткам)</b>. Так как у меня несколько блогов и мне не хочется заниматься рутиной, то я постарался написать виджет как можно более простым для установки.<br /><br />Итак, чтобы установить это расширение в своем блоге, необходимо произвести две довольно простые модификации HTML шаблона (HTML Template'а). Для этого нужно открыть <b>Настройки (Settings) -> Макет (Layout) -> Изменить HTML</b>. Перед тем как менять что-то далее, сделайте резервную копию шаблона (<b>Загрузить весь шаблон</b>)! После чего нужно пометить галочкой <b>Расширить шаблоны виджета (Expend Widget Templates)</b>. После чего можно приступать к редактированию:<br />1. В поле с текстом шаблона нужно найти строку <b><data:post.body/></b>. И вот сразу за этой строкой нужно аккуратно вставить следующий код:<pre><div caption='Похожие темы:' id='akRelatedPosts' max='8'/><br /><b:if cond='data:blog.pageType == &quot;item&quot;'><br /><script language='javascript' type='text/javascript'><br />(function(_1,_2){var _3={};var _4=_2.length;var _5=function(_6){var _7=&quot;([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?&quot;;var d=_6.match(new RegExp(_7));var _9=0;var _a=new Date(d[1],0,1);if(d[3]){_a.setMonth(d[3]-1)}if(d[5]){_a.setDate(d[5])}if(d[7]){_a.setHours(d[7])}if(d[8]){_a.setMinutes(d[8])}if(d[10]){_a.setSeconds(d[10])}if(d[12]){_a.setMilliseconds(Number(&quot;0.&quot;+d[12])*1000)}if(d[14]){_9=(Number(d[16])*60)+Number(d[17]);_9*=((d[15]==&quot;-&quot;)?1:-1)}_9-=_a.getTimezoneOffset();time=(Number(_a)+(_9*60*1000));return Number(time)};var _b=function(){var _c=[];for(var i in _3){_c.push(_3[i])}if(_c.length&lt;1){return}var _e=document.getElementById(&quot;akRelatedPosts&quot;);if(!_e){return}var _f=_e.getAttribute(&quot;max&quot;)||5;_c=_c.sort(function(a,b){var _12=b.weight-a.weight;if(_12!=0){return _12}return b.date-a.date});var s=&quot;&lt;ul&gt;&quot;;for(var i in _c){if(_f--&lt;1){break}var _14=_c[i];s+=&quot;&lt;li&gt;&lt;a href=&#39;&quot;+_14.href+&quot;&#39;&gt;&quot;+_14.title+&quot;&lt;/a&gt;&lt;/li&gt;&quot;}s+=&quot;&lt;/ul&gt;&quot;;var _15=_e.getAttribute(&quot;caption&quot;);s=&quot;&lt;span class=&#39;caption&#39;&gt;&quot;+_15+&quot;&lt;/span&gt;&quot;+s;_e.innerHTML=s};var _16=function(_17){for(var i in _17.feed.entry){var _19=_17.feed.entry[i];var _1a=_19.id[&quot;$t&quot;];var _1b=_3[_1a];if(_1b){_1b.weight++}else{var _1c;for(var _1d in _19.link){if(_19.link[_1d].rel==&quot;alternate&quot;){_1c=_19.link[_1d].href;break}}if(_1c==_1){continue}_1b={weight:1,title:_19.title[&quot;$t&quot;],date:_5(_19.published[&quot;$t&quot;]),href:_1c};_3[_1a]=_1b}}if(--_4==0){_b()}};var _1e=function(_1f){var _20;try{_20=new XMLHttpRequest()}catch(excp1){try{_20=new ActiveXObject(&quot;Msxml2.XMLHTTP&quot;)}catch(excp2){try{_20=new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;)}catch(excp3){_20=false}}}var _21=function(){if(_20.readyState==4){if(_20.status==200){_16(eval(&quot;(&quot;+_20.responseText+&quot;)&quot;))}}};_20.open(&quot;GET&quot;,_1f,true);_20.onreadystatechange=_21;_20.send(null)};for(var _22 in _2){_1e(_2[_22])}} <!-- = = = = = endoffunc = = = = = = --><br />)(&quot;<data:blog.url/>&quot;,[<br /><b:loop values='data:post.labels' var='label'><br /> &quot;/feeds/posts/default/-/<data:label.name/>?alt=json&amp;max-results=5&quot;<b:if cond='data:label.isLast != &quot;true&quot;'>,</b:if><br /></b:loop><br />]);<br /></script><br /></b:if><br /></pre><ad></ad>Должно получиться что-то типа вот этого (на картинку можно нажать и нормально рассмотреть):<br /><a href="http://picasaweb.google.com/lh/photo/O8Slxb1iCNPJc0Ot6zdHew?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgydhmILw0Ij901VKFPayYNihoT8BEF39V_U7VGAca_lxHRGjkzH0a9rgSFtnyhDoiTwYH1fTmIO4WK-o37Pws5Qa2mRyttCYDz1GPICvlvRU9jDiCx9fz48OzD8nqzHKG14i31ZHQNxalZ/s400/rus.png" /></a> Обратите внимание на caption='...' и max='8' Значения в кавычках можно изменить. Значение для <i>caption</i> определяет текст, который будет высвечиватся над списком похожих постов. Значение <i>max</i> определяет максимальное количество элементов в списке.<br />2. Это изменение вобщем-то и не обязательно, все должно заработать и без него. Но выглядеть оно будет не очень. Поэтому, этим шагом производится настройка внешнего вида. Для этого необходимо найти строку <b>]]></b:skin></b>. И <b>перед(!)</b> этой строкой вставить вот такой кусок настроек:<pre>#akRelatedPosts {<br /> padding-top: 20px;<br />}<br /><br />#akRelatedPosts .caption {<br /> font-weight: bold;<br />}<br /><br />#akRelatedPosts ul {<br /> margin: 0;<br />}</pre>Если есть желание и знание css, то можно тут подкрутить стили как душе угодно. Должно получиться как-то вот так:<a href="http://picasaweb.google.com/lh/photo/1KWdAkPHB-8HbUACiKYfZA?authkey=Gv1sRgCMTt-MKs5cTh6wE&feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtW9CaKo2vS_b33DaaW1I4jvimzUinXB6m6NdS95AL0JkhwM9l3IHh0px5r1r6HGr3gCD3p_XyZma2DZPpUlGXUQDY-wSuH1EKAw3aCcfE-qKlw3GWs_q7T2q2wM1ibfqjCXna_lAhx2Tz/s400/css.png" /></a><br /><ad2></ad2>Это все. Можно сохранять шаблон и пробовать.Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-78427116207462611102009-03-05T19:52:00.002+02:002009-03-05T20:00:30.918+02:00Локальное тестирование AJAX запросовИногда бывает очень удобно отлаживать javascript сценарии без заливки на сервер, а просто путем запуска из локально html файла. Но таким образом не получается протестировать скрипты делающие асинхронные запросы. Связано это с тем, что firefox не дает выполнять запрос, если домен в запросе не соответствует домену с которого этот запрос выполняется - делается это из соображений безопасности.<pre>uncaught exception: Access to restricted URI denied (NS_ERROR_DOM_BAD_URI)</pre>Но если вы отдаете себе отчет в том что делаете, то никто не мешает отключить эту проверку. Для этого нужно открыть <b>about:config</b> и установить <b>signed.applets.codebase_principal_support</b> в <b>true</b>. Также перед выполнением запроса в javascript'е, необходимо выполнить следующий код:<pre>netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");</pre><ad2></ad2><br />Самое главное, по окончанию тестирования не забыть удалить эту строку из скрипта и установить <b>signed.applets.codebase_principal_support</b> в <b>false</b>!!Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-73799115934815583662009-02-22T18:48:00.002+02:002009-02-22T18:55:38.269+02:00Google Blogger после LivejournalПосле 5 лет жизни в ЖЖ, переехал в Blogspot. Итак впечатление следующие:<br />1. Практически полная свобода творчества по оформлению блога. В ЖЖ огромное множество ограничений (на бесплатных аккаунтах).<br />2. Интеграция с сервисами Google решает. Очень удобно размещать фотографии в Google Picasa. Наблюдать статистику в Google Webtools и смотреть посещаемость в Google Analytics. А при желании можно добавить Google AdSense. На все про все один аккаунт от google.<br />3. Нет lj-cut. При желании это решается, но мне не особо и нужно.<br />4. С одного аккаунта можно вести множество блогов. В ЖЖ для этого надо было перелогиниваться. В блоггере отношение между журналами и авторами - многие ко многим. Тоесть один журнал может вести множество авторов и каждый автор может вести множество журналов.<br />5. Индивидуальность.<ad2></ad2>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com1tag:blogger.com,1999:blog-6827363280592532957.post-87117102392717052472009-02-22T18:07:00.002+02:002009-02-22T18:11:27.704+02:00Основные заблуждения про Google SitesПочитав форумы и попробовав Google Sites вживую, становится понятно, что основными заблуждениями являются:<br />1. <i>Google Sites не поддерживают домены пользователя.</i> Это не так. <b>В Google Sites можно замэппить любую часть сайта на любой доступный домен.</b><br />2. <i>Google Sites позволяет создавать сайт наполняя его любым легальным контентом.</i> Это не так. Нужно быть осторожным, так как например <b>размещение AdSense блоков запрещено соглашением</b>. И при нарушении данного соглашения, ваш <b>сайт будет удален</b>.<ad2></ad2>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com1tag:blogger.com,1999:blog-6827363280592532957.post-17957183996792113002009-02-21T21:14:00.003+02:002009-02-22T13:50:34.462+02:00Google Blogger & AdSenseBlogspot имеет встроенные механизмы для размещения контекстной рекламмы AdSense. Но по ряду причин встроенный механизм мне не подошел (например, он не дает возможность прицепить каналы для мониторинга активности того или иного блока). С другой стороны, Google Blogger позволяет редактировать HTML Template блога и вставлять туда произвольный код в том числе и любой Javascript. Этим грех не воспользоваться для размещения AdSense кода, сгенерированного в панели управления Google Adsense. Казалось бы все просто, но я потерял 3 часа пытаясь понять, почему код блока, сгенерированный в Google Adsense и вставленный в окне HTML Template, не работает.<ad></ad> Вместо рекламы или хотя бы социальной рекламы я наблюдал пустое пространство совсем даже не того размера, который задавал. Разбор полетов, в том числе с помощью firebug'а показал, что на запрос к http://googleads.g.doubleclick.net, гугл возвращал код 400 Bad Request. Почему это запрос плохой - adsense сервер сообщить не потрудился. Дальнейшее сравнение видов запроса с работающего блока и запроса с неработающего блока показало, что неработающий блок в запросе не сообщает идентификатор клиента и прочую информацию такого рода. Подозрение пало на джаваскрипт, в котором эти параметры определяются. Код, который генерирует AdSense выглядит примерно так:<pre><script type="text/javascript"><b><!--</b><br />[... код в стиле var бла=бла-бла-бла; ...]<br />//<b>--></b><br /></script></pre><ad2></ad2><br />На первый взгляд, все выглядит правильно. Но подозрение закралось на <b><!--</b> и <b>//--></b>. Убрал эти элементы, которые по идее должны помогать браузерам, не поддерживающим джаваскрипт. И о чудо! Блок заработал. Но гугл говорит, что их код править нельзя. Тут у меня закралось еще одно подозрение и в окошке HTML Template гугловский код я записал вот так:<br /><pre><script type="text/javascript"><b>&lt;!--</b><br />[... код в стиле var бла=бла-бла-бла; ...]<br />//<b>--&gt;</b><br /></script></pre><br />Это и оказалось правильным решением проблемы.<br /><br />Вероятное обьяснение: судя повсему, сначало парсер Blogspot'а разбирает template, а потом его форматирует обратно в HTML. При этом переводы строк внутри комментариев <!-- --> он не сохраняет, а рендерит одной строкой в HTML. В результате весь java код выкусывает парсером браузера.Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-31988525560964962762009-02-04T18:10:00.002+02:002009-02-22T16:36:14.177+02:00Microsoft Windows EnvironmentКаждый раз перезжая на новое рабочую машину, я думаю - это последний раз, больше не придется.<br /><ad></ad><br />1. Установить http://virtuawin.sourceforge.net/ и назначить переключение рабочих столов на Win-0 ... Win-9 - как общие столы.<br /> Win-C для стола с Eclipse. <br /> Win-I для всяких чатов.<br /><br />2. Установить http://www.skynergy.com/hotkeyz.html<br /> Назначить Win-z для запуска rxtv с zsh<br /> Назначить Ctrl-Shift-f12 на закрытие окна<br /> Win-Enter - maximize window<br /> Shift-Win-Enter - restore window<br /><br />3. Установить http://www.cygwin.com/ с пакетами python, perl, zsh, diff, patch, make, mc и еще кучей ползеных программ и утилит.<br /><br />4. Если вдруг оказалось, что cygwin считает домашней директорией сетевой диск и из-за этого тормозит, то незабыть поменять home директорию в /etc/passwd<br /><br />5. В /etc/passwd прописать /usr/bin/zsh А в домашнюю директорию положить .zshrc следующего содержания:<pre><br />alias ls='ls --color'<br />alias ll='ls -l --color'<br />alias dfh='df -h'<br />alias duh='du -h'<br />alias rm="rm -i"<br />alias mc="mc -a"<br /><br />alias c="cd /cygdrive/c"<br /><br />alias cdp='cd /cygdrive/c/projects/'<br /><br />export INPUTRC=$HOME/.inputrc<br /><br />export HISTSIZE=80000<br />export HISTFILE=~/.history<br />export SAVEHIST=80000<br /><br />setopt hist_ignore_dups append_history bsd_echo multios<br />setopt hist_ignore_all_dups<br />setopt SHARE_HISTORY<br /><br />prompt=`echo -ne "%{\033[31m%}%n@%M:%B%30<..<%~%#%b "`<br /><br />export EDITOR=vim<br /><br />unsetopt beep<br /><br /># ##########################################################################3<br /># -+--+-+ --+-+-<br /><br />bindkey "\e[1~" beginning-of-line<br />bindkey '\eOH' beginning-of-line<br />bindkey "\e[2~" end-of-history<br />bindkey "\e[3~" delete-char<br />bindkey "\e[4~" end-of-line<br />bindkey "\eOF" end-of-line<br />bindkey "\e[5~" history-incremental-search-backward<br />bindkey "\e[6~" history-incremental-search-forward<br />bindkey "\e[A" history-beginning-search-backward<br />bindkey "\e[B" history-beginning-search-forward<br />bindkey "\e[C" forward-char<br />bindkey "\e[D" backward-char<br />bindkey "\e[7~" beginning-of-line<br />bindkey "\e[8~" end-of-line<br />bindkey "\e[3~" backward-delete-char<br />bindkey "\e[3~" delete-char<br /><br /><br /># ###########################################################################<br />chpwd precmd () {<br /> laststatus=$?<br /> [[ $laststatus != 0 ]] && print "\033[1,33m**** \033[1,31mExit code: $laststatus\n"<br /><br /> [[ -t 1 ]] || return<br /> case $TERM in<br /> *xterm*|*rxvt*|*(dt|k|E)term*) print -Pn "\e]2;%n@%M:%15<..<%~%<<\a"<br /> ;;<br /> esac<br />}<br /><br />preexec () {<br /> [[ -t 1 ]] || return<br /> case $TERM in<br /> *xterm*|*rxvt*|*(dt|k|E)term*) print -Pn "\e]2;%n@%M:%15<..<%~%<< (%15>..>$1%>>)\a"<br /> ;;<br /> esac<br />}<br /><br />chpwd<br /><br /># The following lines were added by compinstall<br /><br />zstyle ':completion:*' completer _expand _complete<br />zstyle ':completion:*' expand prefix suffix<br /><br />autoload -Uz compinit<br />compinit -u<br /># End of lines added by compinstall<br /></pre><ad></ad><br /><b>6. В c:/cygwin положить файлик rxvt.bat:</b><br /><pre>@echo off<br /><br />C:<br />chdir C:\cygwin\bin<br /><br />set CYGWIN=codepage:oem tty binmode title<br />set TERM=cygwin<br />rxvt -geometry 90x30 -bg black -fg white -sr -sl 5000 -fn 10x20 -e c:/cygwin/bin/zsh --login -i</pre>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-90482667908529884512008-11-22T18:52:00.001+02:002009-02-22T16:35:31.375+02:00.ля6 месяцев назад, сделал в VirtualBox'е snapshot виртуальной машины в которой запускаю виндовые проги. Сегодня решил этот снапшот убить. И убил актуальное состояние. В результате у меня больше нет вещей спроектированных и созданных собственными руками -- схемы и разведенные печатные платы. Особенно жалко:<br />1. 1-wire датчик влажности и температуры + 1-wire интерфейс к датчику движения.<br />2. 1-wire датчик PH (ph-метр) на двух ОУ.<br />3. Библиотека использованных компонент для Altium Designer'а...<br /><br />Все это и еще многие другие схемы я планировал выложить в блог... Грустно. Несмотря на то, что все эти схемы вытравлены и реализованы в "железе", некоторые из них мне нужно будет делать повторно....<br /><br />Принимаю соболезнования.<ad2></ad2>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com1tag:blogger.com,1999:blog-6827363280592532957.post-277740139868048232008-08-12T00:22:00.001+03:002009-02-22T16:35:07.602+02:00N560Звук и кнопки на КПК после дождя плохо себя ведут. Сегодня решил с этой проблемой что-то сделать.<br /><ad2></ad2><br />Открывать КПК как-то стремно стало. Боюсь, что обратно я его соберу с еще большими повреждениями. Решил забить на встроенный динамик. При желании можно и наушники для громкой связи использовать задрав громкость до предела. Не выход конечно... А вот с клавиатурой все печальнее. Сначало я думал, что на каждое нажатие кнопки приходит два скан кода один за другим. И думал, что смогу отрезать второй, который приходит слишком рано. Благо перехват скан кодов уже реализован в LooxLight'е. А тут оказалось все куда печальнее. Там не два скан кода приходит. А один "одновременный". Как бы это описать. Ну вот PC клавиатуре там настоящие скан коды. А в клавиатуре КПК, там просто значение у которого каждый бит занчит нажатую кнопку. Значение состоит из двух слов, старшее для стрелочек, а младьшее для всех остальных кнопок. И вот я по логам LooxLight'а наблюдаю следующую картину:<br />2003.01.02 17:33:38.0000: 6f7dacca: 8f7ddf4c: Char <b>00040004</b>, cur=00000004, next=00000005, 8f7ddf4c, 8f7ddc64<br />И так на каждое нажатие. На любое нажатие левое слово = правое. В результате нажимаем стрелочку влево, а получаем и влево и еще какое-то нажатие. И наоборот. Короче хрен обьяснишь. Думаю это как-то завязано на то, что такие клавиатуры обычно реализуются через "матрицу" контактов или хз как и какие-то две ветки там замыкает. А может я и не прав. Сейчас сделаю временное решение, чтобы дисплей хоть разблокировать можно было, а потом буду думать...<br /><br /><b>Временное решение</b>:<br />Сравнивать коды клавиш таким образом, что fix(нажатой кнопки) == fix (образца):<pre><br />UINT fix (UINT x) {<br /> UINT y = (x & 0xFFFF) | (x >> 16);<br /> return y | (y << 16);<br />}<br /></pre><ad2></ad2>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-68035989963255122212008-08-11T19:31:00.001+03:002009-02-22T16:33:45.504+02:00Зедаржки в JavaТак как для задач управление домашней автоматикой я сделал не совсем очивидный выбор - Java, то, естественно, мне было необходимо настраивать работу Java приложения под требование иметь минимальные задержки на события. В результате, для того, чтобы получить имеющийся результат, пришлось предпринять следующие действия:<br /><ad2></ad2><br />1. Пускать приложение с schedtool -n -20 -R -p 1 -e /opt/mywire/bin/mywire<br /> Данная команда позволяет запустить приложение в realtime группе с приоритетом 1. Все приложение linux имеют приоритет 0 независимо от nice level'а. Таким образом работа нашего mywire всегда приоритетнее любой задачи.<br /><br />2. В некоторых критичных нитях, с помощью обращения к native методу происходит установка приоритета в значение 40. Таким образом внутри самого приложения нити имеют разные приоритеты. Это опасно и может привести к блокировкам!<br /><br />3. Для того, чтобы ни в коем случае mywire не оказался в свопе, память лочится с помощью обращения к native методу mlockall.<br /><br />4. Сам java процесс пускается со следующими опциями:<pre><br /> -Xms30m -Xmx64m -XX:PermSize=15m -XX:MaxPermSize=30m<br /> -XX:+UseTLAB -jvm server<br /> -XX:MaxGCPauseMillis=20 -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode<br /> -XX:+CMSClassUnloadingEnabled<br /> -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses<br /> -XX:+BackgroundCompilation</pre><br /> Количество памяти, которое я отдаю под heap выбранно таким образом, чтобы сразу после сборки мусора (full gc), свободной памяти было где-то 60%.<br /><br />Надеюсь ничего не забыл. Как результат мы имеем задержки порядки 10-16мс. При том, что библиотека quartz сама работает с дискретеностью в 10мс. Тоесть если ей сказать, что таску нужно запустить через 5мс, она ее все равно запустить через 10.<br /><br />К сожалению раз в час происходит full gc или что-то типа того и это влечет за собой неожиданные задержки. Величина зависит от того, как совпадут время срабатывания и начало сборки мусора. В среднем это 200мс. Бывает и 500мс и 700мс. Будем думать как это побороть.<br /><br />Вот графики:<br />Latency:<br /><img src="http://toril.ru/lj/hour.rrd.png"><br /><br />Память.<br /><img src="http://toril.ru/lj/phour.rrd.png"><br /><ad></ad><br />Видно, что в момент сборки мусора был всплеск задержек.<br /><br />Предугадывая вопросы:<br /> Java - чтобы писать быстрее.<br /><br /> Latency - в данном случае, это разница времени момента срабатывания таймера и времени, в которое данный таймер должен был сработать. Тоесть, например, если было сказано спать 100мс, а таймер проспал 120мс, то задержка 20мс.<br /><br /> Важность Latency заключается в том, что опрашивать некоторые датчики необходимо весьма часто, скажем датчик движения 2 раза в секунду. В моем варианте датчик движения замыкает размыкает контакты конденсатора давая ему зарядится, таким образом датчик напряжения 1-wire успеет заметить какой-нибудь заряд, даже если он пропустит сам импульс. Так вот если Latency будет слишком высокой (скажем 2-3 секунды), то срабатывания датчик может просто остаться незамеченным.<br /><br /> Кто озадачился вопросом, почему датчик движения не может сам сообщить о срабатывании, отвечу: 1-wire сеть может иметь только одного master'а, все остальные slave'ы.Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-32937817545952371742008-08-11T11:35:00.002+03:002009-02-22T16:32:34.122+02:00Home PortalДомашний портал. Эта та страничка, что стоит в браузере как home page. Отсюда я/жена читаю новости, смотрю погоду за окном (графики влажности, температуры), наблюдаю за работой сервера и т.д. Предназначен он для home automation (ну там энергосберегающий умный дом (smarthouse) и т.д.). Работа в свободное время. Сбор информации и управление 1-wire, в будущем будет еще X-10 (управление светом), камеры и т.д.. Много всего написано, но на сервере на production сервере еще не выложено. Сам портал напроч AJAX'ный, перегрузок страниц не требует. В качестве платформы Jboss Portal + DOJO (следующие портлеты будут без dojo, но на GWT). В качестве ядра, standalone java process со стеком spring + termware + quartz и т.д.<br /><br />Но вообще пост не об этом, а о двух подряд идущих анонсах в ленте torrent.net.ua (см. скриншоты).<br /><ad></ad><br /><a href="http://toril.ru/lj/homeportal.png">Картинка 1</a><br /><a href="http://toril.ru/lj/homeportal2.png">Картинка 2</a><br /><a href="http://toril.ru/lj/homeportal3.png">Картинка 3</a>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-87828215752499460962008-07-10T16:48:00.003+03:002009-02-22T16:32:00.178+02:00Выбор domain языкаПредположим нужно контроллировать некоторую автоматику. Для этих целей необходимо выбрать способ формализации алгоритмов управления. Короче и проще говоря, нужен язык со следующими, как мне кажется, требованиями:<br />1. Имеющий реализацию интерпретатора на Java.<br />2. Парсинг исходного кода в runtime'е, а не предварительно.<br />3. Локоничный.<br />4. Расширяемый словарь.<br />5. Возможность захватывать состояние выполнения... мммм.. как это по-русски.. continuationы.<br /><ad></ad><br />Это может быть что угодно, функциональный, императивный, декларативный, rewriting engine, macro и.д.<br /><br />Пример псевдокода, который должен писаться на целевом языке не намного более длиннее чем это:<pre><br />Loop:<br /> T = GetCurrentTemperature<br /> State = AccordingToMode ( If (T < REQUIRED_T, On, Off) )<br /> SetDeviceState ("HeaterDevice", State)<br /> WaitEvent (Polled("TemperatureDevice"), ModeChange)<br /></pre>При этом, в момент вызова WaitEvent выполнение скрипта должно прерваться с сохранением состояния, чтобы я дальше мог продолжить выполнение после WaitEvent. Как это предполагается использовать из Java (типа того):<br /><pre>Script script = Script.prepare (scriptSource);<br />ScriptYield yield = script.run ();<br />eventManager.addPendingScript (yield.getScriptState(), yield.getResult());</pre><br />а где-то позже, может быть очередное:<br /><pre>ScriptYield yield = scriptState.continue ();</pre><ad2></ad2><br />Я не много хочу? :)<br /><br /><b>Update:</b> Groovy не поддерживает cont-ions, jpython из-за отступов не получится никуда встроить толком, jruby не поддерживает (fiber в виде нитей не канает)... ищем дальше<br /><br /><b>Update:</b> По результатам подошел <a href="www.gradsoft.ua/products/termware_eng.html">TermWare</a>.Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-13415856905194186412008-06-26T22:08:00.001+03:002009-02-22T16:31:21.158+02:00Linux 2Ну вроде разобрался с <a href="http://rus.akshaal.info/2008/06/linux.html">проблемой зависания</a>. Это определенно не дрова видюхи - без них в консоли все равно висло. Это не перегрев - в винде все ok. Четыре зависших попыток компиляции ядра 2.6.25.9 под ядром 2.6.25.8 показали, что проблема воспроизводима легко. Под 2.6.24 удалось собрать 2.6.25.9. Но собрать под 2.6.25.9 ради теста саму 2.6.25.9 не получилось - висла. Скачал 2.6.26-rc8. Тестовая сборка под этим ядром прошла нормально. Проблемы с протоколом Samba исчезли (интересно, что scp как работала нормально, так и работает). Вроде жизнь наладилась. Всем спасибо за советы.<br /><ad></ad><br />Итого. 2.6.25.6, 2.6.25.7, 2.6.25.8, 2.6.25.9 - на моей машине (64 бита, AMD) оказались не рабочими.Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-77019407538518361432008-06-24T16:59:00.001+03:002009-02-22T16:31:02.912+02:00LinuxЗадалбывает меня это. Вот все было круто, не знал я печали, как начались глюки. С чем связано понять не могу, но сходу:<br /><br />Эпизодически, несколько раз в день система фризится напроч. Тоесть мышиный курсор не двигается, музыка не играет и т.д.<br />Если я в это время залогинен по VNC на зафризенную машину, то в момент этот, я могу максимум что сделать - запустить zsh и все. И то наверное потому, что он в кэш памяти сидит. Изображение прорисовывается по нажатию на кнопку. Например набрал su <ENTER>, но ничего не произошло. Нужно любую кнопку нажать, чтобы увидеть запрос на логин. Странное поведение. В логах ничего подозрительного нет. shutdown -r now не отрабатывает (программа вообще не похоже, что запускается).<br /><br />Если копирую что-то по smb протоколу, то в файле только первые несколько кил нормальные, а дальше нули идут. Тоесть mp3 музыка играет пару сек и затыкается, если играть smb share'ы. При этом с другого компа доступ к томуже smb ресурсу работает без проблем и глюков. Если копируют mp3'ки мне на шару, то играет все нормально.<ad></ad><br />Даже не знаю что думать. Куллеры крутятся.. все как обычно. Винда на компе работает нормально, в т.ч. и Crysis. Ядро 2.6.25.(от 6 до 8). Никому ничего не напоминает?Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-56443822850408751582008-06-20T16:39:00.001+03:002009-02-22T16:30:36.899+02:00Документация AspectJЕсли кто-то как и я документацию читает только по-необходимости, то так же как и я может попасть в ловушку при чтении доки на AspectJ. Например глядя на <a href="http://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html#configuring-load-time-weaving-with-aopxml-files">пример</a>:<pre><br /> <include within="javax.*"/><br /> <include within="org.aspectj.*"/><br /></pre><br />может показаться, что аспекты будут применены ко всему, что внутри javax.*, на самом деле это не так ибо, аспекты будут примены только к тем классам, что в javax ни больше ни меньше. Т.е. к классам внутри javax.security это не относится. А если учесть, что непосредственно в javax пакете вообще нет ни одного класса (или есть, но я по памяти не помню), то пример явно идиотский. Короче, должно там быть на самом деле "javax..*"...<ad2></ad2>Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0tag:blogger.com,1999:blog-6827363280592532957.post-49431107635304533212008-06-03T23:27:00.001+03:002009-02-22T16:30:04.069+02:00GWT (1.5)Очень странные ощущения. После возни с AJAX'овыми вещами с помощью JavaScript'а, писать клиентский код на Java просто сказка. Если скомпилировалось - значит заработает. Но компилируется не всегда - к этому надо просто привынуть. Например GWT.create принимает в качестве аргумента Class, но не тут-то было. Например код:<br />Class c = MyService.class; // или получено из аргумента<br />MyServiceAsync svc = GWT.create (c);<br />Прервет компиляцию с сообщением "Only class literals may be used as arguments to GWT.create()".<br /><br />Подход к package structure, который вроде как strongly recomended, весьма странен с моей точки зрения. Ну сабпакаджи client и server весьма логичны, но вот сабпакадж public - сомнителен (в него предполагается ложить ресурсы как я понял(?)). Более того, client классы не могут зависить от server классов. Т.е. нельзя ложить интерфейс сервиса в package server - его нужно ложить в пакадж client, а в server'е должна лежать только имплементация этого сервиса. Получается по сути таже tier based архитектура, но только наоборот. В классической (ну той к которой я привык), клиентская часть знает о серверной, но не наоборот. А в GWT'ной модели, как раз наоборот получается: серверная часть пользуется классами из клиентской (наследует интефрейсы которые хочет видеть клиент). Собственно это при желании можно обойти сделав отдельный GWT-Module для какого-то отдельно пакаджа с общими классами.. но это ж делать надо и против recomended structure. Впрочем я с первого же дня забил на эту структуру и разложил все по своему - главное понимать, что делаешь и тогда все работает.<br /><ad></ad><br />Сервисы-сервлеты в Module.gwt.xml не прописываю. Заюзал spring-mvc: прописал в web.xml его диспатчер, а в app context'е прописываю сами сервисы-контроллеры (для spring-mvc они контроллеры, а для gwt они сервлеты, а для клиента они сервисы). Получилось красиво весьма. Пока нравится.<br /><br />По уровню boilerplate'ности напоминает EJB 2.1.Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com0