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
next prev parent 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).