<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1973090003148579772</id><updated>2012-02-17T12:30:25.939+11:00</updated><category term='concurrent'/><category term='scheme'/><category term='fprog'/><category term='code-golf'/><category term='хаскель'/><category term='моноиды'/><category term='erlang'/><category term='software'/><category term='функциональное программирование'/><category term='haskell'/><category term='functional'/><category term='programming'/><category term='development'/><category term='bert'/><category term='перевод'/><category term='учебник'/><category term='concurrency'/><category term='программирование'/><category term='functional-programming'/><category term='lyah'/><title type='text'>Yasir Arsanukaev :: programming blog</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://arsanukaev.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://arsanukaev.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Yasir Arsanukaev</name><uri>https://profiles.google.com/103729008772345894369</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>5</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1973090003148579772.post-810932166491020208</id><published>2011-09-17T00:20:00.000+10:00</published><updated>2011-09-18T22:09:28.988+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lyah'/><category scheme='http://www.blogger.com/atom/ns#' term='программирование'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='хаскель'/><category scheme='http://www.blogger.com/atom/ns#' term='учебник'/><category scheme='http://www.blogger.com/atom/ns#' term='функциональное программирование'/><category scheme='http://www.blogger.com/atom/ns#' term='перевод'/><category scheme='http://www.blogger.com/atom/ns#' term='моноиды'/><title type='text'></title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;&lt;h1&gt;Изучай Хаскель ради добра! Моноиды&lt;/h1&gt;Вводное инфо на Хабре:&amp;nbsp;&lt;a href="http://habrahabr.ru/blogs/Haskell/128586/"&gt;http://habrahabr.ru/blogs/Haskell/128586/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;В этой главе представлен еще один полезный и веселый класс типов: &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;. Этот класс типов существует для типов, чьи значения могут быть объединены вместе с помощью бинарной операции. Мы рассмотрим, что именно представляют из себя моноиды и что утверждают их законы. Затем мы рассмотрим некоторые моноиды в Хаскеле и то, как они могут быть полезны.&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;Во-первых, давайте взглянем на ключевое слово &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, потому что мы будем часто его использовать, когда мы углубимся в удивительный мир моноидов.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Оборачивание существующего типа в новый тип&lt;/h2&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://s3.amazonaws.com/lyah/maoi.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://s3.amazonaws.com/lyah/maoi.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Пока что вы научились создавать свои алгебраические типы данных, используя ключевое слово &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;. Вы также увидели, как можно давать синонимы имеющимся типам, с применением ключевого слова &lt;code class="prettyprint lang-hs"&gt;type&lt;/code&gt;. В этом разделе мы рассмотрим, как создаются новые типы на основе имеющихся типов данных, используя ключевое слово &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;. Мы также, в первую очередь, поговорим о том, зачем бы нам это было нужно.&lt;br /&gt;&lt;br /&gt;В главе 11 вы увидели пару способов для спискового типа быть аппликативным функтором. Один из них состоит в том, чтобы заставить &lt;code class="prettyprint lang-hs"&gt;&amp;lt;*&amp;gt;&lt;/code&gt; брать каждую функцию из списка, являющегося ее левым параметром, и применяет ее к каждому значению в списке, который находится справа, что в результате возвращает все возможные комбинации применения функции из левого списка к значению в правом списке:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; [(+1),(*100),(*5)] &amp;lt;*&amp;gt; [1,2,3]&lt;br /&gt;[2,3,4,100,200,300,5,10,15]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Второй способ заключается в том, чтобы взять первую функции из списка слева от &lt;code class="prettyprint lang-hs"&gt;&amp;lt;*&amp;gt;&lt;/code&gt; и применить ее к первому значению справа, затем взять вторую функцию из списка слева и применить ее ко второму значению справа, и т. д. В конечном счете, это вроде застегивания двух списков вместе.&lt;br /&gt;&lt;br /&gt;Но списки уже являются экземпляром &lt;code class="prettyprint lang-hs"&gt;Applicative&lt;/code&gt;, поэтому как нам сделать списки также экземпляром &lt;code class="prettyprint lang-hs"&gt;Applicative&lt;/code&gt; вторым способом? Как вы узнали, для этой цели был введен тип &lt;code class="prettyprint lang-hs"&gt;ZipList a&lt;/code&gt;. Этот тип имеет один конструктор значения, &lt;code class="prettyprint lang-hs"&gt;ZipList&lt;/code&gt;, который имеет только одно поле. Мы помещаем оборачиваемый нами список в это поле. Далее &lt;code class="prettyprint lang-hs"&gt;ZipList&lt;/code&gt; делается экземпляром &lt;code class="prettyprint lang-hs"&gt;Applicative&lt;/code&gt;, чтобы когда нам нужно использовать списки в качестве аппликативных функторов для застегивания, мы просто оборачиваем их с помощью конструктора &lt;code class="prettyprint lang-hs"&gt;ZipList&lt;/code&gt;. Как только мы закончили, мы разворачивать их с помощью &lt;code class="prettyprint lang-hs"&gt;getZipList&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getZipList $ ZipList [(+1),(*100),(*5)] &amp;lt;*&amp;gt; ZipList [1,2,3] $&lt;br /&gt;[2,200,15]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Итак, какое отношение это имеет к ключевому слову &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;? Хорошо, подумайте, как бы мы могли написать объявление data для нашего типа &lt;code class="prettyprint lang-hs"&gt;ZipList a&lt;/code&gt;. Вот один из способов:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;data ZipList a = ZipList [a]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Это тип, который обладает лишь одним конструктором значения, и этот конструктор значения имеет только одно поле, которое является списком сущностей. Мы также могли бы использовать синтаксис записей, чтобы автоматически получать функцию, извлекающую список из &lt;code class="prettyprint lang-hs"&gt;ZipList&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;data ZipList a = ZipList { getZipList :: [a] }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Это прекрасно смотрится и на самом деле работает очень хорошо. У нас было два способа сделать существующий тип экземпляром класса типов, поэтому мы использовали ключевое слово &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;, чтобы просто обернуть этот тип в другой тип, и сделали другой тип экземпляром вторым способом.&lt;br /&gt;&lt;br /&gt;Ключевое слово newtype в Хаскеле создано специально для тех случаев, когда мы хотим просто взять один тип и обернуть его во что-либо, чтобы представить его как другой тип. В существующих сейчас библиотеках &lt;code class="prettyprint lang-hs"&gt;ZipList&lt;/code&gt; a определен вот так:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype ZipList a = ZipList { getZipList :: [a] }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Вместо ключевого слова &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; используется ключевое слово &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;. Теперь разберемся, почему. Ну, к примеру, &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; быстрее. Если вы используете ключевое слово &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; для оборачивания типа, появляются накладные расходы на все это оборачивание и разворачивание, когда ваша программа выполняется. Но если вы используете &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, Хаскель знает, что вы просто используете его для оборачивания существующего типа в новый тип (отсюда название), т. к. вы хотите чтобы внутренне он остался тем же, но имел иной тип. По этой причине Хаскель может избавиться от оборачивания и разворачивания как только он решит, какое значение какого типа.&lt;br /&gt;&lt;br /&gt;Так почему бы не использовать просто &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; вместо &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; всегда? Когда вы создаете новый тип из имеющегося типа, используя ключевое слово newtype, у вас может быть только один конструктор значения, и этот конструктор значения может иметь только одно поле. Но с помощью &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; вы можете создавать типы данных, которые имеют несколько конструкторов значения, и каждый конструктор может иметь ноль или более полей:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;data Profession = Fighter | Archer | Accountant&lt;br /&gt;&lt;br /&gt;data Race = Human | Elf | Orc | Goblin&lt;br /&gt;&lt;br /&gt;data PlayerCharacter = PlayerCharacter Race Profession&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;При использовании &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; мы также можем использовать ключевое слово &lt;code class="prettyprint lang-hs"&gt;deriving&lt;/code&gt;, так же, как мы делали бы это с &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;. Мы можем порождать экземпляры для &lt;code class="prettyprint lang-hs"&gt;Eq&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;Ord&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;Enum&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;Bounded&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;Show&lt;/code&gt;, и &lt;code class="prettyprint lang-hs"&gt;Read&lt;/code&gt;. Если мы породим экземпляр для класса типа, оборачиваемый нами тип уже должен быть в этом классе типов. Это логично, поскольку &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; всего лишь оборачивает существующий тип. Поэтому теперь если мы сделаем следующее, мы можем печатать и сравнивать значения нашего нового типа:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype CharList = CharList { getCharList :: [Char] } deriving (Eq, Show)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Давайте попробуем:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; CharList "this will be shown!"&lt;br /&gt;CharList {getCharList = "this will be shown!"}&lt;br /&gt;ghci&amp;gt; CharList "benny" == CharList "benny"&lt;br /&gt;True&lt;br /&gt;ghci&amp;gt; CharList "benny" == CharList "oisters"&lt;br /&gt;False&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;В данном конкретном случае использования &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; конструктор значения имеет следующий тип:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;CharList :: [Char] -&amp;gt; CharList&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Он берет значение типа &lt;code class="prettyprint lang-hs"&gt;[Char]&lt;/code&gt;, например &lt;code class="prettyprint lang-hs"&gt;"my sharona"&lt;/code&gt; и возвращает значение типа &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt;. Из предыдущих примеров, где мы использовали конструктор значения &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt;, видно, что действительно так оно и есть. И наоборот, функция &lt;code class="prettyprint lang-hs"&gt;getCharList&lt;/code&gt;, которая была генерирована за нас, потому как мы использовали синтаксис записей в нашем &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, имеет следующий тип:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;getCharList :: CharList -&amp;gt; [Char]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Она берет значение &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt; и преобразует его в значение &lt;code class="prettyprint lang-hs"&gt;[Char]&lt;/code&gt;. Вы можете воспринимать это как оборачивание и разворачивание, но вы также можете воспринимать это как преобразование значений из одного типа в другой.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Использование newtype для создания экземпляров классов типов&lt;/h3&gt;Часто мы хотим сделать наши типы экземплярами определенных классов типов, но параметры типа просто не соответствуют тому, что мы хотим сделать. Сделать &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt; экземпляром &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt; просто, потому что класс типов &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt; определен вот так:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;class Functor f where&lt;br /&gt;    fmap :: (a -&amp;gt; b) -&amp;gt; f a -&amp;gt; f b&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Поэтому мы просто начинаем с этого:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Functor Maybe where&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;А потом реализуем &lt;code class="prettyprint lang-hs"&gt;fmap&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Все параметры типа согласуются, потому что &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt; занимает место &lt;code class="prettyprint lang-hs"&gt;f&lt;/code&gt; в определении класса типов &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt;. Если взглянуть на &lt;code class="prettyprint lang-hs"&gt;fmap&lt;/code&gt;, как если бы она работала только с &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt;, в итоге она ведет себя вот так:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;fmap :: (a -&amp;gt; b) -&amp;gt; Maybe a -&amp;gt; Maybe b&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://s3.amazonaws.com/lyah/krakatoa.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="278" src="http://s3.amazonaws.com/lyah/krakatoa.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Разве это не замечательно? Теперь, что если мы захотели бы сделать кортеж экземпляром &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt; так, чтобы когда мы отражаем кортеж с помощью функции, используя &lt;code class="prettyprint lang-hs"&gt;fmap&lt;/code&gt;, она применяется к первому элементу кортежа? Таким образом, выполнение &lt;code class="prettyprint lang-hs"&gt;fmap (+3) (1,1)&lt;/code&gt; вернуло бы &lt;code class="prettyprint lang-hs"&gt;(4, 1)&lt;/code&gt;. Оказывается, что написание экземпляра для этого отчасти затруднительно. При использовании &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt; мы просто можем сказать &lt;code class="prettyprint lang-hs"&gt;instance Functor Maybe where,&lt;/code&gt; так как только конструкторы типа, принимающие ровно один параметр, могут быть сделаны экземплярами &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt;. Но, похоже, нет способа сделать что-либо подобное при использовании &lt;code class="prettyprint lang-hs"&gt;(a, b)&lt;/code&gt; так, чтобы в итоге изменялся только параметр &lt;code class="prettyprint lang-hs"&gt;a&lt;/code&gt;, когда мы используем &lt;code class="prettyprint lang-hs"&gt;fmap&lt;/code&gt;. Чтобы обойти эту проблему, мы можем сделать новый тип из нашего кортежа с помощью &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; так, чтобы второй параметр типа представлял тип первого компонента в кортеже:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype Pair b a = Pair { getPair :: (a, b) }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;А теперь мы можем сделать его экземпляром &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt; так, чтобы функция отображала первый компонент:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Functor (Pair c) where&lt;br /&gt;    fmap f (Pair (x, y)) = Pair (f x, y)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Как вы можете видеть, мы можем производить сопоставление типов, объявленных через &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, с образцом. Мы производим сопоставление, чтобы получить лежащий в основе кортеж, применяем функцию &lt;code class="prettyprint lang-hs"&gt;f&lt;/code&gt; к первому компоненту в кортеже, а потом используем конструктор значения &lt;code class="prettyprint lang-hs"&gt;Pair&lt;/code&gt;, чтобы преобразовать кортеж обратно в наш &lt;code class="prettyprint lang-hs"&gt;Pair b a&lt;/code&gt;. Если мы представим, какого типа была бы &lt;code class="prettyprint lang-hs"&gt;fmap&lt;/code&gt;, если бы она работала только с нашими новыми парами, она выглядела бы вот так:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;fmap :: (a -&amp;gt; b) -&amp;gt; Pair c a -&amp;gt; Pair c b&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Опять-таки, мы сказали &lt;code class="prettyprint lang-hs"&gt;instance Functor (Pair c) where&lt;/code&gt;, и поэтому &lt;code class="prettyprint lang-hs"&gt;Pair&lt;/code&gt; c заняло место &lt;code class="prettyprint lang-hs"&gt;f&lt;/code&gt; в определении класса типов для &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;class Functor f where&lt;br /&gt;    fmap :: (a -&amp;gt; b) -&amp;gt; f a -&amp;gt; f b&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Теперь, если мы преобразуем кортеж в &lt;code class="prettyprint lang-hs"&gt;Pair b a&lt;/code&gt;, мы можем использовать с ним &lt;code class="prettyprint lang-hs"&gt;fmap&lt;/code&gt;, и функция будет отображать первый компонент:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getPair $ fmap (*100) (Pair (2, 3))&lt;br /&gt;(200,3)&lt;br /&gt;ghci&amp;gt; getPair $ fmap reverse (Pair ("london calling", 3))&lt;br /&gt;("gnillac nodnol",3)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;О лености newtype&lt;/h3&gt;Единственное, что можно сделать с помощью &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, – это превратить имеющийся тип в новый тип, поэтому внутренне Хаскель может представлять значения типов, определенных с помощью &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, точно так же, как и первоначальные, зная в то же время, что их типы теперь различаются. Это означает, что &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; не только обычно быстрее, чем &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;, его механизм сопоставления с образцом ленивее. Давайте посмотрим, что это значит.&lt;br /&gt;&lt;br /&gt;Как вы знаете, Хаскель по умолчанию ленив, что означает, что какие-либо вычисления будут иметь место только тогда, когда мы пытаемся фактически напечатать результаты выполнения наших функций. Более того, будут произведены только те вычисления, которые необходимы для того, чтобы наша функция вернула нам результаты. Значение &lt;code class="prettyprint lang-hs"&gt;undefined&lt;/code&gt; в Хаскеле представляет ошибочное вычисление. Если мы попытаемся его вычислить (т. е. заставить Хаскель на самом деле произвести вычисление), напечатав его на экране, Хаскель разразится детским приступом гнева (технически это называют исключением):&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; undefined&lt;br /&gt;*** Exception: Prelude.undefined&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Однако если мы создадим список, содержащий в себе несколько значений &lt;code class="prettyprint lang-hs"&gt;undefined&lt;/code&gt;, но запросим только голову списка, которая не равна &lt;code class="prettyprint lang-hs"&gt;undefined&lt;/code&gt;, все пройдет гладко. Это потому что Хаскелю не нужно вычислять какие-либо из остальных элементов в списке если мы хотим посмотреть только первый элемент. Вот пример:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; head [3,4,5,undefined,2,undefined]&lt;br /&gt;3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Теперь рассмотрите следующий тип:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;data CoolBool = CoolBool { getCoolBool :: Bool }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Это ваш обыкновенный алгебраический тип данных, который был объявлен с использованием ключевого слова &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;. Он имеет один конструктор значения, который содержит одно поле, чей тип &lt;code class="prettyprint lang-hs"&gt;Bool&lt;/code&gt;. Давайте создадим функцию, которая сопоставляет с образцом значение &lt;code class="prettyprint lang-hs"&gt;CoolBool&lt;/code&gt; и возвращает значение &lt;code class="prettyprint lang-hs"&gt;"hello"&lt;/code&gt; вне зависимости от того, было ли значение &lt;code class="prettyprint lang-hs"&gt;Bool&lt;/code&gt; в &lt;code class="prettyprint lang-hs"&gt;CoolBool&lt;/code&gt; равно &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt; или &lt;code class="prettyprint lang-hs"&gt;False&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;helloMe :: CoolBool -&amp;gt; String&lt;br /&gt;helloMe (CoolBool _) = "hello"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Вместо применения этой функции к обычному &lt;code class="prettyprint lang-hs"&gt;CoolBool&lt;/code&gt;, давайте сделаем ей обманный бросок, и применим ее к &lt;code class="prettyprint lang-hs"&gt;undefined&lt;/code&gt;!&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; helloMe undefined&lt;br /&gt;"*** Exception: Prelude.undefined&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Чёрт! Исключение! Почему возникло это исключение? Типы, определенные с помощью ключевого слова &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;, могут иметь много конструкторов значения (хотя &lt;code class="prettyprint lang-hs"&gt;CoolBool&lt;/code&gt; имеет только один конструктор). Поэтому для того чтобы понять, согласуется ли значение, переданное нашей функции, с образцом &lt;code class="prettyprint lang-hs"&gt;(CoolBool _)&lt;/code&gt;, Хаскель должен вычислить значение ровно настолько, чтобы понять, какой конструктор значения был использован, когда мы создавали значение. И когда мы пытаемся вычислить значение &lt;code class="prettyprint lang-hs"&gt;undefined&lt;/code&gt;, будь оно даже небольшим, возникает исключение.&lt;br /&gt;&lt;br /&gt;Вместо использования ключевого слова &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; для &lt;code class="prettyprint lang-hs"&gt;CoolBool&lt;/code&gt;, давайте попробуем использовать &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype CoolBool = CoolBool { getCoolBool :: Bool }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Нам не нужно изменять нашу функцию &lt;code class="prettyprint lang-hs"&gt;helloMe&lt;/code&gt;, поскольку синтаксис сопоставления с образцом одинаков независимо от того, использовалось ли &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; или &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; для объявления вашего типа. Давайте сделаем здесь то же самое и применим &lt;code class="prettyprint lang-hs"&gt;helloMe&lt;/code&gt; к значению &lt;code class="prettyprint lang-hs"&gt;undefined&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; helloMe undefined&lt;br /&gt;"hello"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Сработало! Хммм, почему? Ну, как вы уже узнали, когда вы используете &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, Хаскель внутренне может представлять значения нового типа таким же образом, как и первоначальные значения. Ему не нужно помещать их еще в одну коробку; он просто должен быть в курсе, что значения имеют разные типы. И поскольку Хаскель знает, что типы, созданные с помощью ключевого слова &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, могут иметь лишь один конструктор, ему не нужно вычислять значение, переданное функции, чтобы быть убедиться, что значение соответствует образцу &lt;code class="prettyprint lang-hs"&gt;(CoolBool _)&lt;/code&gt;, потому что типы, созданные с помощью &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, могут иметь только один возможный конструктор значения и одно поле!&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://s3.amazonaws.com/lyah/shamrock.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://s3.amazonaws.com/lyah/shamrock.png" /&gt;&lt;/a&gt;&lt;/div&gt;Это различие в поведении может казаться незначительным, но на самом деле оно очень важно. Оно показывает, что хотя типы, определенные с помощью &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, ведут себя одинаково с точки зрения программиста (т. к. оба имеют конструкторы значения и поля), это фактически два различных механизма. Тогда как &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; может использовать для создания ваших новых типов с нуля, &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; предназначен просто для создания совершенно нового типа из существующего типа. Сравнение значений &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; с образцом не похоже на вынимание чего-то из коробки (что характерно для &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;), это скорее представляет собой прямое преобразование из одного типа в другой.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;type против newtype против data&lt;/h3&gt;К этому моменту вы могли запутаться относительно разницы между &lt;code class="prettyprint lang-hs"&gt;type&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, поэтому давайте повторим пройденный материал об их использовании.&lt;br /&gt;&lt;br /&gt;Ключевое слово &lt;code class="prettyprint lang-hs"&gt;type&lt;/code&gt; предназначено для создания синонимов типов. Мы просто даем другое имя уже существующему типу, чтобы на этот тип было проще сослаться. Скажем, мы сделали следующее:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;type IntList = [Int]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Все, что оно делает, – это дает нам возможность сослаться на тип &lt;code class="prettyprint lang-hs"&gt;[Int]&lt;/code&gt; как &lt;code class="prettyprint lang-hs"&gt;IntList&lt;/code&gt;. Их можно использовать взаимозаменяемо. Мы не получаем конструктор значения &lt;code class="prettyprint lang-hs"&gt;IntList&lt;/code&gt; или что-либо в этом роде. Поскольку &lt;code class="prettyprint lang-hs"&gt;[Int]&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;IntList&lt;/code&gt; являются лишь двумя способами сослаться на один и тот же тип, не важно, какое имя мы используем в наших аннотациях типов:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; ([1,2,3] :: IntList) ++ ([1,2,3] :: [Int])&lt;br /&gt;[1,2,3,1,2,3]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Мы используем синонимы типов, когда хотим сделать наши сигнатуры типов более наглядными. Мы даем типам имена, которые говорят нам что-либо об их предназначении в контексте функций, где они используются. Например, когда мы использовали ассоциативный список типа &lt;code class="prettyprint lang-hs"&gt;[(String,String)]&lt;/code&gt; для представления телефонной книги в главе 7, мы дали ему синоним типа &lt;code class="prettyprint lang-hs"&gt;PhoneBook&lt;/code&gt;, чтобы сигнатуры типов наших функций были легко читаемыми.&lt;br /&gt;&lt;br /&gt;Ключевое слово &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; предназначено для оборачивания существующих типов в новые типы, в основном чтобы их можно было проще сделать экземплярами определенных классов типов. Когда мы используем &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; для оборачивания существующего типа, получаемый нами тип отделен от исходного типа. Предположим, мы создаем следующий &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype CharList = CharList { getCharList :: [Char] }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Мы не можем использовать &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt;, чтобы соединить вместе &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt; и список типа &lt;code class="prettyprint lang-hs"&gt;[Char]&lt;/code&gt;. Мы даже не можем использовать &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt;, чтобы соединить два значения &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt;, потому что &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt; работает только со списками, а тип &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt; не является списком, хотя можно сказать, что &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt; содержит список. Мы можем, однако, преобразовать два значения &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt; в списки, соединить их с помощью &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt;, а затем преобразовать получившееся обратно в &lt;code class="prettyprint lang-hs"&gt;CharList&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Когда в наших объявлениях &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; мы используем синтаксис записей, мы получаем функции для преобразования между новым типом и изначальным типом – а именно конструктор значения нашего &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; и функцию для извлечения значения из его поля. Новый тип также не делается автоматически экземпляром классов типов, к которым принадлежит исходный тип, поэтому нам необходимо породить (ключевое слово &lt;code class="prettyprint lang-hs"&gt;deriving&lt;/code&gt;), либо записать его вручную.&lt;br /&gt;&lt;br /&gt;На деле, вы можете воспринимать объявления &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; как объявления &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;, которые могут иметь только один конструктор и одно поле. Если вы поймаете себя на написании такого объявления, рассмотрите использование &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Ключевое слово &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt; предназначено для создания ваших собственных типов данных. Вы можете войти с ними в раж. Они могут иметь столько конструкторов и полей, сколько вы пожелаете, и могут использоваться для реализации любого алгебраического типа данных – всего, начиная со списков и &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt;-подобных типов, заканчивая деревьями.&lt;br /&gt;&lt;br /&gt;Подводя итог вышесказанному, используйте ключевые слова следующим образом:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Если вы просто хотите, чтобы ваши сигнатуры типов выглядели понятнее и были более наглядными, вам, вероятно, нужны синонимы типов.&lt;/li&gt;&lt;li&gt;Если вы хотите взять существующий тип и обернуть его в новый тип, чтобы сделать его экземпляром класса типов, скорее всего, вы ищете &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Если вы хотите сделать что-то совершенно новое, есть хорошие шансы, что вы ищете ключевое слово &lt;code class="prettyprint lang-hs"&gt;data&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Об этих моноидах&lt;/h2&gt;Классы типов в Хаскеле используются для представления интерфейса к типам, которые обладают каким-то схожим поведением. Мы начали с простых классов типов вроде &lt;code class="prettyprint lang-hs"&gt;Eq&lt;/code&gt;, который предназначен для типов, чьи значения можно сравнить, и &lt;code class="prettyprint lang-hs"&gt;Ord&lt;/code&gt; – для сущностей, которые можно упорядочить. Затем мы перешли к более интересным классам типов, как &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;Applicative&lt;/code&gt;.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://s3.amazonaws.com/lyah/pirateship.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="290" src="http://s3.amazonaws.com/lyah/pirateship.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;Когда мы создаем тип, мы думаем о том, какие поведения он поддерживает (как он может действовать), а затем решаем, экземпляром каких классов типов его сделать, основываясь на необходимом нам поведении. Если разумно чтобы значения нашего типа были сравниваемыми, мы делаем наш тип экземпляром класса типов &lt;code class="prettyprint lang-hs"&gt;Eq&lt;/code&gt;. Если мы видим, что наш тип является чем-то вроде функтора, мы делаем его экземпляром &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt;, и т. д.&lt;br /&gt;&lt;br /&gt;Теперь рассмотрите следующее: &lt;code class="prettyprint lang-hs"&gt;*&lt;/code&gt; – это функция, которая принимает два числа и перемножает их. Если мы умножим какое-нибудь число на &lt;code class="prettyprint lang-hs"&gt;1&lt;/code&gt;, результат всегда равен этому числу. Не важно, выполним ли мы &lt;code class="prettyprint lang-hs"&gt;1 * x&lt;/code&gt; или &lt;code class="prettyprint lang-hs"&gt;x * 1&lt;/code&gt; – результат всегда равен &lt;code class="prettyprint lang-hs"&gt;x&lt;/code&gt;. Подобным образом, &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt; – это функция, которая принимает две сущности и возвращает третью. Но вместо того чтобы перемножать числа, она принимает два списка и конкатенирует их. И так же, как &lt;code class="prettyprint lang-hs"&gt;*&lt;/code&gt;, она тоже имеет определенное значение, которое не изменяет другое значение при использовании с &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt;. Этим значением является пустой список: &lt;code class="prettyprint lang-hs"&gt;[]&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; 4 * 1&lt;br /&gt;4&lt;br /&gt;ghci&amp;gt; 1 * 9&lt;br /&gt;9&lt;br /&gt;ghci&amp;gt; [1,2,3] ++ []&lt;br /&gt;[1,2,3]&lt;br /&gt;ghci&amp;gt; [] ++ [0.5, 2.5]&lt;br /&gt;[0.5,2.5]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Похоже, что &lt;code class="prettyprint lang-hs"&gt;*&lt;/code&gt; вместе с &lt;code class="prettyprint lang-hs"&gt;1&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt; наряду с &lt;code class="prettyprint lang-hs"&gt;[]&lt;/code&gt; разделяют некоторые общие свойства:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Функция принимает два параметра.&lt;/li&gt;&lt;li&gt;Параметры и возвращаемое значение имеют одинаковый тип.&lt;/li&gt;&lt;li&gt;Существует такое значение, которое не изменяет другие значения, когда оно используется с бинарной функцией.&lt;/li&gt;&lt;/ul&gt;Есть еще одно общее между двумя этими операциями, что может быть не столь очевидным, как наши предыдущие наблюдения: когда у нас есть три и более значения, и нам необходимо использовать бинарную функцию для превращения их в один результат, порядок, в котором мы применяем бинарную функцию к значениям, не имеет значения. Например, выполним ли мы &lt;code class="prettyprint lang-hs"&gt;(3 * 4) * 5&lt;/code&gt; или &lt;code class="prettyprint lang-hs"&gt;3 * (4 * 5)&lt;/code&gt;, результат равен &lt;code class="prettyprint lang-hs"&gt;60&lt;/code&gt;. То же справедливо для &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; (3 * 2) * (8 * 5)&lt;br /&gt;240&lt;br /&gt;ghci&amp;gt; 3 * (2 * (8 * 5))&lt;br /&gt;240&lt;br /&gt;ghci&amp;gt; "la" ++ ("di" ++ "da")&lt;br /&gt;"ladida"&lt;br /&gt;ghci&amp;gt; ("la" ++ "di") ++ "da"&lt;br /&gt;"ladida"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Мы называем это свойство ассоциативностью. &lt;code class="prettyprint lang-hs"&gt;*&lt;/code&gt; ассоциативна, &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt; тоже. Однако, &lt;code class="prettyprint lang-hs"&gt;-&lt;/code&gt;, например, неассоциативна; выражения &lt;code class="prettyprint lang-hs"&gt;(5 - 3) - 4&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;5 - (3 - 4)&lt;/code&gt; возвращают в результате разные числа.&lt;br /&gt;&lt;br /&gt;Зная об этих свойствах, мы наткнулись на моноиды!&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Класс типов Monoid&lt;/h3&gt;Моноид состоит из ассоциативной бинарной функции и значения, которое действует как тождество по отношению к этой функции. Когда что-то действует как тождество по отношению к функции, это означает, что при вызове с этой функцией и каким-то другим значением результат всегда равен этому другому значению. &lt;code class="prettyprint lang-hs"&gt;1&lt;/code&gt; является тождеством по отношению к &lt;code class="prettyprint lang-hs"&gt;*&lt;/code&gt;, а &lt;code class="prettyprint lang-hs"&gt;[]&lt;/code&gt; является тождеством по отношению к &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt;. В мире Хаскель есть множество других моноидов, поэтому существует класс типов &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;. Он предназначен для типов, которые могут действовать как моноиды. Давайте посмотрим, как определен этот класс типов:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;class Monoid m where&lt;br /&gt;    mempty :: m&lt;br /&gt;    mappend :: m -&amp;gt; m -&amp;gt; m&lt;br /&gt;    mconcat :: [m] -&amp;gt; m&lt;br /&gt;    mconcat = foldr mappend mempty&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Класс типов &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; определен в &lt;code class="prettyprint lang-hs"&gt;Data.Monoid&lt;/code&gt;. Давайте потратим некоторое время, чтобы как следует с ними познакомиться.&lt;br /&gt;&lt;br /&gt;Прежде всего, нам видно, что экземплярами &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; могут быть сделаны только конкретные типы, потому что &lt;code class="prettyprint lang-hs"&gt;m&lt;/code&gt; в определении класса типов не принимает никаких параметров типа. Это отличается от &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;Applicative&lt;/code&gt;, которые требуют, чтобы их экземплярами были конструкторы типа, которые принимают один параметр.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://s3.amazonaws.com/lyah/balloondog.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://s3.amazonaws.com/lyah/balloondog.png" width="255" /&gt;&lt;/a&gt;&lt;/div&gt;Первой функцией является &lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt;. На самом деле это не функция, поскольку это не принимает параметров. Это полиморфная константа вроде &lt;code class="prettyprint lang-hs"&gt;minBound&lt;/code&gt; из &lt;code class="prettyprint lang-hs"&gt;Bounded&lt;/code&gt;. &lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt; представляет тождественное значение для конкретного моноида.&lt;br /&gt;&lt;br /&gt;Далее, у нас есть &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, которая, как вы уже, наверное, догадались, является бинарной функцией. Она принимает два значения одного типа и возвращает еще одно значение того же самого типа. Решение назвать функцию &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; так было отчасти неудачным, поскольку это подразумевает, что мы в некотором роде присоединяем два значения. Тогда как &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt; действительно принимает два списка и присоединяет один в конец другого, &lt;code class="prettyprint lang-hs"&gt;*&lt;/code&gt; на самом деле не делает какого-либо присоединения, она просто перемножает два числа. Когда вы встретите другие экземпляры &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;, вы поймете, что большинство из них тоже не присоединяют значения. Поэтому избегайте мыслить в терминах присоединения и просто думайте о &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; как о бинарной функции, которая принимает два моноидных значения и возвращает третье.&lt;br /&gt;&lt;br /&gt;Последней функцией в определении этого класса типов является &lt;code class="prettyprint lang-hs"&gt;mconcat&lt;/code&gt;. Она принимает список моноидных значений и сокращает их до одного значения, применяя &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; между элементами списка. Она имеет реализацию по умолчанию, которая просто принимает &lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt; в качестве начального значения и сворачивает список справа с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;. Поскольку реализация по умолчанию хорошо подходит для большинства экземпляров, мы не будем сильно переживать по поводу &lt;code class="prettyprint lang-hs"&gt;mconcat&lt;/code&gt;. Когда какой-то тип делают экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;, достаточно реализовать всего лишь &lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;. Хотя для некоторых экземпляров &lt;code class="prettyprint lang-hs"&gt;mconcat&lt;/code&gt; можно реализовать более эффективно, в большинстве случаев реализация по умолчанию подходит идеально.&lt;br /&gt;&lt;h3&gt;Законы моноидов&lt;/h3&gt;Прежде чем перейти к более конкретным экземплярам &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;, давайте кратко рассмотрим законы моноидов.&lt;br /&gt;&lt;br /&gt;Вы узнали, что должно иметься значение, которое действует как тождество по отношению к бинарной функции, и что бинарная функция должна быть ассоциативна. Возможно создать экземпляры &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;, которые не следуют этим правилам, но такие экземпляры никому не нужны, поскольку когда мы используем класс типов &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;, мы полагаемся на то, что его экземпляры ведут себя как моноиды. Иначе какой в этом смысл? Именно поэтому при создании экземпляров &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; мы должны убедиться, что они следуют этим законам:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code class="prettyprint lang-hs"&gt;mempty `mappend` x = x&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code class="prettyprint lang-hs"&gt;x `mappend` mempty = x&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code class="prettyprint lang-hs"&gt;(x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;Первые два закона утверждают, что &lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt; должна вести себя как тождество по отношению к &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, а третий – говорит, что &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; должна быть ассоциативна (порядок, в котором мы используем &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; для сокращения нескольких моноидных значений в одно, не имеет значения). Хаскель не приводит эти законы в исполнение, поэтому мы должны быть внимательными, чтобы наши экземпляры действительно подчинялись им.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Знакомьтесь с некоторыми моноидами&lt;/h2&gt;Теперь, когда вы знаете, что такое моноиды, давайте рассмотрим некоторые типы в Хаскеле, которые являются моноидами, как выглядят их экземпляры &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;, и их использование.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Списки являются моноидами&lt;/h3&gt;Да, списки являются моноидами! Как вы уже увидели, функция &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt; и пустой список &lt;code class="prettyprint lang-hs"&gt;[]&lt;/code&gt; вместе образуют моноид. Экземпляр очень прост:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Monoid [a] where&lt;br /&gt;    mempty = []&lt;br /&gt;    mappend = (++)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Списки являются экземплярами класса типов &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; независимо от типа элементов, которые они содержат. Обратите внимание, что мы написали &lt;code class="prettyprint lang-hs"&gt;instance Monoid [a],&lt;/code&gt; а не &lt;code class="prettyprint lang-hs"&gt;instance Monoid []&lt;/code&gt;, поскольку &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; требует конкретный тип для экземпляра.&lt;br /&gt;&lt;br /&gt;Тестируя это, мы не встречаем сюрпризов:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; [1,2,3] `mappend` [4,5,6]&lt;br /&gt;[1,2,3,4,5,6]&lt;br /&gt;ghci&amp;gt; ("one" `mappend` "two") `mappend` "tree"&lt;br /&gt;"onetwotree"&lt;br /&gt;ghci&amp;gt; "one" `mappend` ("two" `mappend` "tree")&lt;br /&gt;"onetwotree"&lt;br /&gt;ghci&amp;gt; "one" `mappend` "two" `mappend` "tree"&lt;br /&gt;"onetwotree"&lt;br /&gt;ghci&amp;gt; "pang" `mappend` mempty&lt;br /&gt;"pang"&lt;br /&gt;ghci&amp;gt; mconcat [[1,2],[3,6],[9]]&lt;br /&gt;[1,2,3,6,9]&lt;br /&gt;ghci&amp;gt; mempty :: [a]&lt;br /&gt;[]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Обратите внимание, что в последней строке мы написали явную аннотацию типа. Если бы мы написали просто &lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt;, GHCi не знал бы, какой экземпляр использовать, поэтому мы должны были сказать, что нам нужен списковый экземпляр. Мы могли использовать общий тип &lt;code class="prettyprint lang-hs"&gt;[a]&lt;/code&gt; (в отличие от указания &lt;code class="prettyprint lang-hs"&gt;[Int]&lt;/code&gt; или &lt;code class="prettyprint lang-hs"&gt;[String]&lt;/code&gt;), потому что пустой список может действовать так, будто он содержит любой тип.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://s3.amazonaws.com/lyah/smug.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://s3.amazonaws.com/lyah/smug.png" /&gt;&lt;/a&gt;&lt;/div&gt;Поскольку &lt;code class="prettyprint lang-hs"&gt;mconcat&lt;/code&gt; имеет реализацию по умолчанию, мы получаем ее просто так, когда делаем что-либо экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;. В случае со списокм &lt;code class="prettyprint lang-hs"&gt;mconcat&lt;/code&gt; соответствует просто &lt;code class="prettyprint lang-hs"&gt;concat&lt;/code&gt;. Она принимает список списков и разглаживает его, потому что это равнозначно вызову &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt; между всеми смежными списками, содержащимися в списке.&lt;br /&gt;&lt;br /&gt;Законы моноидов действительно выполняются для экземпляра списка. Когда у нас есть несколько списков и мы соединяем их вместе с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; (или &lt;code class="prettyprint lang-hs"&gt;++&lt;/code&gt;), не имеет значения, какие списки мы соединяем первыми, поскольку так или иначе они соединяются на концах. Кроме того, пустой список действует как тождество, поэтому все хорошо.&lt;br /&gt;&lt;br /&gt;Обратите внимание, что моноиды не требуют, чтобы &lt;code class="prettyprint lang-hs"&gt;a `mappend` b&lt;/code&gt; было равно &lt;code class="prettyprint lang-hs"&gt;b `mappend` a&lt;/code&gt;. В случае со списками они очевидно не равны:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; "one" `mappend` "two"&lt;br /&gt;"onetwo"&lt;br /&gt;ghci&amp;gt; "two" `mappend` "one"&lt;br /&gt;"twoone"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;И это нормально. Тот факт, что при умножении выражения &lt;code class="prettyprint lang-hs"&gt;3 * 5&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;5 * 3&lt;/code&gt; дают один и тот же результат – это просто свойство умножения, но оно не выполняется для всех (в действительности, для большинства) моноидов.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Product и Sum&lt;/h3&gt;Мы уже изучили один из способов рассматривать числа как моноиды: просто позволить бинарной функции быть &lt;code class="prettyprint lang-hs"&gt;*&lt;/code&gt;, а тождественному значению – быть &lt;code class="prettyprint lang-hs"&gt;1&lt;/code&gt;. Еще один способ для чисел быть моноидами состоит в том, чтобы бинарной функцией была &lt;code class="prettyprint lang-hs"&gt;+&lt;/code&gt;, а тождественным значением было &lt;code class="prettyprint lang-hs"&gt;0&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; 0 + 4&lt;br /&gt;4&lt;br /&gt;ghci&amp;gt; 5 + 0&lt;br /&gt;5&lt;br /&gt;ghci&amp;gt; (1 + 3) + 5&lt;br /&gt;9&lt;br /&gt;ghci&amp;gt; 1 + (3 + 5)&lt;br /&gt;9&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Законы моноидов выполняются, потому что если вы прибавите &lt;code class="prettyprint lang-hs"&gt;0&lt;/code&gt; к любому числу, результатом будет это число. Прибавление также ассоциативно, поэтому у нас здесь нет никаких проблем.&lt;br /&gt;&lt;br /&gt;Имея два одинаково правомерных способа для чисел быть моноидами, какой способ нам выбрать? Ладно, мы не обязаны выбирать. Вспомните, что когда имеется несколько способов для какого-то типа быть экземпляром одного и того же класса типов, мы можем обернуть этот тип в &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;, а затем сделать новый тип экземпляром класса типов по-другому. Можно совместить несовместимое.&lt;br /&gt;&lt;br /&gt;Модуль &lt;code class="prettyprint lang-hs"&gt;Data.Monoid&lt;/code&gt; экспортирует для этого два типа: &lt;code class="prettyprint lang-hs"&gt;Product&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;Sum&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;code class="prettyprint lang-hs"&gt;Product&lt;/code&gt; определен вот так:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype Product a = Product { getProduct :: a }&lt;br /&gt;    deriving (Eq, Ord, Read, Show, Bounded)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Это просто – всего лишь обертка &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; с одним параметром типа наряду с некоторыми порожденными экземплярами. Его экземпляр для &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; выглядит примерно так:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Num a =&amp;gt; Monoid (Product a) where&lt;br /&gt;    mempty = Product 1&lt;br /&gt;    Product x `mappend` Product y = Product (x * y)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt; – это просто &lt;code class="prettyprint lang-hs"&gt;1&lt;/code&gt;, обернутое в конструктор &lt;code class="prettyprint lang-hs"&gt;Product&lt;/code&gt;. &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; производит сопоставление конструктора &lt;code class="prettyprint lang-hs"&gt;Product&lt;/code&gt; с образцом, умножает два числа, а затем оборачивает результирующее число. Как вы можете видеть, имеется ограничение класса &lt;code class="prettyprint lang-hs"&gt;Num a&lt;/code&gt;. Это значит, что &lt;code class="prettyprint lang-hs"&gt;Product&lt;/code&gt; a является экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; для всех значений &lt;code class="prettyprint lang-hs"&gt;a&lt;/code&gt;, которые уже являются экземпляром &lt;code class="prettyprint lang-hs"&gt;Num&lt;/code&gt;. Для того чтобы использовать &lt;code class="prettyprint lang-hs"&gt;Product a&lt;/code&gt; в качестве моноида, мы должны произвести некоторое оборачивание и разворачивание &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getProduct $ Product 3 `mappend` Product 9&lt;br /&gt;27&lt;br /&gt;ghci&amp;gt; getProduct $ Product 3 `mappend` mempty&lt;br /&gt;3&lt;br /&gt;ghci&amp;gt; getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2&lt;br /&gt;24&lt;br /&gt;ghci&amp;gt; getProduct . mconcat . map Product $ [3,4,2]&lt;br /&gt;24&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;code class="prettyprint lang-hs"&gt;Sum&lt;/code&gt; определен в том же духе, что и &lt;code class="prettyprint lang-hs"&gt;Product&lt;/code&gt;, и экземпляр тоже похож. Мы используем его точно так же:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getSum $ Sum 2 `mappend` Sum 9&lt;br /&gt;11&lt;br /&gt;ghci&amp;gt; getSum $ mempty `mappend` Sum 3&lt;br /&gt;3&lt;br /&gt;ghci&amp;gt; getSum . mconcat . map Sum $ [1,2,3]&lt;br /&gt;6&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Any и All&lt;/h3&gt;Еще одним типом, который может действовать как моноид двумя разными, но одинаково допустимыми способами, является &lt;code class="prettyprint lang-hs"&gt;Bool&lt;/code&gt;. Первый способ состоит в том, чтобы заставить функцию &lt;code class="prettyprint lang-hs"&gt;||&lt;/code&gt;, которая представляет логическое ИЛИ, действовать как бинарную функцию наряду с &lt;code class="prettyprint lang-hs"&gt;False&lt;/code&gt; в качестве тождественного значения. При использовании логического ИЛИ, если какой-либо из параметров равен &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;, она возвращает &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;; в противном случае она возвращает &lt;code class="prettyprint lang-hs"&gt;False&lt;/code&gt;. Поэтому если мы используем &lt;code class="prettyprint lang-hs"&gt;False&lt;/code&gt; в качестве тождественного значения, ИЛИ вернет &lt;code class="prettyprint lang-hs"&gt;False&lt;/code&gt; при использовании с &lt;code class="prettyprint lang-hs"&gt;False&lt;/code&gt; и – &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt; при использовании с &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;. Конструктор newtype &lt;code class="prettyprint lang-hs"&gt;Any&lt;/code&gt; является экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; подобным образом. Он определен вот так:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype Any = Any { getAny :: Bool }&lt;br /&gt;    deriving (Eq, Ord, Read, Show, Bounded)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Его экземпляр выглядит вот так:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Monoid Any where&lt;br /&gt;    mempty = Any False&lt;br /&gt;    Any x `mappend` Any y = Any (x || y)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Он называется &lt;code class="prettyprint lang-hs"&gt;Any&lt;/code&gt;, потому что &lt;code class="prettyprint lang-hs"&gt;x `mappend` y&lt;/code&gt; будет равно &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;, если любое из этих двух значений равно &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;. Даже если три или более значений &lt;code class="prettyprint lang-hs"&gt;Bool&lt;/code&gt;, обернутых в &lt;code class="prettyprint lang-hs"&gt;Any&lt;/code&gt;, объединяются с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; вместе, результат будет содержать &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;, если любое из них равно &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getAny $ Any True `mappend` Any False&lt;br /&gt;True&lt;br /&gt;ghci&amp;gt; getAny $ mempty `mappend` Any True&lt;br /&gt;True&lt;br /&gt;ghci&amp;gt; getAny . mconcat . map Any $ [False, False, False, True]&lt;br /&gt;True&lt;br /&gt;ghci&amp;gt; getAny $ mempty `mappend` mempty&lt;br /&gt;False&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Другой способ для &lt;code class="prettyprint lang-hs"&gt;Bool&lt;/code&gt; быть экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; состоит в том, чтобы сделать это как бы наоборот: заставить &lt;code class="prettyprint lang-hs"&gt;&amp;amp;&amp;amp;&lt;/code&gt; быть бинарной функцией, а затем сделать &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt; тождественным значением. Логическое И вернет &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt; только если оба его параметра равны &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Это объявление newtype:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype All = All { getAll :: Bool }&lt;br /&gt;    deriving (Eq, Ord, Read, Show, Bounded)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;А это экземпляр:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Monoid All where&lt;br /&gt;    mempty = All True&lt;br /&gt;    All x `mappend` All y = All (x &amp;amp;&amp;amp; y)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Когда мы объединяем значения типа &lt;code class="prettyprint lang-hs"&gt;All&lt;/code&gt; с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, результатом будет &lt;code class="prettyprint lang-hs"&gt;Ture&lt;/code&gt; только если все значения, использованные в операции &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, равны &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getAll $ mempty `mappend` All True&lt;br /&gt;True&lt;br /&gt;ghci&amp;gt; getAll $ mempty `mappend` All False&lt;br /&gt;False&lt;br /&gt;ghci&amp;gt; getAll . mconcat . map All $ [True, True, True]&lt;br /&gt;True&lt;br /&gt;ghci&amp;gt; getAll . mconcat . map All $ [True, True, False]&lt;br /&gt;False&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Так же, как при использовании умножения и сложения, мы обычно явно указываем бинарные функции вместо оборачивания их в значения &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt; и последующего использования &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt;. &lt;code class="prettyprint lang-hs"&gt;mconcat&lt;/code&gt; кажется полезной для &lt;code class="prettyprint lang-hs"&gt;Any&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;All&lt;/code&gt;, но обычно проще использовать функции &lt;code class="prettyprint lang-hs"&gt;or&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;and&lt;/code&gt;. &lt;code class="prettyprint lang-hs"&gt;or&lt;/code&gt; принимает списки значений &lt;code class="prettyprint lang-hs"&gt;Bool&lt;/code&gt; и возвращает &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;, если какое-либо из них равно &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;. &lt;code class="prettyprint lang-hs"&gt;and&lt;/code&gt; принимает те же значения и возвращает &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;, если все из них равны &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Моноид Ordering&lt;/h3&gt;Помните тип &lt;code class="prettyprint lang-hs"&gt;Ordering&lt;/code&gt;? Он используется в качестве результата при сравнении сущностей и может иметь три значения: &lt;code class="prettyprint lang-hs"&gt;LT&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;GT&lt;/code&gt;, которые соответственно означают "меньше, чем", "равно" и "больше, чем".&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; 1 `compare` 2&lt;br /&gt;LT&lt;br /&gt;ghci&amp;gt; 2 `compare` 2&lt;br /&gt;EQ&lt;br /&gt;ghci&amp;gt; 3 `compare` 2&lt;br /&gt;GT&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;При использовании чисел и значений &lt;code class="prettyprint lang-hs"&gt;Bool&lt;/code&gt; поиск моноидов состоял просто в просмотре уже существующих широко применяемых функций и проверке того, проявляли ли они какое-либо поведение, присущее моноидам. При использовании &lt;code class="prettyprint lang-hs"&gt;Ordering&lt;/code&gt; нам нужно сильнее вглядеться, чтобы распознать моноид. Оказывается, его экземпляр &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; настолько же интуитивен, насколько и предыдущие, что мы уже встретили, и он также весьма полезен:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Monoid Ordering where&lt;br /&gt;    mempty = EQ&lt;br /&gt;    LT `mappend` _ = LT&lt;br /&gt;    EQ `mappend` y = y&lt;br /&gt;    GT `mappend` _ = GT&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Экземпляр определяется следующим образом: когда мы объединяем два значения &lt;code class="prettyprint lang-hs"&gt;Ordered&lt;/code&gt; с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, сохраняется значение слева, если значение слева не равно &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt;. Если значение слева равно &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt;, результатом будет значение справа. Тождественным значением является &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt;. На первый взгляд такой выбор может показаться несколько случайным, но он на самом деле имеет сходство с тем, как мы сравниваем слова в алфавитном порядке. Мы смотрим на первые две буквы и если они отличаются, мы уже можем решить, какое слово шло бы первым в словаре. Однако, если первые две буквы равны, тогда мы переходим к сравнению следующей пары букв, повторяя процесс.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://s3.amazonaws.com/lyah/bear.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://s3.amazonaws.com/lyah/bear.png" width="311" /&gt;&lt;/a&gt;&lt;/div&gt;Например, когда мы сравниваем слова &lt;code class="prettyprint lang-hs"&gt;ox&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;on&lt;/code&gt; по алфавиту, мы видим, что первые две буквы каждого слова равны, а затем продолжаем сравнивать вторые буквы. Поскольку &lt;code class="prettyprint lang-hs"&gt;x&lt;/code&gt; по алфавиту больше, чем &lt;code class="prettyprint lang-hs"&gt;n&lt;/code&gt;, мы знаем, как сравниваются эти слова. Чтобы лучше понять, как &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt; является тождественным значением, обратите внимание, что если бы мы втиснули одну и ту же букву в одну и ту же позицию в обоих словах, это не изменило бы их алфавитный порядок; к примеру, &lt;code class="prettyprint lang-hs"&gt;oix&lt;/code&gt; по-прежнему больше в алфавитном порядке, чем &lt;code class="prettyprint lang-hs"&gt;oin&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Важно отметить, что в экземпляре класса типов &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; для &lt;code class="prettyprint lang-hs"&gt;Ordering&lt;/code&gt; выражение &lt;code class="prettyprint lang-hs"&gt;x `mappend` y&lt;/code&gt; не равно выражению &lt;code class="prettyprint lang-hs"&gt;y `mappend` x&lt;/code&gt;. Поскольку первый параметр сохраняется, если он не равен &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;LT `mappend` GT&lt;/code&gt; в результате вернет &lt;code class="prettyprint lang-hs"&gt;LT&lt;/code&gt;, тогда как &lt;code class="prettyprint lang-hs"&gt;GT `mappend` LT&lt;/code&gt; в результате вернет &lt;code class="prettyprint lang-hs"&gt;GT&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; LT `mappend` GT&lt;br /&gt;LT&lt;br /&gt;ghci&amp;gt; GT `mappend` LT&lt;br /&gt;GT&lt;br /&gt;ghci&amp;gt; mempty `mappend` LT&lt;br /&gt;LT&lt;br /&gt;ghci&amp;gt; mempty `mappend` GT&lt;br /&gt;GT&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Хорошо, так чем же этот моноид полезен? Предположим, мы пишем функцию, которая принимает две строки, сравнивает их длину, и возвращает &lt;code class="prettyprint lang-hs"&gt;Ordering&lt;/code&gt;. Но если строки имеют одинаковую длину, тогда вместо того, чтобы сразу вернуть &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt;, мы хотим сравнить их по алфавиту.&lt;br /&gt;&lt;br /&gt;Вот один из способов записать это:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;lengthCompare :: String -&amp;gt; String -&amp;gt; Ordering&lt;br /&gt;lengthCompare x y = let a = length x `compare` length y&lt;br /&gt;                        b = x `compare` y&lt;br /&gt;                    in if a == EQ then b else a&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Результат сравнения длин мы присваиваем a, а результат сравнения по алфавиту – &lt;code class="prettyprint lang-hs"&gt;b&lt;/code&gt;, а затем если оказывается, что длины равны, мы возвращаем их порядок по алфавиту.&lt;br /&gt;&lt;br /&gt;Но применяя наше понимание того, как &lt;code class="prettyprint lang-hs"&gt;Ordering&lt;/code&gt; является моноидом, мы можем переписать эту функцию в более простом виде:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;import Data.Monoid&lt;br /&gt;&lt;br /&gt;lengthCompare :: String -&amp;gt; String -&amp;gt; Ordering&lt;br /&gt;lengthCompare x y = (length x `compare` length y) `mappend`&lt;br /&gt;                    (x `compare` y)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Давайте опробуем это:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; lengthCompare "zen" "ants"&lt;br /&gt;LT&lt;br /&gt;ghci&amp;gt; lengthCompare "zen" "ant"&lt;br /&gt;GT&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Вспомните, что когда мы используем &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, сохраняется ее левый параметр, если он не равен &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt;; если он равен &lt;code class="prettyprint lang-hs"&gt;EQ&lt;/code&gt;, сохраняется правый. Вот почему мы поместили сравнение, которое мы считаем первым, более важным критерием, в качестве первого параметра. Теперь предположим, что мы хотим расширить эту функцию, чтобы она также сравнивала количество гласных звуков, и установить это вторым по важности критерием для сравнения. Мы изменяем ее вот так:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;import Data.Monoid&lt;br /&gt;&lt;br /&gt;lengthCompare :: String -&amp;gt; String -&amp;gt; Ordering&lt;br /&gt;lengthCompare x y = (length x `compare` length y) `mappend`&lt;br /&gt;                    (vowels x `compare` vowels y) `mappend`&lt;br /&gt;                    (x `compare` y)&lt;br /&gt;    where vowels = length . filter (`elem` "aeiou")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Мы создали вспомогательную функцию, которая принимает строку и сообщает нам, сколько она содержит гласных звуков, сначала отфильтровывая в ней только буквы, находящиеся в строке &lt;code class="prettyprint lang-hs"&gt;"aeiou"&lt;/code&gt;, а затем применяя к этому функцию &lt;code class="prettyprint lang-hs"&gt;length&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; lengthCompare "zen" "anna"&lt;br /&gt;LT&lt;br /&gt;ghci&amp;gt; lengthCompare "zen" "ana"&lt;br /&gt;LT&lt;br /&gt;ghci&amp;gt; lengthCompare "zen" "ann"&lt;br /&gt;GT&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;В первом примере длины оказались различными, и поэтому вернулось &lt;code class="prettyprint lang-hs"&gt;LT&lt;/code&gt;, так как длина &lt;code class="prettyprint lang-hs"&gt;"zen"&lt;/code&gt; меньше длины &lt;code class="prettyprint lang-hs"&gt;"anna"&lt;/code&gt;. Во втором примере длины равны, но вторая строка содержит больше гласных звуков, поэтому опять возвращается &lt;code class="prettyprint lang-hs"&gt;LT&lt;/code&gt;. В третьем примере они обе имеют одинаковую длину и одинаковое количество гласных звуков, поэтому они сравниваются по алфавиту, и &lt;code class="prettyprint lang-hs"&gt;"zen"&lt;/code&gt; выигрывает.&lt;br /&gt;&lt;br /&gt;Моноид &lt;code class="prettyprint lang-hs"&gt;Ordering&lt;/code&gt; очень полезен, поскольку позволяет нам без труда сравнивать сущности по большому количеству разных критериев, и помещать сами эти критерии по порядку, начиная с наиболее важных, заканчивая наименее важными.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Моноид Maybe&lt;/h3&gt;Давайте рассмотрим несколько способов, которыми &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt; a может быть сделан экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;, и как эти экземпляры полезны.&lt;br /&gt;&lt;br /&gt;Один из способов состоит в том, чтобы обрабатывать &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt; a как моноид только если его параметр типа a тоже является моноидом, а потом реализовать &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; так, чтобы она использовала операцию &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; для значений, обернутых в &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;. Мы используем &lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt; как тождественное значение, и поэтому если одно из двух значений, которые мы объединяем с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, равно &lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt;, мы оставляем другое значение. Вот объявление экземпляра:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Monoid a =&amp;gt; Monoid (Maybe a) where&lt;br /&gt;    mempty = Nothing&lt;br /&gt;    Nothing `mappend` m = m&lt;br /&gt;    m `mappend` Nothing = m&lt;br /&gt;    Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Обратите внимание на ограничение класса. Оно говорит, что &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt; является экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; только если &lt;code class="prettyprint lang-hs"&gt;a&lt;/code&gt; является экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;. Если мы объединяем что-то с &lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt;, используя &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, результатом является это что-то. Если мы объединяем два значения &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt; с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, то содержимые значений &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt; объединяются с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, а затем оборачиваются обратно в &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;. Мы можем делать это, поскольку ограничение класса гарантирует, что тип значения, которое находится внутри &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;, является экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; Nothing `mappend` Just "andy"&lt;br /&gt;Just "andy"&lt;br /&gt;ghci&amp;gt; Just LT `mappend` Nothing&lt;br /&gt;Just LT&lt;br /&gt;ghci&amp;gt; Just (Sum 3) `mappend` Just (Sum 4)&lt;br /&gt;Just (Sum {getSum = 7})&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Это полезно, когда мы имеем дело с моноидами как с результатами вычислений, которые могли окончиться неуспешно. Из-за наличия этого экземпляра нам не нужно проверять, окончились ли вычисления неуспешно, определяя, вернули они значение &lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt; или &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;; мы можем просто продолжить обрабатывать их как обычные моноиды.&lt;br /&gt;&lt;br /&gt;Но что если тип содержимого &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt; не является экземпляром &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt;? Обратите внимание, что в предыдущем объявлении экземпляра единственным случаем, где мы должны полагаться на то, что содержимые являются моноидами, – это когда оба параметра mappend являются значениями &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;. Когда мы не знаем, являются ли содержимые моноидами, мы не можем использовать &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; между ними, так что же нам делать? Ну, единственное, что мы можем сделать, – это отвергнуть второе значение и оставить первое. Для этой цели существует тип &lt;code class="prettyprint lang-hs"&gt;First a&lt;/code&gt;. Вот его определение:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;newtype First a = First { getFirst :: Maybe a }&lt;br /&gt;    deriving (Eq, Ord, Read, Show)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Мы берем &lt;code class="prettyprint lang-hs"&gt;Maybe a&lt;/code&gt; и оборачиваем его с помощью &lt;code class="prettyprint lang-hs"&gt;newtype&lt;/code&gt;. Экземпляр &lt;code class="prettyprint lang-hs"&gt;Monoid&lt;/code&gt; выглядит следующим образом:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance Monoid (First a) where&lt;br /&gt;    mempty = First Nothing&lt;br /&gt;    First (Just x) `mappend` _ = First (Just x)&lt;br /&gt;    First Nothing `mappend` x = x&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt; – это просто &lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt;, обернутое с помощью конструктора &lt;code class="prettyprint lang-hs"&gt;newtype First&lt;/code&gt;. Если первый параметр &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; является значением &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;, мы игнорируем второй. Если первый параметр – &lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt;, тогда мы возвращаем второй параметр в качестве результата независимо от того, является ли он &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt; или &lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getFirst $ First (Just 'a') `mappend` First (Just 'b')&lt;br /&gt;Just 'a'&lt;br /&gt;ghci&amp;gt; getFirst $ First Nothing `mappend` First (Just 'b')&lt;br /&gt;Just 'b'&lt;br /&gt;ghci&amp;gt; getFirst $ First (Just 'a') `mappend` First Nothing&lt;br /&gt;Just 'a'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;code class="prettyprint lang-hs"&gt;First&lt;/code&gt; полезен, когда у нас есть множество значений &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt;, и мы хотим знать, является ли какое-либо из них значением &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;. Для этого годится функция &lt;code class="prettyprint lang-hs"&gt;mconcat&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getFirst . mconcat . map First $ [Nothing, Just 9, Just 10]&lt;br /&gt;Just 9&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Если нам нужен моноид на значениях &lt;code class="prettyprint lang-hs"&gt;Maybe a&lt;/code&gt; такой, чтобы оставался второй параметр, когда оба параметра &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; являются значениями &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;Data.Monoid&lt;/code&gt; предоставляет тип &lt;code class="prettyprint lang-hs"&gt;Last a&lt;/code&gt;, который работает, как &lt;code class="prettyprint lang-hs"&gt;First a&lt;/code&gt;, но при объединении с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; и использовании &lt;code class="prettyprint lang-hs"&gt;mconcat&lt;/code&gt; сохраняется последнее не-&lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt; значение:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getLast . mconcat . map Last $ [Nothing, Just 9, Just 10]&lt;br /&gt;Just 10&lt;br /&gt;ghci&amp;gt; getLast $ Last (Just "one") `mappend` Last (Just "two")&lt;br /&gt;Just "two"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Свертывание с помощью моноидов&lt;/h2&gt;Одним из интересных способов ввести моноиды в работу заключается в том, чтобы они помогали нам определять свертки над различными структурами данных. До сих пор мы производили свертки только над списками, но списки – не единственная структура данных, которую можно свернуть. Мы можем определять свертки почти над любой структурой данных. Особенно хорошо поддаются свертке деревья.&lt;br /&gt;&lt;br /&gt;Поскольку существует так много структур данных, которые хорошо работают со свертками, был введен класс типов &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt;. Так же, как &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt; предназначен для сущностей, которые можно отображать, &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt; предназначен для вещей, которые могут быть сворачивать! Он может быть найден в &lt;code class="prettyprint lang-hs"&gt;Data.Foldable&lt;/code&gt;, и поскольку он экспортирует функции, чьи имена конфликтуют с именами функций из &lt;code class="prettyprint lang-hs"&gt;Prelude&lt;/code&gt;, его лучше импортировать квалифицируя (и подавая с базиликом):&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;import qualified Data.Foldable as F&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Чтобы сэкономить драгоценные нажатия клавиш, мы импортировали его квалифицируя как &lt;code class="prettyprint lang-hs"&gt;F&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Так какие из некоторых функций определяет этот класс типов? Ладно, среди них есть &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;foldl&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;foldr1&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;foldl1&lt;/code&gt;. Чего? Мы уже знакомы с этими функциями. Что в этом нового? Давайте сравним типы &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt; из &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt; из &lt;code class="prettyprint lang-hs"&gt;Prelude&lt;/code&gt;, чтобы узнать, чем они отличаются:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; :t foldr&lt;br /&gt;foldr :: (a -&amp;gt; b -&amp;gt; b) -&amp;gt; b -&amp;gt; [a] -&amp;gt; b&lt;br /&gt;ghci&amp;gt; :t F.foldr&lt;br /&gt;F.foldr :: (F.Foldable t) =&amp;gt; (a -&amp;gt; b -&amp;gt; b) -&amp;gt; b -&amp;gt; t a -&amp;gt; b&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Аа! Значит, тогда как &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt; принимает список и сворачивает его, &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt; из &lt;code class="prettyprint lang-hs"&gt;Data.Foldable&lt;/code&gt; принимает любой тип, который можно свернуть, не только списки! Как и ожидалось, обе функции &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt; делают со списками одно и то же:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; foldr (*) 1 [1,2,3]&lt;br /&gt;6&lt;br /&gt;ghci&amp;gt; F.foldr (*) 1 [1,2,3]&lt;br /&gt;6&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Другой структурой данных, поддерживающей свертку, является &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt;, которую мы все знаем и любим!&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; F.foldl (+) 2 (Just 9)&lt;br /&gt;11&lt;br /&gt;ghci&amp;gt; F.foldr (||) False (Just True)&lt;br /&gt;True&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Но сворачивание значения &lt;code class="prettyprint lang-hs"&gt;Maybe&lt;/code&gt; не очень уж интересно. Оно действует просто как список с одним элементом, если это значение &lt;code class="prettyprint lang-hs"&gt;Just&lt;/code&gt;, и – как пустой список, если это значение &lt;code class="prettyprint lang-hs"&gt;Nothing&lt;/code&gt;. Давайте рассмотрим чуть более сложную структуру данных.&lt;br /&gt;&lt;br /&gt;Помните древовидную структуру данных из главы 7? Мы определили ее вот так:&lt;br /&gt;&lt;br /&gt;Вы узнали, что дерево – это либо пустое дерево, которое не содержит никаких значений, либо это узел, который содержит одно значение, а также два других дерева. После того, как мы его определили, мы сделали его экземпляром &lt;code class="prettyprint lang-hs"&gt;Functor&lt;/code&gt;, и это дало нам возможность отображать его с помощью функций, используя &lt;code class="prettyprint lang-hs"&gt;fmap&lt;/code&gt;. Теперь мы сделаем его экземпляром &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt;, чтобы у нас появилась возможность производить его свертку.&lt;br /&gt;&lt;br /&gt;Один из способов сделать конструктор типа экземпляром &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt; состоит в том, чтобы просто напрямую реализовать для него &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt;. Но другой, часто более простой способ, состоит в том, чтобы реализовать функцию &lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt;, которая также является частью класса типов &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt;. Функция &lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt; имеет следующий тип:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;foldMap :: (Monoid m, Foldable t) =&amp;gt; (a -&amp;gt; m) -&amp;gt; t a -&amp;gt; m&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ее первым параметром является функция, принимающая значение того типа, который содержит наша сворачиваемая структура (обозначен здесь как &lt;code class="prettyprint lang-hs"&gt;a&lt;/code&gt;), и возвращающая моноидное значение. Ее вторым параметром является сворачиваемая структура, которая содержит значения типа &lt;code class="prettyprint lang-hs"&gt;a&lt;/code&gt;. Эта функция отображает структуру с помощью данной функции, таким образом производя сворачиваемую структуру, которая содержит моноидные значения. Затем, объединяя эти моноидные значения с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, она соединяет их все в одно моноидное значение. Эта функция на данный момент может показаться несколько странной, но вы увидите, что ее очень просто реализовать. И реализация этой функции – это все, что требуется, чтобы сделать наш тип экземпляром &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt;! Поэтому если мы просто реализуем &lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt; для какого-либо типа, мы получаем &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;foldl&lt;/code&gt; для этого типа даром!&lt;br /&gt;&lt;br /&gt;Вот как мы делаем &lt;code class="prettyprint lang-hs"&gt;Tree&lt;/code&gt; экземпляром &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;instance F.Foldable Tree where&lt;br /&gt;    foldMap f EmptyTree = mempty&lt;br /&gt;    foldMap f (Node x l r) = F.foldMap f l `mappend`&lt;br /&gt;                             f x `mappend`&lt;br /&gt;                             F.foldMap f r&lt;br /&gt;&lt;/pre&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://s3.amazonaws.com/lyah/accordion.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="244" src="http://s3.amazonaws.com/lyah/accordion.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;Если нам предоставлена функция, которая принимает элемент нашего дерева и возвращает моноидное значение, как нам превратить наше целое дерево в одно моноидное значение? Когда мы использовали &lt;code class="prettyprint lang-hs"&gt;fmap&lt;/code&gt; с нашим деревом, мы применяли функцию, отображая с ее помощью узел, а затем рекурсивно отображали с помощью этой функции левое поддерево, а также правое поддерево. Здесь наша задача состоит не только в отображении с помощью функции, но также и в соединении значений в одно моноидное значение, используя &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;. Сначала мы рассматриваем случай с пустым деревом – печальным и одиноким деревцем, у которого нет никаких значений или поддеревьев. Оно не содержит никаких значений, которые мы можем предоставить нашей функции, создающей моноид, поэтому мы просто говорим, что если наше дерево пусто, то моноидное значение, в которое оно будет превращено, равно &lt;code class="prettyprint lang-hs"&gt;mempty&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Случай с непустым узлом чуть более интересен. Он содержит два поддерева, а также значение. В этом случае мы рекурсивно отображаем левое и правое поддеревья с помощью одной и той же функции &lt;code class="prettyprint lang-hs"&gt;f&lt;/code&gt;, используя &lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt;. Вспомните, что наша &lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt; возвращает в результате одно моноидное значение. Мы также применяем нашу функцию &lt;code class="prettyprint lang-hs"&gt;f&lt;/code&gt; к значению в узле. Теперь у нас есть три моноидных значения (два – из наших поддеревьев, и одно – после применения &lt;code class="prettyprint lang-hs"&gt;f&lt;/code&gt; к значению в узле), и нам просто нужно соединить их вместе в одно значение. Для этой цели мы используем &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, и естественным образом левое поддерево идет первым, затем – значение узла, а потом – правое поддерево.Обратите внимание, что нам не нужно было предоставлять функцию, которая принимает значение и возвращает моноидное значение. Мы принимаем эту функцию как параметр к &lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt;, и все, что нам нужно решить, – это где применить эту функцию и как соединить результирующие моноиды, которые она возвращает.&lt;br /&gt;&lt;br /&gt;Теперь, когда у нас есть экземпляр &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt; для нашего типа, представляющего дерево, мы получаем &lt;code class="prettyprint lang-hs"&gt;foldr&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;foldl&lt;/code&gt; даром! Рассмотрите вот это дерево:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;testTree = Node 5&lt;br /&gt;            (Node 3&lt;br /&gt;                (Node 1 EmptyTree EmptyTree)&lt;br /&gt;                (Node 6 EmptyTree EmptyTree)&lt;br /&gt;            )&lt;br /&gt;	    (Node 9&lt;br /&gt;	        (Node 8 EmptyTree EmptyTree)&lt;br /&gt;	        (Node 10 EmptyTree EmptyTree)&lt;br /&gt;	    )&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;У него &lt;code class="prettyprint lang-hs"&gt;5&lt;/code&gt; в качестве его корня, а его левый узел содержит &lt;code class="prettyprint lang-hs"&gt;3&lt;/code&gt; с &lt;code class="prettyprint lang-hs"&gt;1&lt;/code&gt; слева и &lt;code class="prettyprint lang-hs"&gt;6&lt;/code&gt; справа. Правый узел корня содержит &lt;code class="prettyprint lang-hs"&gt;9&lt;/code&gt;, а затем &lt;code class="prettyprint lang-hs"&gt;8&lt;/code&gt; слева от него и &lt;code class="prettyprint lang-hs"&gt;10&lt;/code&gt; в самой дальней части справа. Используя экземпляр &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt;, мы можем производить все те же свертки, что мы можем производить над списками:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; F.foldl (+) 0 testTree&lt;br /&gt;42&lt;br /&gt;ghci&amp;gt; F.foldl (*) 1 testTree&lt;br /&gt;64800&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt; полезна не только для создания новых экземпляров &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt;. Она также очень удобна для превращения нашей структуры в одно моноидное значение. Например, если мы хотим узнать, равно ли какое-либо из чисел нашего дерева &lt;code class="prettyprint lang-hs"&gt;3&lt;/code&gt;, мы можем сделать следующее:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getAny $ F.foldMap (\x -&amp;gt; Any $ x == 3) testTree&lt;br /&gt;True&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Здесь &lt;code class="prettyprint lang-hs"&gt;\x -&amp;gt; Any $ x == 3&lt;/code&gt; – это функция, которая принимает число и возвращает моноидное значение: значение &lt;code class="prettyprint lang-hs"&gt;Bool&lt;/code&gt;, обернутое в &lt;code class="prettyprint lang-hs"&gt;Any&lt;/code&gt;. &lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt; применяет эту функцию к каждому элементу нашего дерева, а затем превращает получившиеся моноиды в один моноид с помощью &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;. Предположим, мы выполняем следующее:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; getAny $ F.foldMap (\x -&amp;gt; Any $ x &amp;gt; 15) testTree&lt;br /&gt;False&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Все узлы нашего дерева будут содержать значение &lt;code class="prettyprint lang-hs"&gt;Any False&lt;/code&gt; после того, как к ним будет применена лямбда-функция. Но чтобы получить в итоге &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt;, реализация &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt; для &lt;code class="prettyprint lang-hs"&gt;Any&lt;/code&gt; должна принять по крайней мере одно значение &lt;code class="prettyprint lang-hs"&gt;True&lt;/code&gt; в качестве параметра. Поэтому окончательным результатом будет &lt;code class="prettyprint lang-hs"&gt;False&lt;/code&gt;, что логично, поскольку все значения в нашем дереве не превышают &lt;code class="prettyprint lang-hs"&gt;15&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Мы также можем легко превратить наше дерево в список, просто используя &lt;code class="prettyprint lang-hs"&gt;foldMap&lt;/code&gt; с лямбда-функцией &lt;code class="prettyprint lang-hs"&gt;\x -&amp;gt; [x]&lt;/code&gt;. Проецируя сначала эту функцию на наше дерево, каждый элемент становится одноэелементным списком. Действие &lt;code class="prettyprint lang-hs"&gt;mappend&lt;/code&gt;, которое имеет место между всеми этими одноэлементными списками, возвращает в результате один список, содержащий все элементы нашего дерева:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;ghci&amp;gt; F.foldMap (\x -&amp;gt; [x]) testTree&lt;br /&gt;[1,3,6,5,8,9,10]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Круто то, что все эти трюки не ограничиваются деревьями. Они применимы ко всем экземплярам &lt;code class="prettyprint lang-hs"&gt;Foldable&lt;/code&gt;!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1973090003148579772-810932166491020208?l=arsanukaev.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://arsanukaev.blogspot.com/feeds/810932166491020208/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://arsanukaev.blogspot.com/2011/09/monoid.html#comment-form' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/810932166491020208'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/810932166491020208'/><link rel='alternate' type='text/html' href='http://arsanukaev.blogspot.com/2011/09/monoid.html' title=''/><author><name>Yasir Arsanukaev</name><uri>https://profiles.google.com/103729008772345894369</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1973090003148579772.post-6237982262438860580</id><published>2011-02-07T15:47:00.009+11:00</published><updated>2011-02-08T21:26:38.623+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='code-golf'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='functional-programming'/><category scheme='http://www.blogger.com/atom/ns#' term='fprog'/><title type='text'>Code-golf: Поиск в ширину и – в глубину с помощью Haskell</title><content type='html'>Написал короткий код на Haskell для &lt;a href="http://en.wikipedia.org/wiki/Breadth-first_search"&gt;поиска в ширину&lt;/a&gt; и &lt;a href="http://en.wikipedia.org/wiki/Depth-first_search"&gt;поиска в глубину&lt;/a&gt;.&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;Для обоих видов поиска используется рекурсивная структура, которая в читабельном виде выглядит следующим образом:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;data Node a = Node {value::a, children::[Node a]}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Сами тексты соответственно выглядят:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;bfs :: Node a -&gt; [a]&lt;br /&gt;bfs (Node a b) = bfs' [a] b&lt;br /&gt;  where bfs' res [] = res&lt;br /&gt;        bfs' res n = bfs' (res ++ (map value n)) $ concatMap children n&lt;br /&gt;&lt;br /&gt;dfs :: Node a -&gt; [a]&lt;br /&gt;dfs node = dfs' [node] []&lt;br /&gt;  where dfs' [] result = result&lt;br /&gt;        dfs' (x:xs) result = dfs' (children x ++ xs) (result ++ [value x])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ну, и в сжатом виде:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;data N a=N{v::a,c::[N a]}&lt;br /&gt;&lt;br /&gt;b(N a b)=d[a]b&lt;br /&gt;d r[]=r&lt;br /&gt;d r n=d(r++(map v n))$concatMap c n&lt;br /&gt;&lt;br /&gt;d n=f[n][]&lt;br /&gt;f(x:y)=f(c x++y).(++[v x])&lt;br /&gt;f _=id&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Где функция &lt;code&gt;b&lt;/code&gt; – поиск в ширину, а функция &lt;code&gt;d&lt;/code&gt; – в глубину.&lt;br /&gt;&lt;br /&gt;Поиск в глубину пытался сократить по-разному, в т. ч., как видно, с помощью &lt;a href="http://en.wikipedia.org/wiki/Function_composition"&gt;композиции функций&lt;/a&gt; (HaskellWiki – &lt;a href="http://www.haskell.org/haskellwiki/Pointfree"&gt;Point-free Style&lt;/a&gt;). Хотя, если развернуть композицию, вернув назад аргумент, объем кода будет идентичным:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;d n=f[n][]&lt;br /&gt;f(x:y)r=f(c x++y)$r++[v x]&lt;br /&gt;f[]r=r&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Если у кого-то есть идеи по дальнейшему возможному сокращению кода, либо вы хотите попробовать реализовать сжатые алгоритмы на вашем любимом языке, прошу предлагать свои варианты на новом сайте для любителей программирования и программных головоломок Code Golf – Stack Exchange:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;&lt;a href="http://codegolf.stackexchange.com/q/118/112"&gt;Tree traversal: Breadth-first search&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;a href="http://codegolf.stackexchange.com/q/596/112"&gt;Tree traversal: Depth-first search&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Have &lt;code&gt;fun()&lt;/code&gt;! ;-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1973090003148579772-6237982262438860580?l=arsanukaev.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://arsanukaev.blogspot.com/feeds/6237982262438860580/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://arsanukaev.blogspot.com/2011/02/code-golf-haskell.html#comment-form' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/6237982262438860580'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/6237982262438860580'/><link rel='alternate' type='text/html' href='http://arsanukaev.blogspot.com/2011/02/code-golf-haskell.html' title='Code-golf: Поиск в ширину и – в глубину с помощью Haskell'/><author><name>Yasir Arsanukaev</name><uri>https://profiles.google.com/103729008772345894369</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1973090003148579772.post-8070943219261721963</id><published>2010-10-21T00:03:00.004+11:00</published><updated>2010-11-01T00:42:01.335+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='functional'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='bert'/><category scheme='http://www.blogger.com/atom/ns#' term='scheme'/><category scheme='http://www.blogger.com/atom/ns#' term='fprog'/><title type='text'>scheme-bert – библиотека сериализации BERT для R6RS Scheme</title><content type='html'>Выпущена библиотека сериализации/десериализации BERT для реализаций языка программирования Scheme, соответствующих стандарту &lt;a href="http://www.r6rs.org/"&gt;R6RS&lt;/a&gt;.&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;С ее помощью вы можете сериализовать и десериализовать следующие типы данных:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;flonum?&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;integer?&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;symbol?&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;list?&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;string?&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;vector?&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;boolean?&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;hashtable?&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;time?&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;а также &lt;code&gt;'nil&lt;/code&gt; (в различных реализациях Scheme представлен по-разному, например, пустым списком &lt;span style=";font-family:&amp;quot;;font-size:12pt;"  lang="EN-US" &gt;&lt;/span&gt;&lt;code&gt;'()&lt;/code&gt;) в двоичный формат BERT (Binary ERlang Term), разработанный &lt;a href="http://tom.preston-werner.com/"&gt;Томом Престон-Вернером&lt;/a&gt; изначально для сервиса &lt;a href="http://github.com/"&gt;GitHub&lt;/a&gt;, одним из создателей которого он является.&lt;br /&gt;&lt;br /&gt;Небольшой пример кода:&lt;br /&gt;&lt;pre class="prettyprint lang-scm"&gt;&lt;br /&gt;&gt; (define (bytes)&lt;br /&gt;   (bert-encode (list 'foo 13 42 (vector 666 '() 'bar))))&lt;br /&gt;&gt; (bytes)&lt;br /&gt;#"\203l\0\0\0\4d\0\3fooa\ra*h\3b\0\0\2\232jd\0\3barj"&lt;br /&gt;&gt; (bert-decode (bytes))&lt;br /&gt;{foo 13 42 #(666 () bar)}&lt;br /&gt;&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Как видно, сериализации подвергались данные, которым в Erlang аналогичен следующий терм:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[foo, 13, 42, {666, [], bar}].&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Работа кода протестирована на &lt;a href="http://racket-lang.org/"&gt;Racket&lt;/a&gt; 5.0 и &lt;a href="http://www.scheme.com/chezscheme.html"&gt;Chez Scheme&lt;/a&gt; 8.0. Другие &lt;a href="http://www.r6rs.org/implementations.html"&gt;реализации Scheme, поддерживающие стандарт R6RS&lt;/a&gt;, не проверялись, однако работоспособность в основном зависит только от степени соответствия стандарту (&lt;a href="http://ikarus-scheme.org/"&gt;Ikarus Scheme&lt;/a&gt; весьма близок к нему).&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;/code&gt;Поддержка регулярных выражений (пока) не реализована, поскольку мне не удалось найти такой библиотеки для работы с ними, которая была бы максимально портируемой, а также поддерживала возможность получения исходной строки скомпилированного регулярного выражения и опций, при которых оно было скомпилировано. Возможно, &lt;a href="http://code.google.com/p/irregex/issues/detail?id=5"&gt;что-то сдвинется&lt;/a&gt; в этом отношении для Irregex, еще одной библиотеки регулярных выражений для Scheme, которая была бы неплохим вариантом для интеграции в сериализатор.&lt;br /&gt;&lt;br /&gt;Спецификация BERT расположена тут – &lt;a href="http://bert-rpc.org/"&gt;http://bert-rpc.org/&lt;/a&gt;&lt;br /&gt;Библиотека сериализации размещена здесь – &lt;a href="http://bitbucket.org/yasir/scheme-bert"&gt;http://bitbucket.org/yasir/scheme-bert&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Ваши патчи и отзывы приветствуются. Как форкнуть проект, выслать патч, сделать pull request, и прочие вещи, читайте в &lt;a href="http://confluence.atlassian.com/display/BITBUCKET"&gt;разделе помощи Bitbucket&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Regards, Yasir Arsanukaev.&lt;br /&gt;&lt;br /&gt;UPD. Ура, проект теперь входит в экосистему BERT, он добавлен в список на &lt;a href="http://bert-rpc.org"&gt;сайте BERT&lt;/a&gt;!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1973090003148579772-8070943219261721963?l=arsanukaev.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://arsanukaev.blogspot.com/feeds/8070943219261721963/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://arsanukaev.blogspot.com/2010/10/bert-serialization-library-for-scheme.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/8070943219261721963'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/8070943219261721963'/><link rel='alternate' type='text/html' href='http://arsanukaev.blogspot.com/2010/10/bert-serialization-library-for-scheme.html' title='scheme-bert – библиотека сериализации BERT для R6RS Scheme'/><author><name>Yasir Arsanukaev</name><uri>https://profiles.google.com/103729008772345894369</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1973090003148579772.post-906345667832597997</id><published>2010-04-15T00:38:00.028+10:00</published><updated>2011-02-07T16:25:51.784+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='functional-programming'/><category scheme='http://www.blogger.com/atom/ns#' term='fprog'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Haskell и параллелизм: Триангуляция сферы параллельным рекуррентным разбиением тетраэдра с применением плоского затенения</title><content type='html'>Надеюсь, вас не испугал заголовок статьи. Под катом приводится описание одного из методов распараллеливания вычислений в Haskell на примере изображения затененной сферы, имеются скриншоты результатов работы программы (трафика мало).&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Введение&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Сначала нужно решить задачу построения сферы. Для этого обычно применяют аппроксимацию.&lt;br /&gt;&lt;br /&gt;Чтобы аппроксимировать сферическую поверхность, можно воспользоваться методом &lt;span style="font-style: italic;"&gt;рекурсивного разбиения&lt;/span&gt;, а в качестве &lt;a href="http://ru.wikipedia.org/wiki/Правильный_многогранник"&gt;правильного многогранника&lt;/a&gt; выберем тетраэдр, 4 грани которого являются равносторонними треугольниками. Можно было бы использовать любой другой правильный многоугольник; например, &lt;a href="http://ru.wikipedia.org/wiki/Икосаэдр"&gt;икосаэдр&lt;/a&gt; отлично подходит для аппроксимации сферы.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Вершины тетраэдра&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Для определения координат вершин тетраэдра можно воспользоваться &lt;a href="http://www.mathematische-basteleien.de/tetrahedron.htm"&gt;данными&lt;/a&gt; выкладками. Предположим, что после соответствующих вычислений мы имеем следующие координаты четырех вершин тетраэдра:&lt;br /&gt;(0.0, 0.0, 1.0),&lt;br /&gt;(0.0, 0.942809, -0.333333),&lt;br /&gt;(-0.816497, -0.471405, -0.333333),&lt;br /&gt;(0.816497, -0.471405, -0.333333)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Каркас приложения&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Для визуализации используем OpenGL; Haskell имеет к ней соответствующую &lt;a href="http://www.haskell.org/haskellwiki/Opengl"&gt;привязку&lt;/a&gt;. Теперь начнем создавать каркас приложения на Haskell с ее помощью:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;main = do&lt;br /&gt;     (progname, _) &lt;- getArgsAndInitialize     &lt;br /&gt;     initialDisplayMode $= [DoubleBuffered, WithDepthBuffer]&lt;br /&gt;     initialWindowSize $= (Size 640 640)&lt;br /&gt;     createWindow "Sphere approximation"     &lt;br /&gt;     myInit&lt;br /&gt;     displayCallback $= display&lt;br /&gt;     reshapeCallback $= Just reshape&lt;br /&gt;     mainLoop&lt;br /&gt;&lt;br /&gt;myInit = do     &lt;br /&gt;     ortho (-2.0) 2.0 (-2.0) 2.0 (-10.0) 10.0     &lt;br /&gt;     depthFunc $= Just Less&lt;br /&gt;&lt;br /&gt;display = do     &lt;br /&gt;     clear [ColorBuffer, DepthBuffer]&lt;br /&gt;     -- put your drawing code here&lt;br /&gt;     swapBuffers &lt;br /&gt;&lt;br /&gt;reshape s@(Size w h) = do&lt;br /&gt;     viewport $= (Position 0 0, s)&lt;br /&gt;     postRedisplay NothingNothing&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;В этом наброске в функции &lt;code class="prettyprint lang-hs"&gt;main&lt;/code&gt; производится инициализация приложения, установка глобальных переменных состояния, и создается окно. В &lt;code class="prettyprint lang-hs"&gt;myInit&lt;/code&gt; устанавливается матрица ортогональности. В &lt;code class="prettyprint lang-hs"&gt;initialDisplayMode&lt;/code&gt; мы помещаем настройки, в данном случае мы указали, что хотим использовать двойную буферизацию, а также включить буфер глубины (Z-buffer); если этого не сделать, то едва ли изображение будет выглядеть так, как мы этого ожидаем. Пока ничего отображаться в окне не должно. В функции &lt;code class="prettyprint lang-hs"&gt;display&lt;/code&gt; перед началом рисования нужно очистить буферы цвета и глубины с помощью вызова &lt;code class="prettyprint lang-hs"&gt;clear&lt;/code&gt;, а после этого, поскольку мы использовали двойную буферизацию, переключить буфер с помощью &lt;code class="prettyprint lang-hs"&gt;swapBuffers&lt;/code&gt;.      &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Отображение тетраэдра &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Теперь, после того, как у нас имеется каркас, мы попробуем что-нибудь изобразить. Изобразим тетраэдр. Для этого добавим к коду следующие строки:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;type Gf3 = (GLfloat, GLfloat, GLfloat)&lt;br /&gt;&lt;br /&gt;figCoords :: [Gf3]&lt;br /&gt;figCoords = [    (0.0, 0.0, 1.0),&lt;br /&gt;                 (0.0, 0.942809, -0.333333),&lt;br /&gt;                 (-0.816497, -0.471405, -0.333333),&lt;br /&gt;                 (0.816497, -0.471405, -0.333333)&lt;br /&gt;            ]&lt;br /&gt;&lt;br /&gt;tetrahedron coords = do&lt;br /&gt;     triangle (coords !! 0) (coords !! 1) (coords !! 2)&lt;br /&gt;     triangle (coords !! 3) (coords !! 2) (coords !! 1)&lt;br /&gt;     triangle (coords !! 0) (coords !! 3) (coords !! 1)&lt;br /&gt;     triangle (coords !! 0) (coords !! 2) (coords !! 3)&lt;br /&gt;&lt;br /&gt;triangle (x1, y1, z1) (x2, y2, z2) (x3, y3, z3) = do&lt;br /&gt;     renderPrimitive Triangles $ do&lt;br /&gt;          vertex $ Vertex3 x1 y1 z1&lt;br /&gt;          vertex $ Vertex3 x2 y2 z2&lt;br /&gt;          vertex $ Vertex3 x3 y3 z3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;а функцию &lt;code class="prettyprint lang-hs"&gt;display&lt;/code&gt; приведем к следующему виду:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;display = do&lt;br /&gt;     clear [ColorBuffer, DepthBuffer]&lt;br /&gt;     tetrahedron figCoords&lt;br /&gt;     swapBuffers&lt;br /&gt;&lt;/pre&gt;Поскольку никакого затенения сейчас не используется, и все изображается с применением белой заливки, то вы должны увидеть белый равносторонний треугольник. Эти три вершины формируют основание тетраэдра:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_kyZPuIkJka4/S9glasxCXwI/AAAAAAAAACM/pClS9AmAx0A/s1600/haskell_lit_sphere_00.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 223px; height: 201px;" src="http://3.bp.blogspot.com/_kyZPuIkJka4/S9glasxCXwI/AAAAAAAAACM/pClS9AmAx0A/s320/haskell_lit_sphere_00.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5465159288437759746" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Разбиение тетраэдра&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Имеющийся объект — тетраэдр — не очень-то пока похож на сферу. Поэтому попробуем сделать его чуть более похожим, произведя разбивку его граней.&lt;br /&gt;Идея состоит в делении каждой грани тетраэдра на 4 равных треугольника:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;          /\                  /\&lt;br /&gt;         /  \                /  \&lt;br /&gt;        /    \              /----\&lt;br /&gt;       /      \            / \  / \&lt;br /&gt;      /________\          /___\/___\&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Т. е. три новые точки делят каждую сторону треугольника, на которой они лежат, пополам. Три новых треугольника лежат в плоскости той же грани, которая была разбита. Поэтому необходимо сдвинуть новые вершины на поверхность аппроксимируемой сферы, выполнив нормализацию — не путать с нормалью, к ней мы вернемся чуть позже — их координат. Добавим к программе функцию рекуррентного разбиения треугольника:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;divideTriangle :: Gf3 -&gt; Gf3 -&gt; Gf3 -&gt; Int -&gt; IO ()&lt;br /&gt;divideTriangle p1 p2 p3 0 = triangle p1 p2 p3&lt;br /&gt;divideTriangle a@(x1, y1, z1) b@(x2, y2, z2) c@(x3, y3, z3) ndivisions = do&lt;br /&gt;     divideTriangle a  v1  v2  (ndivisions - 1)&lt;br /&gt;     divideTriangle c  v2  v3  (ndivisions - 1)&lt;br /&gt;     divideTriangle b  v3  v1  (ndivisions - 1)&lt;br /&gt;     divideTriangle v1 v3  v2  (ndivisions - 1)&lt;br /&gt;     where v1 = normalizeCoords ((x1 + x2)/2, (y1 + y2)/2, (z1 + z2)/2)&lt;br /&gt;           v2 = normalizeCoords ((x1 + x3)/2, (y1 + y3)/2, (z1 + z3)/2)&lt;br /&gt;           v3 = normalizeCoords ((x2 + x3)/2, (y2 + y3)/2, (z2 + z3)/2)&lt;br /&gt;&lt;br /&gt;normalizeCoords :: Gf3 -&gt; Gf3&lt;br /&gt;normalizeCoords c@(x, y, z)&lt;br /&gt;     | len &gt; 0   = (x/len, y/len, z/len)&lt;br /&gt;     | otherwise = c&lt;br /&gt;     where len   = sqrt (x*x + y*y + z*z)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;а также модифицируем функции &lt;code class="prettyprint lang-hs"&gt;tetrahedron&lt;/code&gt;, &lt;code class="prettyprint lang-hs"&gt;display&lt;/code&gt; и &lt;code class="prettyprint lang-hs"&gt;main&lt;/code&gt; для учета количества рекуррентных разбиений следующим образом:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;tetrahedron coords ndivisions = do&lt;br /&gt;     divideTriangle (coords !! 0) (coords !! 1) (coords !! 2) ndivisions&lt;br /&gt;     divideTriangle (coords !! 3) (coords !! 2) (coords !! 1) ndivisions&lt;br /&gt;     divideTriangle (coords !! 0) (coords !! 3) (coords !! 1) ndivisions&lt;br /&gt;     divideTriangle (coords !! 0) (coords !! 2) (coords !! 3) ndivisions&lt;br /&gt;&lt;br /&gt;display nDivisions = do&lt;br /&gt;     clear [ColorBuffer, DepthBuffer]&lt;br /&gt;     tetrahedron figCoords nDivisions&lt;br /&gt;     swapBuffers&lt;br /&gt;&lt;br /&gt;main = do&lt;br /&gt;     (progname, _) &lt;- getArgsAndInitialize&lt;br /&gt;     initialDisplayMode $= [DoubleBuffered, WithDepthBuffer]&lt;br /&gt;     initialWindowSize $= (Size 640 640)&lt;br /&gt;     createWindow "Sphere approximation"&lt;br /&gt;     myInit&lt;br /&gt;     let nDivisions = 3&lt;br /&gt;     displayCallback $= (display nDivisions)&lt;br /&gt;     reshapeCallback $= Just reshape&lt;br /&gt;     mainLoop&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;После компиляции и запуска такой программы изображается что-то наподобие этого:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh4.ggpht.com/_kyZPuIkJka4/S9gjzQeq1dI/AAAAAAAAACE/taKg8nxluTM/haskell_lit_sphere_01.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 222px; height: 206px;" src="http://lh4.ggpht.com/_kyZPuIkJka4/S9gjzQeq1dI/AAAAAAAAACE/taKg8nxluTM/haskell_lit_sphere_01.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Держу пари, когда мы увеличим число разбиений и применим затенение, то наш рекуррентно разбитый тетраэдр уже будет не отличить от сферы.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Затенение&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Существует &lt;a href="http://www.compgraphics.info/3D/lighting/shading_model.php"&gt;множество&lt;/a&gt; способов затенения. Одним из самых первых и распространенных был метод плоского затенения, где освещенность производится для полигона (треугольника) целиком. Этот метод хорошо подходит для затенения каких-либо объектов с прямыми гранями, но не совсем хорошо — для неровных поверхностей, поскольку при таком затенении на неровном объекте отчетливо становятся видны грани (&lt;a href="http://en.wikipedia.org/wiki/Mach_bands"&gt;Mach bands&lt;/a&gt;, &lt;a href="http://www.fcenter.ru/online.shtml?articles/hardware/videos/8749"&gt;статья "Как компьютер рисует изображения"&lt;/a&gt;). Тем не менее, нам не важна в данном случае фотореалистичность. Поэтому применим плоское затенение.&lt;br /&gt;&lt;br /&gt;Для определения степени освещенности точки — в нашем случае освещается треугольник — нужно определить косинус угла между нормалью, идущей из этой точки, и вектором идущим из этой точки к точечному источнику света (&lt;a href="http://www.xakep.ru/post/14208/default.asp"&gt;статья в журнале Xakep "Освещение в играх"&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;Чтобы найти нормаль, можно воспользоваться векторным произведением:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;crossProd :: (Num a) =&gt; (a, a, a) -&gt; (a, a, a) -&gt; (a, a, a) -&gt; (a, a, a)&lt;br /&gt;crossProd (x1, y1, z1) (x2, y2, z2) (x3, y3, z3) = (x, y, z)&lt;br /&gt;     where x = (y2 - y1) * (z3 - z1) - (z2 - z1) * (y3 - y1)&lt;br /&gt;           y = (z2 - z1) * (x3 - x1) - (x2 - x1) * (z3 - z1)&lt;br /&gt;           z = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Нахождение косинуса угла между полученной нормалью и вектором к источнику света может замедлить вычисления, поэтому можно воспользоваться свойством скалярного произведения векторов:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;dotProd :: (Num a) =&gt; (a, a, a) -&gt; (a, a, a) -&gt; a&lt;br /&gt;dotProd (x1, y1, z1) (x2, y2, z2) = x1*x2 + y1*y2 + z1*z2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Функцию &lt;code class="prettyprint lang-hs"&gt;triangle&lt;/code&gt; изменим следующим образом:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;triangle :: Gf3 -&gt; Gf3 -&gt; Gf3 -&gt; IO ()&lt;br /&gt;triangle a@(x1, y1, z1) b@(x2, y2, z2) c@(x3, y3, z3) = do&lt;br /&gt;     -- Detrmine a triangle normal&lt;br /&gt;     -- (it's just a direction, not a vector)&lt;br /&gt;     -- Normalized normals are happy normals ;)&lt;br /&gt;     let normal     = normalizeCoords $ crossProd a b c&lt;br /&gt;     let lightPos   = (1.0, 1.0, 1.0::GLfloat)&lt;br /&gt;     let intensity  = 1.0 * dotProd normal lightPos&lt;br /&gt;&lt;br /&gt;     renderPrimitive Triangles $ do&lt;br /&gt;          color $ Color3 intensity intensity intensity&lt;br /&gt;          vertex $ Vertex3 x1 y1 z1&lt;br /&gt;          vertex $ Vertex3 x2 y2 z2&lt;br /&gt;          vertex $ Vertex3 x3 y3 z3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Здесь координаты источника света (1.0, 1.0, 1.0), коэффициент отражающей способности установлен в 1.0, интенсивность отражения изменяется для всех цветовых каналов одинаково. Теперь, если вы запустите программу, то можно будет увидеть следующее изображение (48 треугольников):&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_kyZPuIkJka4/S9go6LJV0tI/AAAAAAAAACo/_cqEFRTlz4o/s1600/haskell_lit_sphere_02.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 230px; height: 216px;" src="http://4.bp.blogspot.com/_kyZPuIkJka4/S9go6LJV0tI/AAAAAAAAACo/_cqEFRTlz4o/s320/haskell_lit_sphere_02.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5465163127703589586" /&gt;&lt;/a&gt;&lt;br /&gt;По-моему, оно походит чем-то на подсвеченную сферу. А если увеличить число разбиений до 8-1, то получится очень реалистичная сфера (49 152 треугольника):&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_kyZPuIkJka4/S9gn9gjB1NI/AAAAAAAAACc/dOa9w71yrbM/s1600/haskell_lit_sphere_03.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 230px; height: 216px;" src="http://2.bp.blogspot.com/_kyZPuIkJka4/S9gn9gjB1NI/AAAAAAAAACc/dOa9w71yrbM/s320/haskell_lit_sphere_03.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5465162085476455634" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://bitbucket.org/yasir/haskell/src/tip/sphere_approx/lit_sphere.hs"&gt;Полный код&lt;/a&gt; приложения на данном этапе.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Параллелизм&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Все действия, приведенные выше, были сделаны, преследуя основную цель — сделать полученный код пригодным для распараллеливания. В противном случае можно было бы просто предоставить все операции по формированию изображения, его затенению и т. д. OpenGL, она это сделала бы гораздо эффективнее, поскольку эти алгоритмы реализованы аппаратно.&lt;br /&gt;&lt;br /&gt;Для Haskell &lt;a href="http://www.haskell.org/haskellwiki/Applications_and_libraries/Concurrency_and_parallelism"&gt;имеются разнообразные библиотеки&lt;/a&gt;, позволяющие сделать приложение многопоточным. Мы воспользуемся вызовом forkIO, и синхронизирующей переменной MVar, которые достаточно давно доступны в &lt;a href="http://ru.wikipedia.org/wiki/GHC"&gt;Glasgow Haskell Compiler&lt;/a&gt;. Импортируем их, добавив следующие строки в начало исходного кода:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;import Control.Concurrent&lt;br /&gt;import Control.Concurrent.MVar&lt;br /&gt;import Control.Monad&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;К текущему моменту в нашей программе можно распараллелить вызовы функции &lt;code class="prettyprint lang-hs"&gt;divideTriangle&lt;/code&gt;, осуществляемые в функции &lt;code class="prettyprint lang-hs"&gt;tetrahedron&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-hs"&gt;&lt;br /&gt;tetrahedron :: [Gf3] -&gt; Int -&gt; IO ()&lt;br /&gt;tetrahedron coords ndivisions = do&lt;br /&gt;     child0 &lt;- thread (coords !! 0) (coords !! 1) (coords !! 2) ndivisions&lt;br /&gt;     child1 &lt;- thread (coords !! 3) (coords !! 2) (coords !! 1) ndivisions&lt;br /&gt;     child2 &lt;- thread (coords !! 0) (coords !! 3) (coords !! 1) ndivisions&lt;br /&gt;     child3 &lt;- thread (coords !! 0) (coords !! 2) (coords !! 3) ndivisions&lt;br /&gt;     ret &lt;- mapM takeMVar [child0, child1, child2, child3]&lt;br /&gt;     return ()&lt;br /&gt;&lt;br /&gt;thread :: Gf3 -&gt; Gf3 -&gt; Gf3 -&gt; Int -&gt; IO (MVar (IO ()))&lt;br /&gt;thread p1 p2 p3 ndivisions = do&lt;br /&gt;     mvar &lt;- newEmptyMVar&lt;br /&gt;     forkIO $ do &lt;br /&gt;          let r = divideTriangle p1 p2 p3 ndivisions&lt;br /&gt;          putMVar mvar r&lt;br /&gt;     return mvar&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Здесь создаются 4 нити, работающие параллельно. В функции &lt;code class="prettyprint lang-hs"&gt;thread&lt;/code&gt; перед созданием нити инициализируется синхронизирующая переменная с помощью вызова &lt;code class="prettyprint lang-hs"&gt;newEmptyMVar&lt;/code&gt;. Потом создается сама нить, которая осуществляет вызов &lt;code class="prettyprint lang-hs"&gt;divideTriangle&lt;/code&gt; и помещает в &lt;code class="prettyprint lang-hs"&gt;MVar&lt;/code&gt; результат, который был возвращен функцией &lt;code class="prettyprint lang-hs"&gt;divideTriangle&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Само по себе содержимое &lt;code class="prettyprint lang-hs"&gt;MVar&lt;/code&gt; нас не интересуит, оно лишь служит сигналом нити, выполняющей вызов функции &lt;code class="prettyprint lang-hs"&gt;takeMVar&lt;/code&gt;, к тому, что можно продолжать дальнейшие вычисления, т. е. функция &lt;code class="prettyprint lang-hs"&gt;takeMVar&lt;/code&gt; не вернет управление, пока в &lt;code class="prettyprint lang-hs"&gt;MVar&lt;/code&gt; не будет помещено какое-либо значение. Таким образом, в списке &lt;code class="prettyprint lang-hs"&gt;ret&lt;/code&gt; будут находиться значения, которые нити поместили с помощью вызова &lt;code class="prettyprint lang-hs"&gt;putMVar&lt;/code&gt;, и программа не продолжит работу, пока все нити не поместят эти значения. Вызов &lt;code class="prettyprint lang-hs"&gt;return ()&lt;/code&gt; позволяет вернуть значение, которое соответствует типу функции &lt;code class="prettyprint lang-hs"&gt;tetrahedron :: [Gf3] -&gt; Int -&gt; IO ()&lt;/code&gt;, т. е. пустой кортеж.&lt;br /&gt;&lt;br /&gt;Но если вы скомпилируете и запустите эту программу, то прежней сферы вы не увидите! Предлагаю вам подумать, почему так происходит (вспомните свойства языка Haskell). &lt;a href="http://bitbucket.org/yasir/haskell/src/tip/sphere_approx/lit_sphere_thr.hs"&gt;Полный код&lt;/a&gt; программы на данном этапе.&lt;br /&gt;&lt;br /&gt;Если вы не догадались, почему ничего не изображается, и что нужно сделать, чтобы это исправить, то можете прочитать &lt;a href="http://bitbucket.org/yasir/haskell/src/tip/sphere_approx/lit_sphere_thr_final.hs"&gt;этот код&lt;/a&gt;. В нем я установил постоянную интенсивность по зеленому каналу, чтобы можно было наблюдать также и неосвещенную сторону сферы, и изменил последнюю строчку в функции &lt;code class="prettyprint lang-hs"&gt;tetrahedron&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Заключение&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;В статье была рассмотрена техника распараллеливания вычислений на языке Haskell, продемонстрировано использование привязки к OpenGL. Как говорит &lt;a href="http://www.haskell.org/haskellwiki/Applications_and_libraries/Concurrency_and_parallelism#Concurrency"&gt;эта статья&lt;/a&gt;, Haskell производит паралельные вычисления &lt;a href="http://shootout.alioth.debian.org/gp4/benchmark.php?test=threadring&amp;lang=all"&gt;очень быстро&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Надеюсь, вам понравилось.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Литература&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Эйнджел, Эдвард. Интерактивная компьютерная графика. Вводный курс на базе OpenGL, 2 изд.: Пер. с англ. — М.: Издательский дом "Вильямс", 2001. — 592 с.: ил. — Парал. тит. англ. ISBN 5-8459-0209-6 (рус.)&lt;br /&gt;&lt;br /&gt;P. S. Возможно, кто-то посоветует способ сокрытия под кат текста так, чтобы это действительно работало в т. ч. и на &lt;a href="http://fprog.ru/planet/"&gt;Russian Lambda Planet&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1973090003148579772-906345667832597997?l=arsanukaev.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://arsanukaev.blogspot.com/feeds/906345667832597997/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://arsanukaev.blogspot.com/2010/04/haskell-sphere-triangulation-opengl.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/906345667832597997'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/906345667832597997'/><link rel='alternate' type='text/html' href='http://arsanukaev.blogspot.com/2010/04/haskell-sphere-triangulation-opengl.html' title='Haskell и параллелизм: Триангуляция сферы параллельным рекуррентным разбиением тетраэдра с применением плоского затенения'/><author><name>Yasir Arsanukaev</name><uri>https://profiles.google.com/103729008772345894369</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_kyZPuIkJka4/S9glasxCXwI/AAAAAAAAACM/pClS9AmAx0A/s72-c/haskell_lit_sphere_00.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1973090003148579772.post-565911471000214305</id><published>2010-03-27T23:11:00.010+11:00</published><updated>2010-03-28T04:06:38.871+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='functional'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='fprog'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrent'/><title type='text'>Распределенные математические вычисления на языке Erlang</title><content type='html'>В статье иллюстрируется пример настройки окружения и серверов для возможности проведения распределенных вычислений на языке Erlang, а также их осуществление. В качестве примера предлагается перемножение матрицы на вектор с использованием пула &lt;a href="http://www.erlang.org/doc/man/pool.html"&gt;pool(3)&lt;/a&gt; серверов Erlang. В данном случае пул состоит из 3 машин под управлением ОС FreeBSD.&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Введение&lt;/span&gt;&lt;br /&gt;Многие из вас, наверняка, уже использовали вызов spawn[_link] в своих приложениях, но немногие, как я заметил, знают о более лаконичном -- но также и более функциональном -- его аналоге pspawn[_link]. Попробуем заполнить этот пробел, решив простую математическую задачу.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Подготовка&lt;/span&gt;&lt;br /&gt;Настройки серверов для хоста с двумя тюрьмами, расположенными на нем:&lt;br /&gt;&lt;pre style="background-color: black; color: rgb(255, 255, 255); font-weight: bold;"&gt;[root@conductor ~/crunching]# uname -prs&lt;br /&gt;FreeBSD 7.1-RELEASE i386&lt;br /&gt;[root@conductor ~/crunching]# grep '\.loc' /etc/hosts&lt;br /&gt;192.168.32.40&lt;br /&gt;192.168.32.201&lt;br /&gt;192.168.32.202&lt;br /&gt;[root@conductor ~/crunching]# jls&lt;br /&gt;JID  IP Address      Hostname&lt;br /&gt; 1  192.168.32.201  j1.loc&lt;br /&gt; 2  192.168.32.202  j2.loc&lt;br /&gt;[root@conductor ~/crunching]# ifconfig em0 | grep inet&lt;br /&gt;inet 192.168.32.40 netmask 0xffffff00 broadcast 192.168.32.255&lt;br /&gt;inet 192.168.32.201 netmask 0xffffff00 broadcast 192.168.32.201&lt;br /&gt;inet 192.168.32.202 netmask 0xffffff00 broadcast 192.168.32.202&lt;br /&gt;[root@conductor ~/crunching]# cat ~/.hosts.erlang&lt;br /&gt;'j1.loc'&lt;br /&gt;'j2.loc'&lt;br /&gt;[root@conductor ~/crunching]# pkg_info -E erlang*&lt;br /&gt;[root@conductor ~/crunching]#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Запуск мастер-ноды poolmaster@conductor Erlang на хосте, затем запуск пула ведомых нод (slaves) в тюрьмах. Возвращение списка адресов машин, составляющих пул:&lt;br /&gt;&lt;pre style="background-color: black; color: rgb(255, 255, 255); font-weight: bold;"&gt;[root@conductor ~/crunching]# erl -sname poolmaster -rsh ssh&lt;br /&gt;Erlang R13B01 (erts-5.7.2) [source] [rq:1] [async-threads:0] [hipe] [kernel-poll:false]&lt;br /&gt;&lt;br /&gt;Eshell V5.7.2  (abort with ^G)&lt;br /&gt;(poolmaster@conductor)1&gt; c(pwnd).&lt;br /&gt;{ok,pwnd}&lt;br /&gt;(poolmaster@conductor)2&gt; pool:start(crunchpool).&lt;br /&gt;[crunchpool@j1,crunchpool@j2]&lt;br /&gt;(poolmaster@conductor)3&gt; pool:get_nodes().&lt;br /&gt;[poolmaster@conductor,crunchpool@j1,crunchpool@j2]&lt;br /&gt;(poolmaster@conductor)4&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Удаленное подключение к shell Erlang-ноды, находящейся на другой машине (в данном случае -- в тюрьме), и исполнение произвольного кода:&lt;br /&gt;&lt;pre style="background-color: black; color: rgb(255, 255, 255); font-weight: bold;"&gt;[root@conductor ~/crunching]# erl -sname client -remsh crunchpool@j1&lt;br /&gt;Erlang R13B01 (erts-5.7.2) [source] [rq:1] [async-threads:0] [hipe] [kernel-poll:false]&lt;br /&gt;&lt;br /&gt;Eshell V5.7.2  (abort with ^G)&lt;br /&gt;(crunchpool@j1)1&gt; os:cmd("hostname").&lt;br /&gt;"j1.loc\n"&lt;br /&gt;(crunchpool@j1)2&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;На всех узлах пула (в т. ч. на ведущем  (master)) запускаются Erlang Port Mapper Daemon (epmd(1)) и beam(1):&lt;br /&gt;&lt;pre style="background-color: black; color: rgb(255, 255, 255); font-weight: bold;"&gt;&lt;br /&gt;[root@conductor ~] jexec 1 sockstat -4&lt;br /&gt;USER    COMMAND   PID   FD    PROTO    LOCAL ADDRESS         FOREIGN ADDRESS&lt;br /&gt;root    beam      2863  7     tcp4     192.168.32.201:63037  *:*&lt;br /&gt;root    beam      2863  8     tcp4     192.168.32.201:50707  192.168.32.201:4369&lt;br /&gt;root    beam      2863  10    tcp4     192.168.32.201:54136  192.168.32.40:61473&lt;br /&gt;root    beam      2863  13    tcp4     192.168.32.201:63037  192.168.32.202:61991&lt;br /&gt;root    epmd      1143  3     tcp4     192.168.32.201:4369   *:*&lt;br /&gt;root    epmd      1143  4     tcp4     192.168.32.201:4369   192.168.32.201:50707&lt;br /&gt;root    inetd     861   5     tcp4     192.168.32.201:22     *:*&lt;br /&gt;root    sendmail  837   5     tcp4     192.168.32.201:25     *:*&lt;br /&gt;root    syslogd   777   6     udp4     192.168.32.201:514    *:*&lt;br /&gt;[root@conductor ~] jexec 2 sockstat -4&lt;br /&gt;USER    COMMAND   PID   FD    PROTO    LOCAL ADDRESS         FOREIGN ADDRESS&lt;br /&gt;root    beam      2880  7     tcp4     192.168.32.202:64315  *:*&lt;br /&gt;root    beam      2880  8     tcp4     192.168.32.202:57386  192.168.32.202:4369&lt;br /&gt;root    beam      2880  10    tcp4     192.168.32.202:50430  192.168.32.40:61473&lt;br /&gt;root    beam      2880  11    tcp4     192.168.32.202:61991  192.168.32.201:63037&lt;br /&gt;root    epmd      1160  3     tcp4     192.168.32.202:4369   *:*&lt;br /&gt;root    epmd      1160  4     tcp4     192.168.32.202:4369   192.168.32.202:57386&lt;br /&gt;root    inetd     1024  5     tcp4     192.168.32.202:22     *:*&lt;br /&gt;root    sendmail  1002  4     tcp4     192.168.32.202:25     *:*&lt;br /&gt;root    syslogd   942   6     udp4     192.168.32.202:514    *:*&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Запуск процесса распределенного вычисления, оканчивающегося выдачей результатов. Получение списка присоединенных нод, остановка пула, выход из Erlang shell:&lt;br /&gt;&lt;pre style="background-color: black; color: rgb(255, 255, 255); font-weight: bold;"&gt;(poolmaster@conductor)13&gt; pwnd:vStart[[1,4,-2,1]], [9,1,11,1], [3,-3,5,1], [1,1,1,1]], [1,5,1,-3]).&lt;br /&gt;ResL = [{module,pwnd},{module,pwnd},{module,pwnd}]&lt;br /&gt;PIDS = [&lt;0.141.0&gt;,&lt;8877.51.0&gt;,&lt;8838.51.0&gt;,&lt;0.142.0&gt;]&lt;br /&gt;poolmaster@conductor: [&lt;0.141.0&gt;/1] V1=[1,4,-2,1] * V2=[1,5,1,-3] == [16,0,0,0]&lt;br /&gt;poolmaster@conductor: [&lt;0.142.0&gt;/4] V1=[1,1,1,1] * V2=[1,5,1,-3] == [0,0,0,4]&lt;br /&gt;crunchpool@j1: [&lt;8838.51.0&gt;/3] V1=[3,-3,5,1] * V2=[1,5,1,-3] == [0,0,-10,0]&lt;br /&gt;crunchpool@j2: [&lt;8877.51.0&gt;/2] V1=[9,1,11,1] * V2=[1,5,1,-3] == [0,22,0,0]&lt;br /&gt;[16,22,-10,4]&lt;br /&gt;(poolmaster@conductor)14&gt; nodes().&lt;br /&gt;[crunchpool@j1,crunchpool@j2]&lt;br /&gt;(poolmaster@conductor)15&gt; pool:stop().&lt;br /&gt;stopped&lt;br /&gt;(poolmaster@conductor)16&gt; nodes().&lt;br /&gt;[]&lt;br /&gt;(poolmaster@conductor)17&gt; q().&lt;br /&gt;ok&lt;br /&gt;(poolmaster@conductor)18&gt;&lt;br /&gt;[root@conductor ~/crunching]#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;В примере выше задан ключ -rsh ssh, сообщающий Erlang VM, что доступ к другим нодам должен осуществляться по протоколу SSH. В этой демонстрации доступ к узлам производится по SSH с применением аутентификации через публичный ключ. Можно также использовать другие методы аутентификации, например, Kerberos. Мастер-нода автоматически запускает Erlang VM удаленно на каждом из ведомых узлов пула. Т. о. налицо простота ввода новых мощностей в кластер. В идеале можно было бы свести к минимуму объем работы по конфигурированию окружения (ОС, VM etc.) и построить кластер на основе diskless узлов, загружающихся через LAN Boot, тем самым повысив надежность.&lt;br /&gt;&lt;br /&gt;Исходя из последнего вывода на stdout смею предположить, что запуск (pspawn) процессов в пуле в отсутствие нагрузки происходит на разных серверах циклически (in a round-robin fashion), в противном случае учитывается загруженность каждого индивидуального вычислительного узла и производится балансировка нагрузки, т. е. запуск каждого нового процесса (именно Erlang процесса в Erlang VM, а не процесса ОС, aka "green process") на одном из наименее загруженных узлов пула.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Исходный код&lt;/span&gt;&lt;br /&gt;&lt;pre style="background-color: black; color: rgb(51, 255, 51); font-weight: bold;"&gt;-module(pwnd).&lt;br /&gt;&lt;br /&gt;-export([startSay/0, say/2]).&lt;br /&gt;-export([vSum/2, vMul/2, vVec/3, vStart/2, vSpawn/1,&lt;br /&gt;       vReduce/2, vScatter/5, vWorker/1]).&lt;br /&gt;&lt;br /&gt;-import(lists).&lt;br /&gt;&lt;br /&gt;vSum([], []) -&gt;&lt;br /&gt;   [];&lt;br /&gt;vSum([H1 | T1], [H2 | T2]) -&gt;&lt;br /&gt;   [H1 + H2] ++ vSum(T1, T2).&lt;br /&gt;&lt;br /&gt;vMul([], []) -&gt;&lt;br /&gt;   [];&lt;br /&gt;vMul([H1 | T1], [H2 | T2]) -&gt;&lt;br /&gt;   [H1 * H2] ++ vMul(T1, T2).&lt;br /&gt;&lt;br /&gt;vVec(N, I, EL)-&gt;&lt;br /&gt;   ZERO = lists:map(fun(X)-&gt;0*X end, lists:seq(1, N)),&lt;br /&gt;   lists:sublist(ZERO, I - 1) ++&lt;br /&gt;   [EL] ++ lists:sublist(ZERO, N - I).&lt;br /&gt;&lt;br /&gt;vWorker(PID) -&gt;&lt;br /&gt;   receive&lt;br /&gt;        {V1, V2, N} -&gt;&lt;br /&gt;             P = vVec(length(V1),N,lists:sum(vMul(V1,V2))),&lt;br /&gt;             io:format("~w: [~w/~w] V1=~w * V2=~w == ~w~n",&lt;br /&gt;             [node(), self(), N, V1, V2, P]),&lt;br /&gt;             PID ! {vector, P}&lt;br /&gt;   end.&lt;br /&gt;&lt;br /&gt;vScatter([], _V, [], 0, _TOT) -&gt;&lt;br /&gt;   [];&lt;br /&gt;vScatter([MH | MT], V, [PID | REST], N, TOT) -&gt;&lt;br /&gt;   PID ! {MH, V, TOT - N + 1},&lt;br /&gt;   vScatter(MT, V, REST, N - 1, TOT).&lt;br /&gt;&lt;br /&gt;vReduce(RES, 0) -&gt;&lt;br /&gt;   RES;&lt;br /&gt;vReduce(RES, N) -&gt;&lt;br /&gt;   receive&lt;br /&gt;        {vector, V} -&gt;&lt;br /&gt;             vReduce(vSum(RES, V), N - 1)&lt;br /&gt;   end.&lt;br /&gt;&lt;br /&gt;vSpawn([]) -&gt;&lt;br /&gt;   [];&lt;br /&gt;vSpawn([_H | T]) -&gt;&lt;br /&gt;   [pool:pspawn_link(&lt;br /&gt;         pwnd,&lt;br /&gt;         vWorker,&lt;br /&gt;         [self()]&lt;br /&gt;   )] ++ vSpawn(T).&lt;br /&gt;&lt;br /&gt;vStart(M, V) -&gt;&lt;br /&gt;   % Distribute code&lt;br /&gt;   {Mod, Bin, Filename} = code:get_object_code(?MODULE),&lt;br /&gt;   {ResL, []} = rpc:multicall(&lt;br /&gt;        code,&lt;br /&gt;        load_binary,&lt;br /&gt;        [Mod, Filename, Bin]&lt;br /&gt;   ),&lt;br /&gt;   io:format("ResL = ~w~n", [ResL]),&lt;br /&gt;   ZERO = lists:map(fun(X)-&gt;0*X end,&lt;br /&gt;        lists:seq(1, length(V))&lt;br /&gt;   ),&lt;br /&gt;   PIDS = vSpawn(lists:seq(1, length(V))),&lt;br /&gt;   io:format("PIDS = ~w~n", [PIDS]),&lt;br /&gt;   vScatter(M, V, PIDS, length(PIDS), length(PIDS)),&lt;br /&gt;   vReduce(ZERO, length(V)).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Заключение&lt;br /&gt;&lt;/span&gt;Таким образом, отредактировав всего пару конфигурационных файлов и заменив spawn на pspawn, мы получили гибкое распределенное приложение, способное реагировать на изменения кода.&lt;br /&gt;Для повышения общей производительности есть много вариантов. Например, компиляция BEAM модулей с нативным кодом (HiPE, изначально являлся проектом Upsala University). На тестовой non-SMP одноядерной платформе на базе Celeron 2400 MHz производительность перемножения векторов с количеством элементов ~108 при запуске HiPE модуля увеличилась на 30%. Есть возможность использования портов и драйверов (&lt;a href="http://www.erlang.org/doc/tutorial/c_portdriver.html"&gt;ports and linked-in drivers&lt;/a&gt;) для запуска приложений, написанных на С и других языках. Можно задействовать &lt;a href="http://en.wikipedia.org/wiki/BLAS"&gt;BLAS&lt;/a&gt;. Есть и другие варианты оптимизации.&lt;br /&gt;&lt;br /&gt;Ниже, в коде, функция vStart/2 использует автоматический механизм передачи и загрузки модулей (двоичного кода) на узлы кластера. Поэтому отпадает необходимость при изменении программы распространять ее на каждую машину вручную. Кроме того в Erlang реализована возможность горячей замены кода (hotswap) без остановки выполнения приложения, при этом в памяти может существовать 2 версии модуля. Это позволяет существенно снизить или совсем исключить простои, которые являются необходимой мерой при разработке приложений, например, на С, Java и т. д. (&lt;a href="http://www.erlang.org/doc/"&gt;подробнее&lt;/a&gt;). Некоторые системы, написанные разными разработчиками на Erlang, работают годами и регулярно обновляются.&lt;br /&gt;&lt;br /&gt;В 2005 году был представлен проект ERESYE, позволяющий программировать Artificial Intelligence (искусственную разумность:) и экспертные системы в Erlang. Есть &lt;a href="http://kingping.blinkenshell.org/Expert_system_in_Erlang__the_Domain_of_Transport.html"&gt;пример использования&lt;/a&gt; этой технологии.&lt;br /&gt;&lt;br /&gt;Т. о. можно наблюдать преимущества функционального программирования в среде Erlang/OTP, в котором за долгую историю языка воплощено и продолжает активно внедряться множество продвинутых технологий, которым даже подражают другие языки, например, Scala (&lt;a href="http://yarivsblog.com/articles/2008/05/18/erlang-vs-scala/"&gt;Erlang vs. Scala&lt;/a&gt;) и т. д.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Trivia&lt;/span&gt;&lt;br /&gt;При загрузке кода в ваш редактор отступы могут сбиться. Для vim в командном режиме перейдите в начало файла (gg) и наберите на клавиатуре команду =G&lt;br /&gt;Это откорректирует отступы.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Источники&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://plm.dia.uniroma3.it/milicchio/wp-content/uploads/2009/03/erlang-walkthrough.pdf"&gt;Giving a taste of a parallelization&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.erlang.org/workshop/2008/Sess23.pdf"&gt;High performance technical computing with erlang&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1973090003148579772-565911471000214305?l=arsanukaev.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://arsanukaev.blogspot.com/feeds/565911471000214305/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://arsanukaev.blogspot.com/2010/03/erlang.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/565911471000214305'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1973090003148579772/posts/default/565911471000214305'/><link rel='alternate' type='text/html' href='http://arsanukaev.blogspot.com/2010/03/erlang.html' title='Распределенные математические вычисления на языке Erlang'/><author><name>Yasir Arsanukaev</name><uri>https://profiles.google.com/103729008772345894369</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>
