<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>developers.org.ua &#187; ADEpt</title>
	<atom:link href="http://www.developers.org.ua/archives/author/adept/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.developers.org.ua</link>
	<description>сообщество программистов</description>
	<pubDate>Mon, 08 Sep 2008 13:11:32 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.5.1</generator>
	<language>en</language>
			<item>
		<title>Видео со встречи developers.org.ua 8.11.2007 г.</title>
		<link>http://www.developers.org.ua/archives/adept/2007/11/11/dou-meetup-2-video/</link>
		<comments>http://www.developers.org.ua/archives/adept/2007/11/11/dou-meetup-2-video/#comments</comments>
		<pubDate>Sun, 11 Nov 2007 12:20:54 +0000</pubDate>
		<dc:creator>ADEpt</dc:creator>
		
		<category><![CDATA[Встречи ДОУ]]></category>

		<category><![CDATA[События]]></category>

		<guid isPermaLink="false">http://www.developers.org.ua/archives/adept/2007/11/11/dou-meetup-2-video/</guid>
		<description><![CDATA[Наконец у меня дошли руки пережать видео, записанное в четверг на встрече developers.org.ua, а ShaDOw любезно предоставил место, куда его можно выложить.
Видео выложено в двух вариантах - 100 Mb и 250 Mb. Файлы отличаются только степенью сжатия (битрейт 100 и 330 kbps соответственно), все остальные параметры у них одинаковы - 720&#215;576, кодек h264, аудио в [...]]]></description>
			<content:encoded><![CDATA[<p>Наконец у меня дошли руки пережать видео, записанное в четверг на встрече developers.org.ua, а ShaDOw любезно предоставил место, куда его можно выложить.</p>
<p>Видео выложено в двух вариантах - <a href="http://shadow.in.ua/dou-2/dou-meeting-2-100mb.avi">100 Mb</a> и <a href="http://shadow.in.ua/dou-2/dou-meeting-2-250mb.avi">250 Mb</a>. Файлы отличаются только степенью сжатия (битрейт 100 и 330 kbps соответственно), все остальные параметры у них одинаковы - 720&#215;576, кодек h264, аудио в mp3 (48k). </p>
<p>Я пробовал заливать результат на Google Video, но после тамошнего пережатия картинка получается &#8220;нечитаемой&#8221;. Если кто владеет соответствующим кунг-фу - я буду рад услышать практические советы о том, как готовить подобные ролики для Google Video.</p>
<p>PS<br />
Да, если кому-то хочется пятиканального звука и видео с &#8220;DVD-качеством&#8221; (MPEG2, 9000 kbps) - оригинальными материалами можно разжиться у меня, Макса Ищенко и ShaDOw.</p>
<br/><a href="http://www.developers.org.ua/archives/adept/2007/11/11/dou-meetup-2-video/#ratings">Оценить статью на сайте</a>.

<hr/>
<strong>Новое:</strong> мы запустили 
<a href="http://www.developers.org.ua/forum/">ФОРУМ</a>!
<hr/>
Свежая вакансия:


<a href="http://www.developers.org.ua/jobboard/739?ref=dou_rss">Senior Java Developer (MGS - 101)</a>
  (MiraSoft Group)

]]></content:encoded>
			<wfw:commentRss>http://www.developers.org.ua/archives/adept/2007/11/11/dou-meetup-2-video/feed/</wfw:commentRss>
<enclosure url="http://shadow.in.ua/dou-2/dou-meeting-2-100mb.avi" length="101569100" type="video/x-msvideo" />
<enclosure url="http://shadow.in.ua/dou-2/dou-meeting-2-250mb.avi" length="248530272" type="video/x-msvideo" />
		</item>
		<item>
		<title>Играем в Ним при помощи ленивых вычислений и бесконечных списков</title>
		<link>http://www.developers.org.ua/archives/adept/2007/08/12/playing-wythoffs-nim/</link>
		<comments>http://www.developers.org.ua/archives/adept/2007/08/12/playing-wythoffs-nim/#comments</comments>
		<pubDate>Sun, 12 Aug 2007 18:43:59 +0000</pubDate>
		<dc:creator>ADEpt</dc:creator>
		
		<category><![CDATA[Статьи]]></category>

		<category><![CDATA[ФП]]></category>

		<guid isPermaLink="false">http://www.developers.org.ua/archives/adept/2007/08/12/playing-wythoffs-nim/</guid>
		<description><![CDATA[Добрый вечер.
Продолжая (после долгого перерыва) серию статей про ФП, я решил написать о том, как ленивая модель вычислений позволяет просто и элегантно работать с бесконечными списками. 
Зачем это нужно? Допустим, решение какой-то задачи требует обработки нескольких массивов/списков/наборов данных. Если мы описываем решение в терминах операций над конкретными элементами (&#8221;a[i]=b[j]+c[j-i+1,j]&#8220;), у нас всегда есть риск упустить [...]]]></description>
			<content:encoded><![CDATA[<p>Добрый вечер.</p>
<p>Продолжая (после долгого перерыва) серию статей про ФП, я решил написать о том, как ленивая модель вычислений позволяет просто и элегантно работать с бесконечными списками. </p>
<p><b>Зачем это нужно?</b> Допустим, решение какой-то задачи требует обработки нескольких массивов/списков/наборов данных. Если мы описываем решение в терминах операций над конкретными элементами (&#8221;a[i]=b[j]+c[j-i+1,j]&#8220;), у нас всегда есть риск упустить какой-то частный случай или совершить простейшую (и от этого вдвойне досадную) ошибку. Все наверняка хоть раз да и встречались с классическими ошибками типа &#8220;на единичку больше/меньше&#8221; и &#8220;выход за границы массива&#8221;.</p>
<p>Если же мы сумеем сформулировать решение в терминах операций над &#8220;целыми&#8221; списками/массивами, мы не только уменьшаем описанный риск, но и, как правило, получаем более короткий и читаемый код. Впрочем, довольно вступлений, перейдем к сути.<br />
Для иллюстрации будет использоваться язык <a href="www.haskell.org">Haskell</a>, для полного понимания происходящего полезно экспериментировать с приводимым кодом в какой-нибудь интерактивной среде (<a href="http://www.haskell.org/ghc/">GHC</a>, <a href="http://www.cs.uu.nl/helium/">Helium</a> или <a href="http://www.haskell.org/hugs">Hugs</a>).</p>
<p><b>Задача:</b> реализовать оптимальную стратегию игры в <a href="http://en.wikipedia.org/wiki/Wythoff's_game">Wythoff&#8217;s game</a>. Описание игры &#8220;на пальцах&#8221; выглядит так: представим себе шахматную доску, бесконечную в направлении вправо-вниз. На какой-то ее клетке стоит ферзь. Играют двое. На своем ходу игрок может передвинуть ферзя (по обычным правилам) на любую клетку, но только в направлении влево или вверх (или влево-вверх). Выигрывает тот, кто поставит ферзя в левый верхний угол доски.</p>
<p><b>Другое описание</b> этой же игры выглядит так: есть две кучки одинаковых предметов. На своем ходу игрок может взять произвольное кол-во предметов из любой кучки, или же из обоих кучек сразу. Выигрывает тот, кто возьмет последний предмет. Игра выглядит детской и простой, но на самом деле является важным инструментом в теории игр: к ней и ее ближайшему родственнику - классической игре в Ним - сводится целый класс т.н. комбинаторных игр и изучению ее свойств посвящено множество книг и статей.</p>
<p><b>Выигрышная стратегия</b> заключается в том, чтобы ставить ферзя только на такие (оптимальные) позиции, из которых оппонент, во-первых, не сможет закончить игру, и, во-вторых, не сможет сделать ход в какую-то другую оптимальную позицию, обладающую такими же свойствами. Если пронумеровать строки и столбцы шахматной доски, начиная с нуля, то координаты оптимальных позиций будут таковы (за подробным объяснением см. <a href="http://www.cut-the-knot.org/pythagoras/withoff.shtml">сюда</a>):</p>
<table border="1">
<tr>
<td>Номер позиции (n)</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>10</td>
<td>11</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
</tr>
<tr>
<td>Строка</td>
<td>1</td>
<td>3</td>
<td>4</td>
<td>6</td>
<td>8</td>
<td>9</td>
<td>11</td>
<td>12</td>
<td>14</td>
<td>16</td>
<td>17</td>
<td>19</td>
<td>21</td>
<td>22</td>
<td>24</td>
</tr>
<tr>
<td>Столбец</td>
<td>2</td>
<td>5</td>
<td>7</td>
<td>10</td>
<td>13</td>
<td>15</td>
<td>18</td>
<td>20</td>
<td>23</td>
<td>26</td>
<td>28</td>
<td>31</td>
<td>34</td>
<td>36</td>
<td>39</td>
</tr>
</table>
<p>Если мы каким-то образом вычислили список координат оптимальных позиций (назовем его &#8220;winning_positions&#8221;, предполагая, что он выглядит вот так: [(1,2),(3,5),(4,7),...]), то поиск оптимального хода реализуется &#8220;в лоб&#8221; (да простят меня за английские комментарии):</p>
<pre><code>
module Main where

import Data.List (delete)

-- | Find optimal move (da,db) for game position (a,b).
-- Here, `da' and `db' are numbers that should be
-- substracted from `a' and `b', respectively.
move a b = move' a b winning_positions

-- | Find position in the list of winning positions that
-- could be reached from (a,b) by either:
-- 1)substracting solely from `a' or `b'
-- 2)substracting simultaneously from `a' and `b'
move' a b ((x,y):rest)
  | a == b = (a,b)
  | a == x = (0,b-y)
  | a == y = (0,b-x)
  | b == y = (a-x,0)
  | b == x = (a-y,0)
  | a-x == b-y = (a-x,a-x)
  | a-y == b-x = (a-y,a-y)
  | otherwise = move' a b rest

main = loop
loop =
  do putStrLn "Enter two number that describe game position and press Enter"
     l &lt;- getLine
     let [a,b] = map read $ words l
     putStr &#8220;My move: &#8221;
     putStrLn $ show $ move a b
     loop
</code></pre>
<p>Дело за малым - получить список оптимальных позиций. Можно увидеть (или сходить по приведенной мною ссылке и почитать <img src='http://www.developers.org.ua/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ), что &#8220;строка&#8221; является суммой &#8220;порядкового номера&#8221; позиции и &#8220;столбца&#8221;, а столбец, в свою очередь, является минимальным числом из ряда [1..], которое еще не было использовано в качестве &#8220;строки&#8221; или &#8220;столбца&#8221;.</p>
<p>Так и запишем (я постарался снабдить листинги гиперссылками на примеры использования задействованных функций, чтобы не перегружать текст примерами и объяснениям, так что если вы не знаете, что делают ($), map, zipWith и т.п. - кликайте на ссылки и читайте):</p>
<pre><code>
column = <a href="http://www.zvon.org/other/haskell/Outputprelude/zipWith_f.html">zipWith</a> (+) [1..] row
row    = not_yet_used

winning_positions = <a href="http://www.zvon.org/other/haskell/Outputprelude/zip_f.html">zip</a> row column
</code></pre>
<p>Осталось определить &#8220;not_yet_used&#8221; - список, в котором на позиции N стоит <u>минимальное</u> число, не использованное в N-1 предыдущих оптимальных позициях. Очевидно, что можно взять список [1..] и, двигаясь последовательно по списку выигрышных позиций, вычеркивать из [1..] использованные строки и столбцы. После каждого вычеркивания &#8220;выписывать отдельно&#8221; минимальное неиспользованное число (оно будет первым в оставшемся списке) и двигаться дальше. Код практически дословно повторяет сказанное:</p>
<pre><code>
not_yet_used = <a href="http://www.zvon.org/other/haskell/Outputprelude/map_f.html">map</a> <a href="http://www.zvon.org/other/haskell/Outputprelude/head_f.html">head</a> <a href="http://www.zvon.org/other/haskell/Outputprelude/D_o.html">$</a> <a href="http://www.zvon.org/other/haskell/Outputprelude/scanl_f.html">scanl</a> remove [1..] winning_positions
  where remove lst (a,b) = <a href="http://haskell.org/ghc/docs/latest/html/libraries/base/Data-List.html#v%3Adelete">delete</a> b $ delete a lst
</code></pre>
<p>Вот и все, <b>алгоритм реализован</b>, и вы можете проверить, что, например, &#8220;move 12 6&#8243; выдает правильный ход &#8220;(2,0)&#8221;, переводящий игрока в оптимальную позицию (10,6).</p>
<p>Теперь приглядимся повнимательнее к тому, как функции используют друг друга. Для вычисления &#8220;column&#8221; используется &#8220;[1..]&#8221; и &#8220;row&#8221;. Для вычисления &#8220;row&#8221; используется &#8220;not_yet_used&#8221;. Для нее, в свою очередь, нужны &#8220;winning_positions&#8221;. Для вычисления которой нужны &#8230; все те же &#8220;row&#8221; и &#8220;column&#8221;. Всё, круг замкнулся (см. рисунок).<br />
<a href='http://www.developers.org.ua/wordpress/wp-content/uploads/2007/08/02_deps.png' title='Dependencies between functions in Nim implementation'><img src='http://www.developers.org.ua/wordpress/wp-content/uploads/2007/08/02_deps.png' alt='Dependencies between functions in Nim implementation' /></a></p>
<p>Получилась эдакая змея, кусающая себя за хвост. Простите, спросите вы, но как все это может работать?</p>
<p>Разгадка в том, что хоть все функции и используют для вычислений бесконечные списки и возвращают бесконечные же списки результатов, но делают они это лениво - т.е. не потребляя из бесконечного списка больше элементов, чем реально необходимо для вычисления следующего элемента результата.</p>
<p>А запускается вся эта карусель тогда, когда &#8220;scanl&#8221; из функции &#8220;not_yet_used&#8221; выдает на-гора начало своего результата - список [1..]. Первый элемент этого списка тут же попадает в значение &#8220;row&#8221;, после чего можно вычислить первый элемент результата &#8220;column&#8221;, после чего можно вычислить первую пару из &#8220;winning_positions&#8221;, которую можно использовать для вычисления следующего значения &#8220;not_yet_used&#8221; и т.д. и т.п (см. схему после этого абзаца). При этом вам вовсе не обязательно самому рисовать подобные картинки и прослеживать зависимости - за вас это сделает компилятор <img src='http://www.developers.org.ua/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p><a href='http://www.developers.org.ua/wordpress/wp-content/uploads/2007/08/03_eval.png' title='Nim evaluation scheme'><img src='http://www.developers.org.ua/wordpress/wp-content/uploads/2007/08/03_eval.png' alt='Nim evaluation scheme' /></a></p>
<p>Получившийся в результате список оптимальных позиций является бесконечным во всех практических смыслах этого слова. Например, если попытаться его напечатать (&#8221;print winning_positions&#8221;), то среда исполнения будет выводить оптимальные позиции до тех пор, пока вы ее не прервете.</p>
<p>Причем, для операций с бесконечными списками вовсе не требуется бесконечно много памяти <img src='http://www.developers.org.ua/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> Если скомпилировать программу, печатающую список всех оптимальных позиций и запустить ее, то можно заметить, что она занимает в памяти около 10 мб, и это значение со временем увеличивается очень плавно. А все дело в том, что значения &#8220;winnig_positions&#8221;, выведенные на экран, уже не требуются для дальнейших вычислений и тут же собираются сборщиком мусора, равно как и значения списоков &#8220;row&#8221; и &#8220;column&#8221;. Таким образом, в любой момент времени в памяти хранится по одному значению из списков &#8220;row&#8221;, &#8220;column&#8221; и &#8220;winning_position&#8221; + в памяти находится диапазон натуральных чисел от следующего минимально-неиспользованного до максимально-использованного (его использует в своих вычислениях функция &#8220;not_yet_used&#8221;). &#8220;Удлинение&#8221; этого диапазона и вызывает увеличение объема потребляемой памяти.</p>
<p>А неиспользованные элементы (или непрерывные диапазоны элементов) бесконечных списков представляются в Haskell  в виде так называемых thunk-ов - &#8220;ссылок&#8221; на код, который их вычислит. Когда элемент &#8220;потребляется&#8221; для какого-то вычисления, thunk заменяется на вычисленное значение. Таким образом (упрощая), для хранения списка всех натуральных чисел [1..] достаточно хранить число &#8220;1&#8243; + thunk с описанием, как вычислить последующие числа.</p>
<p>Надеюсь, что это краткий экскурс в ленивые вычисления с использованием бесконечных списков оказался для вас не только забавным, но и полезным. Удачи, до новых встреч!</p>
<p>PS<br />
Disclaimer: данный код не является самым умным, самым быстрым или самым оптимальным способом решения поставленной задачи.</p>
<br/><a href="http://www.developers.org.ua/archives/adept/2007/08/12/playing-wythoffs-nim/#ratings">Оценить статью на сайте</a>.

<hr/>
<strong>Новое:</strong> мы запустили 
<a href="http://www.developers.org.ua/forum/">ФОРУМ</a>!
<hr/>
Свежая вакансия:


<a href="http://www.developers.org.ua/jobboard/746?ref=dou_rss">Monster HTML coder</a>
  (Егор Егоров)

]]></content:encoded>
			<wfw:commentRss>http://www.developers.org.ua/archives/adept/2007/08/12/playing-wythoffs-nim/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Отчет о ICFPC-2007, или Как прошел чемпионат по прикладной программной паталогоанатомии на скорость</title>
		<link>http://www.developers.org.ua/archives/adept/2007/07/30/icfpc2007-lb-announc/</link>
		<comments>http://www.developers.org.ua/archives/adept/2007/07/30/icfpc2007-lb-announc/#comments</comments>
		<pubDate>Mon, 30 Jul 2007 11:51:36 +0000</pubDate>
		<dc:creator>ADEpt</dc:creator>
		
		<category><![CDATA[War stories]]></category>

		<category><![CDATA[Репортажи]]></category>

		<category><![CDATA[ФП]]></category>

		<guid isPermaLink="false">http://www.developers.org.ua/archives/adept/2007/07/30/icfpc2007-lb-announc/</guid>
		<description><![CDATA[Добрый день. 
С интересом прочитал о том, как команда DOU участвовала в ICFPC-2007. В комментариях вспоминали меня и мой прошлогодний отчет, и я подумал, что многоуважаемой публике будет небезинтересно узнать о том, на какие грабли в этом году наступила команда Lazy Bottoms, чего мы достигли и что именно нам помешало занять место в top15 завершившегося [...]]]></description>
			<content:encoded><![CDATA[<p>Добрый день. </p>
<p>С интересом прочитал о том, как <a href="http://www.developers.org.ua/archives/bialix/2007/07/24/icfpc-2007-report/">команда DOU участвовала в ICFPC-2007</a>. В комментариях вспоминали меня и мой прошлогодний отчет, и я подумал, что многоуважаемой публике будет небезинтересно узнать о том, на какие грабли в этом году наступила команда Lazy Bottoms, чего мы достигли и что именно нам помешало занять место в top15 завершившегося соревнования. </p>
<p>Прошу любить и жаловать: <a href="http://users.livejournal.com/_adept_/67233.html">отчет о ICFPC-2007 от команды Lazy Bottoms</a>.</p>
<br/><a href="http://www.developers.org.ua/archives/adept/2007/07/30/icfpc2007-lb-announc/#ratings">Оценить статью на сайте</a>.

<hr/>
<strong>Новое:</strong> мы запустили 
<a href="http://www.developers.org.ua/forum/">ФОРУМ</a>!
<hr/>
Свежая вакансия:


<a href="http://www.developers.org.ua/jobboard/753?ref=dou_rss">Firmware Engineer </a>
  (mindspeed)

]]></content:encoded>
			<wfw:commentRss>http://www.developers.org.ua/archives/adept/2007/07/30/icfpc2007-lb-announc/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Анонс: открытое соревнование по программированию ICFPC-2007</title>
		<link>http://www.developers.org.ua/archives/adept/2007/07/08/icfpc07-announce/</link>
		<comments>http://www.developers.org.ua/archives/adept/2007/07/08/icfpc07-announce/#comments</comments>
		<pubDate>Sat, 07 Jul 2007 21:21:45 +0000</pubDate>
		<dc:creator>ADEpt</dc:creator>
		
		<category><![CDATA[Новости]]></category>

		<category><![CDATA[События]]></category>

		<guid isPermaLink="false">http://www.developers.org.ua/archives/adept/2007/07/08/icfpc07-announce/</guid>
		<description><![CDATA[Вы пишете бизнес-приложения на Java, но в качестве хобби изучаете Ruby и уверены, что на нем все можно написать в 10 раз быстрее и короче? Или, может, вы виртуоз C++ и убеждены, что при необходимости вы оптимизируете свой код так, как не снилось писакам на всяких разных PHP? А может, ваш любимый язык - Python, [...]]]></description>
			<content:encoded><![CDATA[<p>Вы пишете бизнес-приложения на Java, но в качестве хобби изучаете Ruby и уверены, что на нем все можно написать в 10 раз быстрее и короче? Или, может, вы виртуоз C++ и убеждены, что при необходимости вы оптимизируете свой код так, как не снилось писакам на всяких разных PHP? А может, ваш любимый язык - Python, и вы точно знаете, что при нужде вы решите любую задачу быстрее других - ведь 50% ее решения уже содержится в многочисленных библиотеках?</p>
<p>Через две недели у вас будет уникальный шанс проверить, чего стоят ваши убеждения, знания и навыки в состязании с другими программистами всего мира. Через две недели (20-го июля) начнется <a href="http://www.icfpcontest.org/">10-е открытое соревнование по программированию ICFPC-2007</a>.</p>
<p>Больше всего это похоже на программистские &#8220;бои без правил&#8221;. Про задание заранее неизвестно ничего - это может быть <a href="http://www.cs.cornell.edu/icfp/task.htm">разработка raytracer-а</a>, или <a href="http://alliance.seas.upenn.edu/~plclub/cgi-bin/contest/ants.html">программирование массы роботов для решения общей задачи</a> или же <a href="http://icfpc.plt-scheme.org/spec.html">реализация компьютерного игрока для игры в сложную командную игру</a>. </p>
<p>У вас будет 72 часа на то, чтобы достигнуть успеха. Вы можете использовать любые языки программирования. Вы можете бороться самостоятельно или командой (ее размер - не ограничен). Вы можете сидеть в одной комнате или быть в разных углах земного шара. Вы можете спать по 12 часов или не спать вовсе. Вы можете использовать любые вычислительные ресурсы, которые вам доступны. Единственное ограничение - не участвовать более чем в одной команде одновременно.</p>
<p>Заинтересовались? Добавляйте в закладки <a href="http://www.kingsrook.com/icfp/countdown.html">страницу с таймером</a>, отсчитывающим время до старта, <a href="http://www.icfpcontest.org/teams/register">регистрируйте</a> вашу команду (это не обязательно, но желательно) и начинайте готовиться - осталось всего 12 дней.</p>
<p>А если вы сомневаетесь - почитайте отчеты участников о прошлогоднем соревновании, ссылки на которые собраны <a href="http://www.developers.org.ua/archives/max/2006/07/31/icfpc06">тут</a> (в том числе - и отчет вашего покорного слуги).</p>
<br/><a href="http://www.developers.org.ua/archives/adept/2007/07/08/icfpc07-announce/#ratings">Оценить статью на сайте</a>.

<hr/>
<strong>Новое:</strong> мы запустили 
<a href="http://www.developers.org.ua/forum/">ФОРУМ</a>!
<hr/>
Свежая вакансия:


<a href="http://www.developers.org.ua/jobboard/750?ref=dou_rss">Master Development Engineer ASP.NET in Kiev/Kharkov/Odessa (2008/KKO/4532)</a>
  (materialize)

]]></content:encoded>
			<wfw:commentRss>http://www.developers.org.ua/archives/adept/2007/07/08/icfpc07-announce/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Func links: Caml Trading, Lift/Scala, XMonad, Pragmatic Haskell</title>
		<link>http://www.developers.org.ua/archives/adept/2007/05/18/func-links-1/</link>
		<comments>http://www.developers.org.ua/archives/adept/2007/05/18/func-links-1/#comments</comments>
		<pubDate>Fri, 18 May 2007 14:16:24 +0000</pubDate>
		<dc:creator>ADEpt</dc:creator>
		
		<category><![CDATA[Linkdump]]></category>

		<category><![CDATA[Ресурсы Сети]]></category>

		<guid isPermaLink="false">http://www.developers.org.ua/archives/adept/2007/05/18/func-links-1/</guid>
		<description><![CDATA[Этот пост - первый из серии более-менее регулярных (я надеюсь) сообщений о интересных и полезных проектах и статьях, так или иначе имеющих отношение к функциональному программированию. Даже если вы никогда не слышали о Haskell, OCaml, Scala и других подобных языках, вам может интересно (и небесполезно) ознакомиться со ссылками, хотя бы для расширения кругозора.
Have fun!

Caml Trading: [...]]]></description>
			<content:encoded><![CDATA[<p>Этот пост - первый из серии более-менее регулярных (я надеюсь) сообщений о интересных и полезных проектах и статьях, так или иначе имеющих отношение к функциональному программированию. Даже если вы никогда не слышали о Haskell, OCaml, Scala и других подобных языках, вам может интересно (и небесполезно) ознакомиться со ссылками, хотя бы для расширения кругозора.</p>
<p>Have fun!</p>
<hr />
<h2>Caml Trading: Experiences in Functional Programming on Wall Street</h2>
<p>Yaron Minsky написал интереснейшую статью об опыте <b>использования OCaml для написания программного обеспечения, задействованного в автоматизации торгов на фондовой бирже</b>. Что заставило финансовую компанию отказаться от VBA и C# в пользу OCaml? Какие свойства языка привели к выбору в пользу OCaml, какие сильные и слабые стороны проявились в процессе написания и эксплуатации программного обеспечения? Как можно подсумировать опыт, накопленый с 2005-го года, и каковы перспективы использования OCaml для написания &#8220;больших&#8221; приложений?</p>
<p>Обо всем этом можно прочитать на 23 странице <a href="http://www.haskell.org/sitewiki/images/0/03/TMR-Issue7.pdf">седьмого выпуска</a> онлайнового журнала <a href="http://www.haskell.org/haskellwiki/The_Monad.Reader">The Monad.Reader</a>.</p>
<h2>Lft/Scala</h2>
<p><a href="http://www.scala-lang.org">Scala</a>, гибридный язык, сочетающий в себе элементы функциональной и объектно-ориентированой парадигм и компилирующийся в байт-код JVM, теперь обзавелся собственным web framework-ом.</p>
<p>Библиотека, называемая <a href="http://liftweb.net/">Lift</a>, по утверждению авторов, делалась с оглядкой на лидеров в этом сегменте, и взяла от них все самое лучшее (<i>&#8230; lift borrows from the best of existing frameworks including Seaside&#8217;s highly granular sessions and security, Rails fast flash-to-bang, Django&#8217;s &#8220;more than just CRUD is included&#8221;, and Erlyweb&#8217;s scalability for Comet-style applications.</i>).</p>
<p><b>Утверждается, что код на lift/Scala получается таким же лаконичным, как при использовании Ruby On Rails, но при этом приложения исполняются в шесть раз быстрее и в многопоточном режиме.</b> Кроме того, статическая типизация Scala позволяет устранить бОльшее количество ошибок на этапе разработки, а компиляция в байт-код JVM позволяет програмисту использовать любые существующие библиотеки, написанные на Java.</p>
<p>В качестве примера использования lift/Scala можно привести <a href="http://blog.circleshare.com/index.php?/archives/55-Prance-with-the-Horses,-Skittr-with-the-Mice.html">клон</a> набирающей популярность &#8220;социальной IM-системы&#8221; <a href="http://twitter.com/">Twittr.com</a>. Код длиной в 900 строк позволяет (по утверждению автора) обслуживать более миллиона клиентов на системе из двух комьютеров на базе Core 2 Duo.</p>
<h2>Window Manager на Haskell в 500 строк</h2>
<p>В рамках графической подсистемы <a href="http://en.wikipedia.org/wiki/X_Window_System">X Window</a>, используемой в большинстве совеременных *nix-совместимых ОС, управление окнами (перемещение, максимизация/минимизация, расположение на экране, отрисовка бордюров и кнопочек и т.п.) выполняется не самой графической системой, а отдельной программой, называемой <a href="http://en.wikipedia.org/wiki/X_window_manager">window manager</a>. </p>
<p>Сушествуют десятки window manager-ов на любой вкус и цвет, но если в конце-концов выбор вас не удовлетворил, вы всегда можете написать свой, с нужными вам функциями и свойствами. Именно так поступили Don Stewart, Spencer Janssen и Jason Creighton - разочаровавшись в существующих tiling window manager-ах (ion3, wmii, ratpoison, &#8230;), <b>они написали свой. На Haskell. Уложившись в 500 строк кода.</b></p>
<p>Дон Стюарт разместил в своем блоге детальный рассказ о том, как проектировался, писался и тестировался (автоматически!) этот проект. Изложение содержит большое количество кода на Haskell, но рекомендуется к прочтению даже тем, кто его не знает. <a href="http://www.cse.unsw.edu.au/~dons/blog/2007/05/01#xmonad_part1_model">Первая часть</a> и <a href="http://www.cse.unsw.edu.au/~dons/blog/2007/05/17#xmonad_part1b_zipper">вторая часть</a>.</p>
<h2>Следующая книжка из серии Pragmatic Programmer будет посвящена Haskell</h2>
<p>Издательство Pragmatic Programmers, известное такими <a href="http://pragmaticprogrammer.com/bookshelf/index.html">книгами</a>, как <a href="http://pragmaticprogrammer.com/titles/mjwti/index.html">&#8220;My job went to India&#8221;</a>, <a href="http://pragmaticprogrammer.com/titles/rails2/index.html">&#8220;Agile Web Development with Rails</a> и <a href="http://pragmaticprogrammer.com/ppbook/index.shtml">&#8220;Pragmatic Programmer: From Journeyman to Master&#8221;</a>, <b>приняло решение издать книгу о Haskell</b>. Новость из первых рук сообщил (будущий) автор книги <a href="http://blogs.nubgames.com/code/?p=23">в своем блоге</a>.</p>
<p>Очевидно, на положительное решение повлияли положительные обзоры книги <a href="http://pragmaticprogrammer.com/titles/jaerlang/index.html">&#8220;Programming Erlang&#8221;</a>, которая месяц тому назад вышла на &#8220;финишную прямую&#8221; и попала в руки бета-тестеров.</p>
<br/><a href="http://www.developers.org.ua/archives/adept/2007/05/18/func-links-1/#ratings">Оценить статью на сайте</a>.

<hr/>
<strong>Новое:</strong> мы запустили 
<a href="http://www.developers.org.ua/forum/">ФОРУМ</a>!
<hr/>
Свежая вакансия:


<a href="http://www.developers.org.ua/jobboard/758?ref=dou_rss">C++ Developer</a>
  (Quadrox)

]]></content:encoded>
			<wfw:commentRss>http://www.developers.org.ua/archives/adept/2007/05/18/func-links-1/feed/</wfw:commentRss>
		</item>
		<item>
		<title>ФП: lazy evaluation - это завтрашние результаты вычисления функций уже сегодня.</title>
		<link>http://www.developers.org.ua/archives/adept/2007/04/24/fp-lazy-evaluation/</link>
		<comments>http://www.developers.org.ua/archives/adept/2007/04/24/fp-lazy-evaluation/#comments</comments>
		<pubDate>Tue, 24 Apr 2007 14:31:31 +0000</pubDate>
		<dc:creator>ADEpt</dc:creator>
		
		<category><![CDATA[Разработка]]></category>

		<category><![CDATA[Статьи]]></category>

		<guid isPermaLink="false">http://www.developers.org.ua/archives/adept/2007/04/24/fp-lazy-evaluation-eto-zavtrashnie-rezultatyi-vyichisleniya-funktsiy-uzhe-segodnya/</guid>
		<description><![CDATA[После вводной статьи о ФП впору переходить к демонстрации конкретных примеров, приемов и трюков. 
Сегодняшняя статья будет посвящена ленивым вычислениям (lazy evaluation). Оставайтесь с нами, и вы увидите, как функция может использовать в качестве аргумента результаты своей работы.
В качестве языка для иллюстрирования взят Haskell. От читателя не требуется знания Haskell или его синтаксиса.
Рассмотрим такую задачу: [...]]]></description>
			<content:encoded><![CDATA[<p>После <a href="http://www.developers.org.ua/archives/adept/2007/04/20/why-fp">вводной статьи о ФП</a> впору переходить к демонстрации конкретных примеров, приемов и трюков. </p>
<p>Сегодняшняя статья будет посвящена <b>ленивым вычислениям</b> (<a href="http://en.wikipedia.org/wiki/Lazy_evaluation">lazy evaluation</a>). Оставайтесь с нами, и вы увидите, <b>как функция может использовать в качестве аргумента результаты своей работы</b>.</p>
<p>В качестве языка для иллюстрирования взят <a href="http://www.haskell.org">Haskell</a>. От читателя не требуется знания Haskell или его синтаксиса.</p>
<p>Рассмотрим такую задачу: дан текст, в котором используются специальные слова <code>@label</code> и <code>@ref</code>. С помощью <code>@label</code> определяются метки, а с помощью <code>@ref</code> делаются ссылки на них. Например, так:</p>
<pre>
Введение@label{intro}. Как известно, чушь бывает разная: зеленая (@ref{green}) и красная (@ref{red}).
...
Как мы уже отмечали (@ref{intro}), чушь бывает разная, и именно поэтому жизненно важно ...
...
Чушь красная@label{red}.
Не следует путать с чушью зеленой (@ref{green}).
...
Чушь зеленая@label{green}.
Радикально отличается от чуши красной (@ref{red}).
...
</pre>
<p>Нам необходимо написать программу, которая заменит метки их номером (в виде &#8220;[1]&#8220;, &#8220;[2]&#8220;, &#8230;), а ссылки - текстом &#8220;см. [1]&#8220;. Из нашего образца должно получиться:</p>
<pre>
Введение[1]. Как известно, чушь бывает разная: зеленая (см. [2]) и красная (см. [3]).
&#8230;
Как мы уже отмечали (см. [1]), чушь бывает разная, и именно поэтому жизненно важно &#8230;
&#8230;
Чушь красная[2].
Не следует путать с чушью зеленой (см. [3]).
&#8230;
Чушь зеленая[3].
Радикально отличается от чуши красной (см. [2]).
&#8230;
</pre>
<p>Классический алгоритм таков: проходим по тексту, собирая метки в порядке следования и назначая им номера. Проходим второй раз, заменяя метки и ссылки на вычисленые номера. Второй проход необходим, т.к. нам могут встретиться ссылки на метки, определенные дальше по тексту. Будем програмировать &#8220;сверху вниз&#8221;:</p>
<pre>

&gt; module Main where
&gt; import Data.List
&gt; import Text.Regex.Posix
&gt; import Control.Arrow
&gt;
&gt; main = do
&gt;   txt &lt;- readFile "sample.txt"
&gt;   let numbering = collectLabels txt
&gt;   let result = replaceLabels numbering txt
&gt;   writeFile "sample.out" result
</pre>
<p>Тут <code>txt</code>, <code>numbering</code> и <code>result</code> - это не имена переменных, а <b>имена, связанные (<a href="http://en.wikipedia.org/wiki/Free_variables_and_bound_variables">bound</a>) с выражениями</b>. Другими словами мы можем использовать <code>numbering</code> для того, чтобы быстро сослаться на результат применения функции <code>collectLabels</code> к выражению, связанному с именем <code>txt</code>. Можно связывать имена со значениями несколько раз, при этом более &#8220;вложенное&#8221; связывание перекрывает внешнее (как и в случае с именами локальных переменных в императивных языках программирования).</p>
<p>Поскольку мы идем &#8220;сверху вниз&#8221; и реализации <code>collectLabels</code> и <code>replaceLabels</code> у нас еще нет, так и напишем:</p>
<pre>
 &gt; collectLabels = undefined
 &gt; replaceLabels = undefined
</pre>
<p>Сохраняем наше творчество в файл &#8220;labeling.hs&#8221;. Запускаем ghci (интерактивную среду, прилагающуюся к компилятору <a href="http://www.haskell.org/ghc/">GHC</a>), в появившемся промпте вбиваем &#8220;:load labeling.hs&#8221;. </p>
<p>Ура, наша программа компилируется, хотя ничего разумного пока еще не делает. Мы еще не знаем, как именно будут реализованы функции <code>collectLabels</code> и <code>replaceLabels</code>, поэтому их тело состоит из одного примитива <code>undefined</code>. Использование <code>undefined</code> позволяет нам по ходу написания проверять правильность того, что мы пишем, начиная с самых первых строк. Как минимум, мы можем проверять, что компилятору удается выполнить <b>вывод типов</b> (<a href="http://en.wikipedia.org/wiki/Type_inference">type inference</a>), т.е. определить, какой тип у всех выражений и функций, использованых в нашей программе, и проверить эти типы на соответствие друг другу (<a href="http://en.wikipedia.org/wiki/Type_checking">type checking</a>). </p>
<p>Компиляторы C++/Java всегда выполняют проверку типов, но возможности вывода типов у них сильно ограничены. <b>Упражнение номер один</b> для пытливого читателя - написать минимальный компилирующийся &#8220;скелет&#8221;, аналогичный приведеному, на C++/Java.</p>
<p><b>Начнем со сбора меток</b>. Нам нужно найти в тексте все вхождения <code>@label{xxx}</code> в порядке упоминания и сложить их в какую-то структуру данных. Например, в список. Для извлечения текста используем сравнение с регулярным выражением:</p>
<pre>

&gt; collectLabels txt =
&gt;   let matches = ( txt =~ "@label\\\\{([^}]*)\\\\}&#8221; :: [[String]] )
&gt;       in map (!!1) matches
</pre>
<p>Явно указывая тип возвращаемого значения для ф-ии <code>(=~)</code>, мы заставляем библиотеку <code>Text.Regex.Posix</code> выбирать один из многих вариантов представления результата сравнения с регулярным выражением (всего их более 15-и). В данном случае нас интересует список <code>[весь_текст_совпадения, текст_группы_1, текст_группы_2, ...]</code> для каждого совпадения с образцом. Пример ожидаемого результата: <code>[["@label{ccc}","ccc"],["@label{bbb}","bbb"]]</code>.</p>
<p>В каждом списке-результате нас интересует второй элемент - имя метки. Ф-ия извлечения элемента списка по номеру имеет имя <code>(!!)</code> - например, второй элемент списка <code>lst</code> можно получить выражением <code>lst!!1</code>. Поскольку нам надо применить операцию ко всем результатам, воспользуемся ф-ией <code>map</code> и приемом, называемым &#8220;частичное применение&#8221; (partial application или <a href="http://en.wikipedia.org/wiki/Currying">currying</a>).</p>
<p>Чтобы этот кусок кода было проще понять, обратимся к ghci. Используем его для того, чтобы поиграться с кусками кода и лучше понять, что именно они делают:</p>
<blockquote><p>
<code><br />
<u>Можно просто проверить, совпала строка с регулярным выражением или нет</u><br />
<b>*Main&gt; &#8220;@label{aaa} some text @label{bbb} some more text&#8221; =~ &#8220;@label\\{([^}]*)\\}&#8221; :: Bool</b><br />
True</p>
<p><u>&#8230; или получить полный текст всех совпадений</u><br />
<b>*Main&gt; &#8220;@label{aaa} some text @label{bbb} some more text&#8221; =~ &#8220;@label\\{([^}]*)\\}&#8221; :: [String]</b><br />
["@label{aaa}","@label{bbb}"]</p>
<p><u> &#8230; или полный текст всех совпадений, плюс - текст всех подгрупп</u><br />
<b>*Main&gt; let matches = &#8220;@label{aaa} some text @label{bbb} some more text&#8221; =~ &#8220;@label\\{([^}]*)\\}&#8221; :: [[String]]</b><br />
[["@label{aaa}","aaa"],["@label{bbb}","bbb"]]</p>
<p><u>&#8230; из которого потом можно извлечь либо текст совпадения</u><br />
<b>*Main&gt; map (!!0) matches</b><br />
["@label{aaa}","@label{bbb}"]</p>
<p><u> &#8230; либо содержимое первой подгруппы.</u><br />
<b>*Main&gt; map (!!1) matches</b><br />
["aaa","bbb"]</p>
<p><u>Кроме того, мы можем посмотреть, что компилятор думает о типе определенного выражения:</u><br />
<b>*Main&gt; :type map (!!1)</b><br />
map (!!1) :: [[a]] -&gt; [a]<br />
</code>
</p></blockquote>
<p>Если вы использовали функцию <code>map</code> в Perl, Python или Ruby, то знаете, что ей нужно давать функцию от одного аргумента. В то же время <code>(!!)</code> - это функция от двух аргументов (список и позиция_в_списке). Однако, если функции <code>(!!)</code> дать всего один аргумент, то в результате получится новая функция от одного аргумента. Это и есть <i>частичное применение</i>. </p>
<p>Особо обратите внимание на последний пример: используя информацию о том, что <code>(!!)</code> возвращает элемент списка, компилятор самостоятельно вычислил, что выражение-функция <code>map (!!1)</code> будет преобразовывать список списков в список просто значений. <b>Самостоятельно посмотрите</b> на тип функций <code>map</code>, <code>(!!)</code> и <code>(!!1)</code>, чтобы понять, как именно компилятор произвел вывод. Подобный алгоритм выведения типов используется также в языках <a href="http://en.wikipedia.org/wiki/Clean_%28programming_language%29" title="Clean (programming language)">Clean</a>, <a href="http://en.wikipedia.org/wiki/ML_%28programming_language%29" title="ML (programming language)">ML</a>, <a href="http://en.wikipedia.org/wiki/OCaml" title="OCaml">OCaml</a>, <a href="http://en.wikipedia.org/wiki/Scala_%28programming_language%29" title="Scala (programming language)">Scala</a>, <a href="http://en.wikipedia.org/wiki/Nemerle" title="Nemerle">Nemerle</a>, <a href="http://en.wikipedia.org/wiki/D_%28programming_language%29" title="D (programming language)">D</a>. Ожидается, что вывод типов появится в <a href="http://en.wikipedia.org/wiki/C_Sharp#C.23_3.0_new_language_features" title="C Sharp">C# 3.0</a> и <a href="http://en.wikipedia.org/wiki/Perl_6" title="Perl 6">Perl 6</a> (помните, что я говорил о влиянии ФП на mainstream языки?).</p>
<p>Как мы видим, функции не только могут быть использованы в качестве аргументов в вызовах функций, но и могут быть результатами вычисления выражений. В этом смысле функции ничем не отличаются от прочих базовых типов языка - чисел, символов, списков. Говорят, что функции являются <b>полноправными сущностями</b> (<a href="http://en.wikipedia.org/wiki/First_class_function">first-class citizens</a>). В качестве <b>упражнения номер два</b> можете попробовать определить на C++/Java аналоги функций <code>map</code> и <code>!!</code> так, чтобы их можно было комбинировать в стиле <code>map (!!1)</code> и самостоятельно сделать вывод о том, являются ли функции полноправными сущностями в этих языках.</p>
<p>Проверим наш код:</p>
<blockquote><p>
<code><br />
<b>*Main&gt; collectLabels &#8220;some text@label{one} with labels@label{two}\nand several lines@label{three}&#8221;</b><br />
["one","two","three"]<br />
</code>
</p></blockquote>
<p><b>Быстро закрепим пройденый материал</b>: мы увидели кусок программы на функциональном языке Haskell. Язык - со строгой типизацией (как в C++/Java), что не мешает существованию интерактивной  среды исполнения (interactive top-level), как в Python. Для создания программы используется создание новых функций путем комбинирования библиотечных функций, и функций, определенных пользователем. Отсуствие <b>изменяемых переменных</b> (mutable variables) пока что сильно не мешает.</p>
<p><b>Теперь напишем замену меток и ссылок номерами</b>. Имена меток у нас уже собраны в список. Номер, на который надо заменять метку - это ее позиция в этом списке. Нам надо пройтись по всему тексту, находя специальные слова и заменяя их номерами.</p>
<p>Нам нужно разделить исходный текст на строки, которые в свою очередь разбить на слова. Среди слов нам надо найти &#8220;специальные&#8221; и заменить их на номера, после чего собрать слова в строки, а строки - в текст. Так и запишем:</p>
<pre>

&gt; replaceLabelsUgly labelList txt = unlines ( map unwords ( map (map process) ( map words ( lines txt ))))
&gt;   where process = undefined
</pre>
<p>Люди, страдающие лиспофобией, уже бьются в припадке и хрипят &#8220;Скобки! Уберите скобки! Опять эти чертовы скобки! &#8220;. Пожалуй, они в чем-то правы. Перепишем эту функцию так:</p>
<pre>

&gt; replaceLabelsBetter labelList txt = unlines $ map unwords $ map (map process) $ map words $ lines txt
&gt;   where process = undefined
</pre>
<p>Уже лучше. &#8220;Прикольный синтаксический наворот&#8221; - скажут многие. Однако, это не выверт в синтаксисе языка. Тут использована функция <code>($)</code>, которая объявлена как инфиксный оператор и определена так:</p>
<pre>
f $ x = f x
</pre>
<p>Получается, что <code>$</code> - это такой способ <a href="http://en.wikipedia.org/wiki/Function_composition">композиции функций</a>, только и всего. И в самом деле, легко убедиться, что <code>f (g x) = f $ g x</code>. Можно даже взять и переписать функцию <code>replaceLabels</code> с использованием композиции функций в чистом виде:</p>
<pre>

&gt; replaceLabelsPointfree labelList = unlines . map unwords . map (map process) . map words . lines
&gt;   where process = undefined
</pre>
<p>Обратите внимание, что в этой форме записи у <code>replaceLabelsPointfree</code> стало на один аргумент меньше, как это и происходит в математической нотации. Мы пишем <code>f(x) = ...</code> и <code>g(x) = ...</code>, но <code>t = f . g</code>.</p>
<p>Впрочем, тут могут объявится любители Ruby и сказать &#8220;все равно эта функция записана криво и непонятно. Что за дела? Чтобы ее понять, надо читать ее с конца. То ли дело запись вида <code>txt.to_lines().map({|l| l.words()}).map ...</code>, которую можно нормально прочесть слева направо&#8221;.</p>
<p>Что ж перепишем так, чтобы нормально читалось слева направо:</p>
<pre>
-&gt; replaceLabels labelList = lines &gt;&gt;&gt; map words &gt;&gt;&gt; map (map process) &gt;&gt;&gt; map unwords &gt;&gt;&gt; unlines
-&gt;   where process = undefined
</pre>
<p>Как можно догадаться, <code>(&gt;&gt;&gt;)</code> - это еще одна функция, позволяющая записать композицию ф-ий вот так, &#8220;шиворот навыворот&#8221;, в виде, напоминающем <a href="http://en.wikipedia.org/wiki/Pipeline_%28Unix%29">shell pipes</a> из Unix-а.</p>
<p>Ну, тут уже явно возмутятся даже те, кто честно пытался вникнуть в написанное без перенесения на Haskell опыта работы со своим любимым языком программирования. &#8220;И это называется <b>нормально</b>?&#8221; - скажут они. &#8220;Тут же нифига не понятно!&#8221;.</p>
<p>Ну, раз непонятно, надо &#8220;мерять хвост частями&#8221; с помощью ghci:</p>
<blockquote><p><code><br />
<b>*Main&gt; lines &#8220;this is a\nsample\nmultiline text&#8221;</b><br />
["this is a","sample","multiline text"]</p>
<p><b>*Main&gt; lines &gt;&gt;&gt; map words $ &#8220;this is a\nsample\nmultiline text&#8221;</b><br />
[["this","is","a"],["sample"],["multiline","text"]]</p>
<p><b>*Main&gt; lines &gt;&gt;&gt; map words &gt;&gt;&gt; map unwords $ &#8220;this is a\nsample\nmultiline text&#8221;</b><br />
["this is a","sample","multiline text"]</p>
<p><b>*Main&gt; lines &gt;&gt;&gt; map words &gt;&gt;&gt; map unwords &gt;&gt;&gt; unlines $ &#8220;this is a\nsample\nmultiline text&#8221;</b><br />
&#8220;this is a\nsample\nmultiline text\n&#8221;<br />
</code></p></blockquote>
<p>Теперь осталось разобраться с тем, что стоит между <code>map words</code> и <code>map unwords</code>. Если <code>process w</code> проверяет, не является ли слово <code>w</code> специальным, и заменяет его на номер, то <code>map process ws</code> выполнит эту операцию для всех слов в <code>ws</code>. Если <code>ws</code> - это список слов, стоявших в одной строке, то нам осталось применить эту операцию ко всем строкам текста, чтобы обработать его полностью. Для этого и нужен второй, внешний <code>map</code>. Получаем, что обработка все слов текста - это и есть <code>map (map process)</code>.</p>
<p><b>Быстро закрепим пройденый материал:</b> мы увидели, как возможность объявлять функции инфиксными операторами с нужным приоритетом позволяет гибко создавать новые управляющие конструкции языка и, фактически, создавать удобный нам синтаксис. Прослеживается <b>типичный шаблон</b> (pattern) програмирования на функциональных языках - создание &#8220;конвейера&#8221; из функций, последовательно применяющих к исходным данным ряд преобразований для получения конечного результата.</p>
<p>Нам осталось определить функцию <code>process</code>. Она должна работать следующим образом: все слова вида <code>@label{xxx}</code> заменять на &#8220;[номер метки xxx]&#8220;, все слова <code>@ref{xxx}</code> заменять на &#8220;см. [номер метки xxx]&#8220;, а все прочие слова - не трогать. С вашего позволения, я сразу приведу соответствующих код, без подробных объяснений.</p>
<pre>

&gt; replaceLabels labelList = lines &gt;&gt;&gt; map words &gt;&gt;&gt; map (map process) &gt;&gt;&gt; map unwords &gt;&gt;&gt; unlines
&gt;   where process w | isInfixOf "@label{" w = replace ""     w
&gt;                   | isInfixOf "@ref{"   w = replace "see " w
&gt;                   | otherwise             = w
&gt;
&gt;         -- Эта функция заменяет ссылку/метку в слове `w' на ее [номер], предваряя [номер] строкой `prepend&#8217;
&gt;         replace prepend w =
&gt;           &#8212; Слово &#8220;Введение@label{aaaa}.&#8221; будет разбито на части &#8220;Введение&#8221;, &#8220;aaa&#8221; и &#8220;.&#8221;,
&gt;           &#8212; из которых мы потом соберем результат.
&gt;           let [[_,prefix,label,suffix]] = ( w =~ &#8220;(.*)@.*\\\\{([^}]*)\\\\}(.*)&#8221; :: [[String]] )
&gt;               in prefix ++ prepend ++ &#8220;[" ++ idx label ++ "]&#8221; ++ suffix
&gt;
&gt;         &#8212; Возвращает позицию `l&#8217; в списке `labelList&#8217; (нумерация начинается с 1).
&gt;         idx l = case elemIndex l labelList of
&gt;                      Just x  -&gt; show (x+1)
&gt;                      Nothing -&gt; error $ &#8220;Label &#8220;++l++&#8221; is not defined&#8221;
</pre>
<p>Я надеюсь, что даже минимальный опыт работы с python и ruby позволит без особого напряжения прочесть и понять этот код. Возможно, вам также поможет пример функции, которая для четных аргументов возвращает &#8220;fuzz&#8221;, а для нечетных - &#8220;buzz&#8221;:</p>
<pre>
fuzzbuzz x | even x    = "fuzz"
           | odd  x    = "buzz"
           | otherwise = "something is odd ..."
</pre>
<p>Проверим наш код:</p>
<pre>
<b>*Main&gt; main</b>
</pre>
<p>В файле &#8220;sample.out&#8221; должен оказаться текст с замененными ссылками.</p>
<p>Тут самое время спросить: <b>&#8220;НУ И ЧТО?&#8221;</b>. Казалось бы, до сих пор мы не сделали ничего необычного и отличающегося от того, что мы делали бы при программировании на любом другом императивном языке. Ну, разве что, обошлись без переменных <img src='http://www.developers.org.ua/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Наберитесь еще немного терпения, мы уже подобрались &#8220;гвоздю программы&#8221;. Попробуем соединить сбор меток и замену ссылок в одну функцию. Предположим, что у нас уже есть полученый каким-то волшебным образом список всех меток в документе, назовем его <code>allLabels</code>. Откуда он взялся? А черт его знает. Допустим, мы сами прислали его себе из будущего, из того момента, когда мы уже обработали весь файл и знаем полный список меток <img src='http://www.developers.org.ua/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>По-прежнему будем обрабатывать текст по словам, но чуть-чуть изменим алгоритм: если в слове есть ссылка, то использует <code>allLabels</code> для вычисления ее номера. Если же в слове есть метка, то используем <code>allLabels</code> для вычисления ее номера <i>и добавим метку в список <code>labelList</code></i>. Зачем нам нужен <code>labelList</code>? Ну, должен же откуда-то взяться в будущем список меток, который мы пошлем себе в прошлое <img src='http://www.developers.org.ua/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Вашему вниманию предлагается функция <code>process'</code> (апостроф - это допустимый символ в имени функции), которая обрабатывает слово <code>w</code>, используя словарь меток <code>allLabels</code> и аккумулятор <code>labelList</code>, куда мы добавляем все найденые метки.</p>
<pre>

&gt; process' (_,labelList,allLabels) w
&gt;   | isInfixOf "@label{" w = replace ""      True
&gt;   | isInfixOf "@ref{"   w = replace "see "  False
&gt;   | otherwise             = (w, labelList, allLabels)
&gt;
&gt;   -- Заменяем ссылку/метку в слове `w' на ее номер, предваряя его строкой `prepend'. Если `add' == True,
&gt;   -- добавляем метку в список labelList
&gt;   where replace prepend add =
&gt;           let [[_,prefix,label,suffix]] = ( w =~ &#8220;(.*)@.*\\\\{([^}]*)\\\\}(.*)&#8221; :: [[String]] )
&gt;               labelList&#8217; = if add then (label:labelList) else labelList
&gt;             in (concat [prefix, prepend, "[", idx label,"]&#8220;, suffix], labelList&#8217;, allLabels)
&gt;
&gt;         idx l = case elemIndex l allLabels of
&gt;                      Just x  -&gt; show (x+1)
&gt;                      Nothing -&gt; &#8220;Label &#8220;++l++&#8221; is not defined&#8221;
</pre>
<p>Кроме того, несколько изменим процесс обработки всего текста: вместо того, чтобы превращать его в список списков слов, превратим его в &#8220;плоский&#8221; список слов, который будет удобно обработать функций <code>process'</code>. Чтобы можно было собрать текст обратно, сохранив разбиение на слова, свяжем в с каждым словом номер строки, в которой оно (слово) стояло. После замены ссылоку/меток собирем слова в цельный текст, используя сохраненную информацию о том, какие слова были в какой строке.</p>
<pre>

&gt; process = combineFromWords . replaceLabels . splitToWords
&gt;   where
&gt;   splitToWords txt = unzip $ concat $ zipWith tagWords [1..] $ lines txt
&gt;   replaceLabels (tags, ws) = zip tags (processWords ws)
&gt;   combineFromWords ws = unlines $ map (unwords . map snd) $ groupBy (comparing fst) ws
&gt;   tagWords lineNo line = zip (repeat lineNo) (words line)
&gt;   comparing f a b = (f a) == (f b)
</pre>
<p>Пару примеров для облегчения понимания этого куска (используйте ghci, чтобы увидеть типы и &#8220;попробовать на зуб&#8221; все функции, которые я не упомянул в этих примерах):</p>
<blockquote><p><code><br />
<b>*Main&gt; zip [1..] $ lines &#8220;aaa bbb ccc\nddd eee fff&#8221;</b><br />
[(1,"aaa bbb ccc"),(2,"ddd eee fff")]</p>
<p><b>*Main&gt; let l = zipWith (\lineNo line -&gt; zip (repeat lineNo) (words line)) [1..] $ lines &#8220;aaa bbb ccc\nddd eee fff&#8221;</b><br />
[[(1,"aaa"),(1,"bbb"),(1,"ccc")],[(2,"ddd"),(2,"eee"),(2,"fff")]]</p>
<p><b>*Main&gt; unzip $ concat l</b><br />
([1,1,1,2,2,2],["aaa","bbb","ccc","ddd","eee","fff"])<br />
</code></p></blockquote>
<p>А теперь, собственно, создадим нашу &#8220;машину времени&#8221; - функцию, которая получит результаты работы функции <code>process'</code> (собраный списко меток) и &#8220;отправит его в прошлое&#8221; - передаст в качестве аргумента &#8230; самой функции <code>process'</code>.</p>
<pre>

&gt; processWords ws =
&gt;   -- Из списка слов ["@label{aaa}","@ref{aaa}","ccc"] получаем список
&gt;   &#8212; [ ("[0]&#8220;,["aaa"],все_метки_из_будущего)
&gt;   &#8212; , (&#8221;см [0]&#8220;, ["aaa"], все_метки_из_будущего)
&gt;   &#8212; , (&#8221;ccc&#8221;, ["aaa"], все_метки_из_будущего)
&gt;   &#8212; ]
&gt;   &#8212; Нас интересует последий элемент этого списка, который содержит список всех найденых меток
&gt;   let result = drop 1 $ scanl process&#8217; ([],[],reverse collectedLabels) ws
&gt;       &#8212; Извлекаем список найденых меток, чтобы использовать его &#8230;
&gt;       &#8212; &#8230; в предыдущей строке в качестве аргумента для process&#8217; !
&gt;       (_,collectedLabels,_) = last result
&gt;       &#8212; Конечным результатом является список обработаных слов, без информации о найденых метках.
&gt;       in map discardLabelLists result
&gt;   where discardLabelLists (w,_,_) = w
</pre>
<p>Чтобы проверить, что <b>этот код действительно работает</b>, изменим функцию <code>main</code>:</p>
<pre>

&gt; main' = do
&gt;   txt &lt;- readFile "sample.txt"
&gt;   writeFile "sample.out" $ process txt
</pre>
<p>Выполнив <code>main'</code>, вы можете убедиться, что все метки и ссылки в действительности были заменены номерами. Черная магия? Отнюдь. Но как &#8230;?</p>
<p>Использование в вычислениях еще не посчитаных значений становится возможным благодаря так называемой &#8220;ленивой&#8221; схеме вычислений (lazy evaluation) выражений в языках функционального программирования. При использовании этой схемы результатом выражения <code>let y = ((f x) + (g x))</code> является не фактическая сумма, а что-то вроде ссылки на код, который эту сумму посчитает, когда нам понадобится значение <code>y</code> (я намерено опускаю технические детали). <b>Если значение нам не понадобится - то и вычисление фактически не произойдет</b>. </p>
<p>Таким образом, даже если вставить в произвольное место программы выражение <code>let foobar = sum ( map (^2) [1..1000000000] )</code>, можно не боятся, что программа будет тратить время на подсчет суммы квадратов всех чисел от одного до ста миллионов. Если мы нигде явно или косвенно не используем символ <code>foobar</code>, то это вычисление просто не произойдет.</p>
<p>Возвращаясь к нашему коду, становится понятно, что использование функции <code>idx l</code> не приводит к фактическому немедленному поиску метки <code>l</code> в списке <code>allLabels</code>. Вместо него создается &#8220;замороженный код&#8221; (thunk), который произведет этот поиск тогда, когда в нем будет необходимость. А необходимость эта появляется аж в самом конце программы, когда мы выводим результат обработки в выходной файл. Естественно, в этот момент времени у нас уже есть полный список меток, использованых в обрабатываемом тексте. </p>
<p>Получается, что я всех обманул - мы не отправляем &#8220;в прошлое&#8221; результаты вычислений, мы как бы &#8220;отправляем в будущее&#8221; сами вычисления с пометкой &#8220;до востребования&#8221;. </p>
<p>Прошу не считать эту статью поводом для очередной священной войны - она не писалась в рассчете на это. Я просто надеюсь, что этот текст послужил для вас хорошей пищей для размышлений и поводом присмотреться поближе к функциональному програмированию.</p>
<p>Да, а чтобы вы не думали, что использованый мной прием - искуственный и редковстречающийся, вот вам еще практических примеров из этой же категории:</p>
<pre>
-- Функция `fib' возвращает список чисел Фибоначчи
fib = 1:1:(zipWith (+) fib (tail fib))

-- Функция `primes' возвращает список простых чисел
primes = primes' [2..]
  where primes&#8217; (n:ns) = n : primes&#8217; [ y | y &lt;- ns, y `mod` n /= 0] 

&#8211; Функция ralign выравнивает список строк вправо:
ralign xs = [ rjustify m x | x &lt;- xs ]
  where
  m = maximum [ length x | x &lt;- xs ]
  rjustify m x = replicate (m - length x) &#8216; &#8216;  ++ x
</pre>
<p>Если эта статья вызовет интерес, то в следующих выпусках нашей программы ожидайте: работа с бесконечными списками и бесконечными структурами данных, зачем нужны в реальной жизни катаморфизмы (foldr) и анаморфизмы (unfoldr) списков и многое другое <img src='http://www.developers.org.ua/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>PS<br />
Текст этой статьи можно сохранить в файл с суффиксом &#8220;.lhs&#8221; (literate Haskell), и его можно будет без изменений загружать и исполнять в ghci как программу.</p>
<p>PPS<br />
Ссылки для самостоятельного чтения:</p>
<ul>
<li> Перечень <a href="http://www.haskell.org/haskellwiki/Learning_Haskell">учебных текстов</a> по Haskell на <a href="http://www.haskell.org/haskellwiki">Haskell Wiki</a>. Беззастенчивая реклама: <a href="http://www.haskell.org/haskellwiki/Hitchhikers_Guide_to_the_Haskell">&#8220;Hitchhickers guide to Haskell&#8221;</a> написан вашим покорным слугой.
</li>
<li> <a href="http://en.wikibooks.org/wiki/Haskell">Wikibook по Haskell</a>
</li>
<li> <a>OCaml Tutorial</a>
</li>
</ul>
<br/><a href="http://www.developers.org.ua/archives/adept/2007/04/24/fp-lazy-evaluation/#ratings">Оценить статью на сайте</a>.

<hr/>
<strong>Новое:</strong> мы запустили 
<a href="http://www.developers.org.ua/forum/">ФОРУМ</a>!
<hr/>
Свежая вакансия:


<a href="http://www.developers.org.ua/jobboard/750?ref=dou_rss">Master Development Engineer ASP.NET in Kiev/Kharkov/Odessa (2008/KKO/4532)</a>
  (materialize)

]]></content:encoded>
			<wfw:commentRss>http://www.developers.org.ua/archives/adept/2007/04/24/fp-lazy-evaluation/feed/</wfw:commentRss>
		</item>
		<item>
		<title>&#8220;Почему ФЯ?&#8221; или Стоит ли учить что-то радикально отличное от C++/Java/Python</title>
		<link>http://www.developers.org.ua/archives/adept/2007/04/20/why-fp/</link>
		<comments>http://www.developers.org.ua/archives/adept/2007/04/20/why-fp/#comments</comments>
		<pubDate>Fri, 20 Apr 2007 04:59:47 +0000</pubDate>
		<dc:creator>ADEpt</dc:creator>
		
		<category><![CDATA[Разработка]]></category>

		<category><![CDATA[Статьи]]></category>

		<guid isPermaLink="false">http://www.developers.org.ua/archives/adept/2007/04/20/why-fp/</guid>
		<description><![CDATA[О чем эта статья?
В заголовке нет опечатки - это именно &#8220;ФЯ&#8221;, а не &#8220;Я&#8221;. Это сокращение означает &#8220;функциональные языки&#8221;, и речь в статье пойдет о функциональном программировании (FP), точнее, даже не о нем самом, а о том, стоит ли тратить свое драгоценное время на знакомство с ним.
В сети можно найти десятки вводных статей о FP, [...]]]></description>
			<content:encoded><![CDATA[<p><b>О чем эта статья?</b></p>
<p>В заголовке нет опечатки - это именно &#8220;ФЯ&#8221;, а не &#8220;Я&#8221;. Это сокращение означает &#8220;функциональные языки&#8221;, и речь в статье пойдет о <b>функциональном программировании</b> (FP), точнее, даже не о нем самом, а о том, стоит ли тратить свое драгоценное время на знакомство с ним.</p>
<p>В сети можно найти десятки вводных статей о FP, как на русском, так и на английском. Зачем писать еще одну? Чем эта <b>статья будет отличаться от уже существующих</b>?</p>
<p>Большинство существующих источников начинаются с описания того, <b>что такое FP</b> (и зачастую этим и ограничиваются). Очевидно, предполагается, что читатель сам проникнется полезностью принципов FP, увидит области его применения, и на него снизойдет просветление</p>
<p>Но, как показывает практика, <b>просветление</b> (в виде способности с первого взгляда видеть корректное и элегантное решение проблемы в выбранной парадигме программирования) никогда не приходит вот так сразу, после первого знакомства с темой.</p>
<p>Как правило, первая реакция на знакомство с чем-то радикально новым - это &#8220;<b>культурный шок</b>&#8220;, и FP в этом смысле не исключение. Типичная реакция может выглядеть так: &#8220;Как, жить без глобальных переменных? Что вы говорите, не просто без глобальных, а без переменных вообще? Без объектов и методов, без инкапсуляции? Тю, придурки, на дворе 21 век, а они без объектов&#8230; Нафига это надо? Пойду лучше GoF почитаю&#8221;.</p>
<p>Так вот, эта статья ставит целью в первую очередь дать ответ на вопрос &#8220;<b>нафига</b> знать/изучать/использовать FP?&#8221;.</p>
<p>Итак, не откладывая в долгий ящик: </p>
<p><b>Зачем же стоит изучать FP?</b></p>
<ol>
<li><b>Чтобы ознакомиться с новой парадигмой программирования.</b> Изучение новой парадигмы программирования приведет к расширению вашего кругозора, освоению новых подходов и вообще позволит взглянуть на все, что вы делаете под новым ракурсом. Вы не обязательно начнете применять все изученное на практике, но знание наверняка пойдет вам на пользу. Если вы сомневаетесь - подумайте, будет ли полезно человеку, программирующему процедурно, изучить OOP, даже если он никогда не будет программировать на C++ или Java?</p>
<p>Возможно, со стороны будет проще проникнуть в суть уже известных вещей и увидеть не только их положительные и отрицательные стороны. После знакомства с <a href="http://en.wikipedia.org/wiki/Multiple_dispatch">мультиметодами</a> станут видны недостатки модели передачи управления в OOP и OOP-инкапсуляции вообще. После изучения <a href="http://en.wikipedia.org/wiki/Type_polymorphism">параметрического полиморфизма</a> станут видны ограничения &#8220;inclusion полиморфизма&#8221; (того самого, который входит в знаменитую OOP-тройку &#8220;полиморфизм, инкапсуляция, абстракция&#8221;).</p>
<p>Знание <b>слабых сторон</b> ежедневно используемых инструментов, безусловно, позволит использовать их более <b>эффективно</b>.</li>
<li><b>Чтобы повысить свою производительность</b>. Многочисленные статьи утверждают, что использование функциональных языков способно увеличить скорость написание программ, уменьшить время на отладку и т.п. Впрочем, сложно верить статьям, хочется примеров из реальной жизни.
<p>Взгляните на перечень языков, которыми пользовались победители <a href="http://www.icfpcontest.org/#Previous">соревнований ICFPC</a>, в рамках которых необходимо за три дня реализовать довольно объемную алгоритмически сложную систему, или изучите статистику, собранную <a href="http://projecteuler.net">Project Euler</a>. Вы обнаружите, что существенная часть тех, кто выступил лучше всех (или попал в верхние 20%) использовала тот или иной язык функционального программирования.</p>
<p>Причем, если авторов ICFPC еще можно при желании заподозрить в предвзятом отношении к участникам, использовавшим функциональные языки, то projecteuler.net должны быть в этом смысле вне подозрений.</li>
<li><b>Чтобы понять, как можно связать программирование и математику</b>. Наконец-то можно будет получить ответ на вопрос, зачем вообще придумали эти чертовы частично-рекурсивные функции и лямбда-исчисление, или - о ужас - теорию категорий. Более того - окажется, что эта- и бета-редукции - это мощный инструмент оптимизации программ, а теория категорий помогает писать generic код.</li>
<li><b>Чтобы поглубже узнать то, что вы уже наверняка и так использовали (хотя бы раз)</b>. Многие принципы и инструменты, впервые появившиеся в функциональных языках, рано или поздно просачиваются в mainstream языки и получают широкое распространение. Примерами могут служить функции &#8220;map&#8221; и &#8220;filter&#8221; (&#8221;grep&#8221;) в <a href="http://perldoc.perl.org/functions/map.html">perl</a> и <a href="http://docs.python.org/lib/built-in-funcs.html">python</a>, <a href="http://docs.python.org/tut/node7.html#SECTION007140000000000000000">list comprehensions</a> в python, анонимные функции в этих же языках или анонимные методы в Java, весь <a href="http://en.wikipedia.org/wiki/XSLT">язык XSLT</a> целиком (хотя тех, кто придумал его синтаксис, убить мало), новомодный механизм <a href="http://en.wikipedia.org/wiki/Language_Integrated_Query">LINQ</a> и многое другое.</li>
<li><b>Чтобы быть в курсе новинок в области языков программирования</b>. В настоящее время в мире ведутся интенсивные исследования в области теории языков программирования.
<p>Практическая реализация результатов этих исследований происходит в виде расширения одного из существующих функциональных языков, поскольку функциональное программирование органично вытекает из теории вычислений, мат. логики и прочих математических дисциплин.</p>
<p>Примерами могут служить <a href="http://en.wikipedia.org/wiki/Currying">currying</a>, <a href="http://en.wikipedia.org/wiki/Monads_in_functional_programming">монады</a>, <a href="http://en.wikipedia.org/wiki/Software_transactional_memory">software transactional memory</a>, <a href="http://www.math.nagoya-u.ac.jp/~garrigue/papers/fose2000.html">polymorphic variants</a>, <a href="http://en.wikipedia.org/wiki/Erlang_programming_language">функции-как-процессы, их автоматическая миграция и прозрачное масштабирование runtime</a>. К сожалению, я отступаю от своего собственного правила, и даю ссылки на <b>&#8220;что&#8221;</b>, без объяснения <b>&#8220;зачем&#8221;</b>, т.к. тут материала еще на 10 статей. </li>
</ol>
<p>PS<br />
Традиционно те, кто впервые сталкивается с FP, одним из первых задают вопрос: &#8220;ну хорошо, если все так замечательно, то где серьезный софт, написаный на этих языках?&#8221;. Почти наверняка это вопрос возникнет и у читателей этой статьи, и лучше ответить на него сразу:
<ul>
<li> на Erlang написан Jabber server <a href="http://ejabberd.jabber.ru/">ejabberd</a>, который используется, в частности, на серверах jabber.kiev.ua и jabber.ru </li>
<li> на OCaml написан один из самых функциональных P2P клиентов <a href="http://mldonkey.org/">mldonkey</a> </li>
<li> на Haskell написана распределенная система контроля версий <a href="http://www.darcs.net/">darcs</a> </li>
</ul>
<p><b>UPD:</b><br />
Дополнения и исправления по материалам присланых комментариев:</p>
<ul>
<li> Тот полиморфизм, который в OOP - это inclusion polymorphism. А ad-hoc - это из процедурного стиля. Fixed.
</li>
<li> Вместо альфа-редукции следует читать эта-редукции. Fixed.
</li>
</ul>
<p>Дополнительный списко примеров real-world софта на ФЯ:</p>
<ul>
<li> Единственная уже работающая реализация perl6 сделана на Haskell: <a href="http://www.pugscode.org/">pugs</a>
</li>
<li> Haskell используется для финансовой аналитики в Credit Suisse: <a href="http://cufp.galois.com/slides/2006/HowardMansell.pdf">статья</a>
</li>
<li> <a href="http://galois.com/">Galois Connectios</a> разрабатывает на Haskell и OCaml инстументы и приложения в области криптографии и информационной безопасности
</li>
</ul>
<p>Надеюсь, что вы вынесли для себя из этой статьи что-то полезное. Я буду рад услышать ответную реакцию (в виде комментариев или писем на dastapov@gmail.com). Если статья вызовет интерес - возможно, на DOU появится постоянный раздел, посвященный FP.</p>
<br/><a href="http://www.developers.org.ua/archives/adept/2007/04/20/why-fp/#ratings">Оценить статью на сайте</a>.

<hr/>
<strong>Новое:</strong> мы запустили 
<a href="http://www.developers.org.ua/forum/">ФОРУМ</a>!
<hr/>
Свежая вакансия:


<a href="http://www.developers.org.ua/jobboard/752?ref=dou_rss">Test and Tool developer </a>
  (mindspeed)

]]></content:encoded>
			<wfw:commentRss>http://www.developers.org.ua/archives/adept/2007/04/20/why-fp/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
