unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: Alex Ott <alexott@gmail.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 9624@debbugs.gnu.org
Subject: bug#9624: 24.0.90; Emacs crashes in BIDI code when trying to open Muse file
Date: Wed, 28 Sep 2011 09:48:23 +0200	[thread overview]
Message-ID: <CALV1_=LG5B=dkSSSypgooUKuwKra27nDHHzpiaOhuieUw+iung@mail.gmail.com> (raw)
In-Reply-To: <83ipodf752.fsf@gnu.org>

[-- Attachment #1: Type: text/plain, Size: 701 bytes --]

Examples are attached

On Wed, Sep 28, 2011 at 9:39 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>> Date: Wed, 28 Sep 2011 09:02:07 +0200
>> From: Alex Ott <alexott@gmail.com>
>>
>> Emacs crashes when I trying to open any "complex" .muse file with some
>> markup inside -- I tried to open files in English & Russian and in both
>> cases Emacs crashes in BIDI code.  I can provide example files if needed.
>
> Please do provide example files.  Even if I can intuit the cause of
> the problem, I need the examples to verify the fix.
>
> Thanks.
>



-- 
With best wishes,                    Alex Ott
http://alexott.net/
Tiwtter: alexott_en (English), alexott (Russian)
Skype: alex.ott

[-- Attachment #2: index.html --]
[-- Type: text/html, Size: 6654 bytes --]

[-- Attachment #3: ClojureProtocols.muse --]
[-- Type: application/octet-stream, Size: 27588 bytes --]

#title Типы и протоколы в Clojure
#keywords clojure, кложура, datatype, protocol, defprotocol, reify

Одно из самых больших изменений в Clojure версии 1.2 -- введение в язык новых артефактов:
протоколов (protocols) и типов данных (datatypes).  Данные изменения позволяют улучшить
производительность программ по сравнению с мультиметодами, что в будущем даст возможность
написать Clojure на Clojure (в данный момент протоколы и типы данных уже активно
используются при реализации Clojure).

<contents>

* Что это такое и зачем нужно?

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

Существует несколько причин введения протоколов и типов данных в новую версию языка:
 - Увеличить скорость работы полиморфных функций, при этом поддерживая большую часть
   функциональности мультиметодов, поскольку для протоколов диспатчеризация выполняется
   только по типу данных;
 - Использовать лучшие стороны интерфейсов (только спецификация функций, без реализации,
   реализация нескольких интерфейсов одним типом), в тоже время избегая недостатков
   (список реализуемых интерфейсов задан во время реализации типа данных, создание
   иерархии типов вида =isa/instanceof=);
 - Избежать [[http://en.wikipedia.org/wiki/Expression_Problem][Expression problem]] и дать возможность расширять набор операций над типами
   данных без изменениях определения типов данных (в том числе и чужих) и перекомпиляции
   исходного кода[1];
 - Использовать высокоуровневые абстракции для типов данных и операций над ними[2], что
   упрощает проектирование программ.

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

В отличии от имеющегося в Clojure =gen-interface= (и соответствующих =proxy/gen-class=)
определение протоколов и типов не требует AOT (ahead-of-time) компиляции исходного кода,
что упрощает распространение программ на Clojure.  Однако при определении протокола,
Clojure автоматически создает соответствующий интерфейс, который будет доступен для кода,
написанного на Java.

Типы данных, определенные с помощью =deftype= или =defrecord= позволяют программисту на
Clojure определять свои структуры данных, вместо использования обычных отображений и
структур, но об этом [[#datatypes][ниже]].

*Важно помнить, что протоколы и типы данных с одним и тем же именем могут быть определены в
разных пространствах имен, так что стоит быть осторожным и не наделать ошибок при импорте
определений и последующей реализации протоколов!*

* Определение протоколов

Протоколом называется именованный набор функций с определенными сигнатурами.  Для
определения используется макрос, применение которого выглядит следующим образом:
<src lang="clojure">
(defprotocol название "описание" & сигнатуры)
</src>

=название= -- единственный обязательный параметр, хотя определение протокола без функций не
имеет особого смысла.  В описании вы можете описать ваш протокол, и это описание будет
показываться при вызове функции =doc= для вашего протокола.  Для протокола вы можете указать
одну или несколько сигнатур функций, где каждая сигнатура выглядит следующим образом:
<src lang="clojure">
(имя [аргументы+]+ "описание")
</src>

Вы можете определять одну функцию, которая будет принимать различное количество
параметров, но первым аргументом функции всегда является объект, на основании которого
будет выполняться диспатчеризация, и к которому эта функция будет применяться.  Вы можете
рассматривать его как =this= в Java и C++.  В дополнение к сигнатурам, вы можете описать
вашу функцию, но это необязательно.

Давайте посмотрим на стандартный пример:
<src lang="clojure">
(defprotocol AProtocol
  "A doc string for AProtocol abstraction"
  (bar [a b] "bar docs")
  (baz [a] [a b] [a b c] "baz docs"))
</src>

Данный протокол определяет две функции: =bar= -- с двумя параметрами, и =baz= -- с одним,
двумя или тремя параметрами.

=defprotocol= также создаст соответствующий интерфейс, с тем же самым именем что и протокол.
Данный интерфейс будет иметь те же самые функции, что и протокол.

* Реализация протоколов

Протокол сам по себе ни на что не влияет -- чтобы использовать его, мы должны добавить его
специализации для типов данных или классов JVM.  Для этого может использоваться функция
=extend=, использование которой выглядит следующим образом:
<src lang="clojure">
(extend тип-или-класс
  протокол-1
   {:метод-1 уже-определенная-функция
    :метод-2 (fn [a b] ...)
    :метод-3 (fn ([a]...) ([a b] ...)...)}
  протокол-2
    {...}
...)
</src>

Для этой функции вы указываете имя типа данных или класса (или =nil=), и передаете список
состоящий из названий протоколов (=протокол-1= и т.д.) и отображений, которые связывают
функции протокола (=метод-1= и т.д.) с их реализациями -- анонимными или именованными
функциями.

Стоит отметить, что функция =extend= является низкоуровневым инструментом реализации
протоколов.  Кроме этого, в состав языка введены макросы =extend-protocol= & =extend-type=,
которые немного упрощают реализацию протоколов[4].  Протокол также может быть реализован
непосредственно при [[#datatypes][объявлении типа данных]].

Использование =extend-type= выглядит практически также как и использование =extend=, но
пользователь записывает реализации в более удобном виде (=extend-type= раскрывается в
соответствующий вызов =extend=):
<src lang="clojure">
(extend-type тип-или-класс
  протокол-1
    (метод-2 [a b] ...)
    (метод-3 ([a]...) 
             ([a b] ...)...)
  протокол-2
    (....)
...)
</src>

Макрос =extend-protocol= использоваться в тех случаях, если вы хотите реализовать один
протокол для нескольких типов данных или классов.  В общем виде использование
=extend-protocol= выглядит следующим образом:
<src lang="clojure">
(extend-protocol название-протокола
  Тип-или-Класс-1
   (метод-1 ...)
   (метод-2 ...)
  Тип-или-Класс-2
   (метод-1 ...)
   (метод-2 ...)
...)
</src>

При использовании, =extend-protocol= раскрывается в серию вызовов =extend-type= для каждого из
используемых типов.

Давайте рассмотрим небольшой пример.  Пусть мы объявим следующий простой протокол:
<src lang="clojure">
(defprotocol Hello "Test of protocol"
  (hello [this] "hello function"))
</src>

Мы можем использовать =extend=, =extend-protocol=, или =extend-type= для его специализации для
класса =String=:
<src lang="clojure">
(extend String
  Hello
  {:hello (fn [this] (str "Hello " this "!"))})

(extend-protocol Hello String
                 (hello [this] (str "Hello " this "!")))

(extend-type String Hello
             (hello [this] (str "Hello " this "!")))
</src>

При использовании любой из этих реализаций для объекта класса =String= мы получим один и тот
же ответ:
<src lang="clojure">
user> (hello "world")
"Hello world!"
</src>

Стоит отметить, что если вы не реализовали протокол для определенного типа данных, то при
вызове функции будет сгенерировано исключение.  В том случае, если вам необходима
"реализация по умолчанию", то вы можете специализировать протокол для класса =Object=.

#datatypes
* Определение типов данных

В Clojure 1.2 введены два метода определения новых именованных типов данных (=deftype= и
=defrecord=), которые реализуют абстракции, определенные протоколами и/или интерфейсами (к
типам данных относится также =reify=, который описан ниже).

=deftype= и =defrecord= динамически создают именованный класс, который имеет набор заданных
полей и (необязательно) методов для одного или нескольких протоколов и/или интерфейсов.
Поскольку они не требуют явной компиляции, то это дает возможность их использования в
интерактивной разработке.  С точки зрения разработчика =deftype= и =defrecord= похожи на
=defstruct=, но во многом они отличаются:
 - они создают уникальный класс с соответствующими полями;
 - созданный класс имеет конкретный тип;
 - имеется конструктор;
 - для полей можно указывать типы (это будет использоваться для оптимизации и ограничения
   типов в конструкторе).

=deftype= является "базовым" инструментом для определения типов данных -- созданный тип
имеет только конструктор, и ничего больше -- все остальное должен реализовывать
разработчик.  Но при этом, =deftype= может иметь изменяемые поля, чего не имеет =defrecord=.

В отличии от =deftype=, =defrecord= более прост в использовании, поскольку создаваемый тип
данных имеет большую функциональность (по большей части за счет реализации интерфейсов
=IKeywordLookup=, =IPersistentMap=, =Serializable= и т.д.):
 - автоматически генерируемые функции =hashCode= и =equals=;
 - возможность указания мета-информации;
 - доступ к полям с помощью ключевых символов;
 - вы можете добавлять поля, не указанные в определении.

=deftype= и =defrecord= обычно имеют разные области применения: =deftype= в основном
используется для "системных" вещей -- коллекций, и т.п., тогда как =defrecord= в основном
используется для хранения информации из "проблемной области" -- данных о заказчиках,
записях в БД и т.п. -- то, для чего использовались отображения в версиях 1.0 и 1.1.

Давайте рассмотрим как использовать конкретные средства для создания типов данных.

** deftype & defrecord

В общей форме использование макросов =deftype= и =defrecord= выглядит следующим образом:
<src>
(deftype имя [& поля] & спецификации)
(defrecord имя [& поля] & спецификации)
</src>

Для обоих макросов обязательным параметром является лишь имя, которое становится именем
класса.  Поля, которые станут членами класса, перечисляются в векторе, следующем за
именем, и могут содержать объявления типов.  После этого вектора, можно указать список
реализуемых интерфейсов и протоколов, вместе с реализацией (это не обязательно, поскольку
для этого вы позже можете использовать =extend-protocol= & =extend-type=).

Спецификации протоколов/интерфейсов выглядят следующим образом:
<src>
протокол/интерфейс
(название-метода [аргументы*] реализация)*
</src>

Вы можете указать любое количество протоколов/интерфейсов, которые будут реализованы
данным типом данных.  Давайте посмотрим на простейший тип данных, который реализует
протокол =Hello=:
<src lang="clojure">
(deftype A []
  Hello
  (hello [this] (str "Hello A!")))
</src>

Мы можем вызвать функцию =hello= для нашего объекта, и получим следующий вывод:
<src lang="clojure">
user> (hello (A.))
"Hello A!"
</src>

Мы можем также создать тип с помощью =defrecord=:
<src lang="clojure">
(defrecord B [name]
  Hello
  (hello [this] (str "Hello " name "!")))
</src>

и вызвать метод =hello= для этого типа:
<src lang="clojure">
user> (hello (B. "world"))
"Hello world!"
</src>

Как уже отмечалось выше, создаваемые поля по умолчанию являются неизменяемыми, но если вы
создаете тип с помощью =deftype=, то вы можете пометить некоторые поля как изменяемые,
используя метаданные (с помощью ключевого символа =:volatile-mutable= или
=:unsynchronized-mutable=).  Для таких полей вы сможете использовать оператор =(set! afield
aval)= для изменения данных.  Давайте посмотрим как это делается на примере -- если мы
создадим следующий протокол и тип данных:
<src lang="clojure">
(defprotocol Setter
  (set-name [this new-name]))
(deftype AM [^{:volatile-mutable true} mfield]
  Hello
  (hello [this] (str "Hello " mfield "!"))
  Setter
  (set-name [this new-name] (set! mfield new-name)))
</src>
то мы сможем изменять значение поля:
<src>
user> (def am (AM. "world"))
#'user/am
user> (hello am)
"Hello world!"
user> (set-name am "peace")
"peace"
user> (hello am)
"Hello peace!"
</src>

** reify

=reify= используется тогда, когда вам нужно реализовать протокол или интерфейс только в
одном месте -- когда вы используете =reify= вы одновременно объявляете тип, и сразу создаете
объект этого типа. Функция =reify= по своему использованию очень похожа на =proxy=, но с
некоторыми исключениями:
 - можно использовать только для интерфейсов и протоколов;
 - реализуемые методы являются методами результирующего класса, и они вызываются напрямую,
   без поиска в отображении, но при этом не поддерживается подмена методов в отображении.

Эти отличия позволяют получить более высокую производительность по сравнению с =proxy=, и
при создании и при выполнении.

Вот небольшой пример реализации протокола =Hello= для конкретного объекта:
<src lang="clojure">
(def int-reify (reify Hello
                 (hello [this] "Hello integer!")))
</src>

И при вызове =hello= для этого объекта, мы получим соответствующий результат:
<src lang="clojure">
user> (hello int-reify)
"Hello integer!"
</src>

* Дополнительные функции и макросы

Для работы с протоколами и типами данных определено некоторое количество вспомогательных
функций, которые могут вам понадобиться:
 =extends?= :: возвращает =true= если данный тип данных (2-й аргумент) реализует интерфейс,
   заданный первым аргументом;
 =extenders= :: возвращает коллекцию типов, реализующих заданный протокол;
 =satisfies?= :: возвращает =true= если данный протокол (1-й аргумент) применим к данному
   объекту (2-й аргумент);

* Дополнительная информация

Как всегда, основной источник информации -- сайт языка: ознакомьтесь с разделами [[http://clojure.org/protocols][protocols]]
и [[http://clojure.org/datatypes][datatypes]].  Хорошее описание протоколов и типов данных можно найти в 13-й главе недавно
вышедшей книги [[http://www.amazon.com/gp/product/1430272317?ie=UTF8&tag=aleottshompag-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=1430272317][Practical Clojure. The Definitive Guide]], а также в [[http://www.amazon.com/gp/product/1935182595?ie=UTF8&tag=aleottshompag-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=1935182595][Clojure in Action]] и
[[http://www.amazon.com/gp/product/1935182641?ie=UTF8&tag=aleottshompag-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=1935182641][The Joy of Clojure. Thinking the Clojure Way]], которые будут выпущены в ближайшее время.

Stuart Halloway создал очень [[http://vimeo.com/11236603][интересный скринкаст]] в котором он рассказывает о том, зачем
были созданы протоколы и data types, и демонстрирует их применение на небольших примерах.

Введение новых возможностей в язык не обходится без статей в блогах.  Вот ссылки на
некоторые интересные статьи на эту тему:
 - [[http://www.ibm.com/developerworks/java/library/j-clojure-protocols/index.html][Статья Stuart Sierra]] на IBM developerWorks;
 - [[http://www.infoq.com/presentations/Clojure-Expression-Problem][Clojure's Solutions to the Expression Problem]] -- выступление Chris Houser на
   конференции Strange Loop 2010;
 - Серия из 3-х статей ([[http://www.objectcommando.com/blog/2010/03/26/clojure-protocols-part-1/][1]], [[http://www.objectcommando.com/blog/2010/03/29/clojure-protocols-part-2/][2]], [[http://www.objectcommando.com/blog/2010/04/12/clojure-protocols-part-3/][3]]) о протоколах в блоге Object Commando;
 - [[http://freegeek.in/blog/2010/05/clojure-protocols-datatypes-a-sneak-peek/][Краткое введение]] в протоколы, написанное Baishampayan Ghose;
 - [[http://formpluslogic.blogspot.com/2010/08/clojure-protocols-and-expression.html][Clojure Protocols and Expression Problem]];
 - Два постинга от Sean Devlin -- [[http://fulldisclojure.blogspot.com/2010/08/thoughts-on-protocols.html][Protocol Implementation Awesomeness]] и
   [[http://fulldisclojure.blogspot.com/2010/08/partially-implemented-protocols.html][Partially Implemented Protocols]];
 - [[http://blog.higher-order.net/2010/05/05/circuitbreaker-clojure-1-2/][Пример реализации паттерна "Circuit Breaker"]] с помощью протоколов;
 - [[http://kotka.de/blog/2010/03/memoize_done_right.html#protocols][Пример использования протоколов]] для мемоизации;
 - [[http://bestinclass.dk/index.clj/2010/04/prototurtle-the-tale-of-the-bleeding-turtle.html][Еще пример]] использования протоколов для реализации turtle graphics;
 - [[http://kirindave.tumblr.com/post/658770511/monkey-patching-gorilla-engineering-protocols-in][Небольшое сравнение]] Monkey Patching в Ruby с протоколами в Clojure;
 - Пример реализации стека с помощью протоколов ([[http://www.viksit.com/content/stack-implementation-clojure-using-protocols-and-records][1]], [[http://www.viksit.com/content/stack-implementation-clojure-ii-functional-approach][2]]), написанный Viksit Gaur;

 

; ================================================================================

; - http://gist.github.com/306174 - пример использования протоколов, написанный Rich Hickey 
; - http://gist.github.com/420036
; - http://groups.google.com/group/clojure/browse_thread/thread/b8620db0b7424712
; - http://fogus.me/static/preso/clj1.2+/
; - http://www.infoq.com/presentations/Clojure-Expression-Problem - video at StrangeLoop
; - http://debasishg.blogspot.com/2010/08/random-thoughts-on-clojure-protocols.html

;  LocalWords:  defprotocol reify datatype protocols datatypes data types AOT defrecord
;  LocalWords:  Halloway Stuart injection patching monkey gen-interface proxy deftype

Footnotes: 
[1] Стоит однако отметить, что протоколы не реализуют monkey patching и внедрение методов
    (injection) в существующие типы данных.

[2] Возможность реализации абстракций на Clojure и высокая скорость работы протоколов
    позволит в будущем написать Clojure на самой Clojure, без использования исходного кода
    на Java.

[3] Люди знакомые с Haskell могут рассматривать протоколы как некоторое подобие типов
    классов (typeclasses) в этом языке, правда при этом нельзя определять реализации по
    умолчанию для методов.

[4] Но =extend= может использоваться в тех случаях, когда вы хотите использовать одни и те
    же реализации для разных типов данных -- в этом случае, вы можете создать отображение
    с нужными функциями, и использовать его для разных типов, например, как описано в
    [[http://fulldisclojure.blogspot.com/2010/08/thoughts-on-protocols.html][следующем блог-постинге]].

;  LocalWords:  gen-class ahead-of-time instanceof extend-type extend-protocol Object
;  LocalWords:  String this extend

  reply	other threads:[~2011-09-28  7:48 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-09-28  7:02 bug#9624: 24.0.90; Emacs crashes in BIDI code when trying to open Muse file Alex Ott
2011-09-28  7:39 ` Eli Zaretskii
2011-09-28  7:48   ` Alex Ott [this message]
2011-09-28  7:48   ` Alex Ott
2011-09-28  8:54     ` Eli Zaretskii
2011-09-28 10:20       ` Alex Ott
2011-09-28 10:35         ` Eli Zaretskii
2011-09-28 14:38           ` Eli Zaretskii
2011-09-28 14:56             ` Eli Zaretskii

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CALV1_=LG5B=dkSSSypgooUKuwKra27nDHHzpiaOhuieUw+iung@mail.gmail.com' \
    --to=alexott@gmail.com \
    --cc=9624@debbugs.gnu.org \
    --cc=eliz@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).