четверг, 28 ноября 2013 г.

Немножко политоты



tl;dr: Все пишут, а я чем хуже? Евросоюз - плохо, Таможенный союз - хорошо. 

Короче, смысл этой заметки в том, что я сильно крепился, но не выдержал и решил высказаться по теме. 

Итак, для начала, никто не примет Украину в Евросоюз — об этом даже речи нет. Украина станет "ассоциированным членом" ЕС, как Израиль или Албания. Подробнее об этом можно прочитать в "соглашении об ассоциации Украины и Европейского союза". Это открытый документ на понятном языке на 236 страницах. Есть ещё и в оригинале — на 906 страницах. К майданщикам у меня вопрос - его кто-нибудь осилил не в пересказе? Врать не буду, я тоже не осилил, но всё же по диагонали полистал. 

Раздел IV, глава 1, часть 2 "Отмена пошлин" сыграет на руку не Украине, а Евросоюзу. Потому как Украина не сможет защищать свой рынок от импортных товаров. Да, я знаю, что вы хотите сказать: "это рынок, значит, туда нашим товарам и дорога, а их товары лучше, вот и будем пользоваться". Есть с этим одна проблема - закрытие производств, безработица и прочие радости прогрессирующего капитализма. Взамен будут открываться торгово-развлекательные центры в которых всё больше и больше людей будет продавать. Продавать европейские товары, отдавая прибыль на сторону. При этом, со стороны таможенного союза пошлины поднимут - и нам торговать будет не только нечем, но и не с кем.

Нельзя также переоценить вклад статьи 19 части первой "Перемещение персон" в общий развал экономики. Я, вообще, подозреваю, что как раз-таки за визы большая часть майданщиков и борются. Фиг с ним, что там будет на Украине, они за это время свалить успеют. Так вот, прогнозирую серьёзнейший отток трудоспособного населения, подстёгиваемый закрытием производств. Люди повалят пачками — уже в очереди стоят.

Статья 93 "доступ на рынок" в пункте 2 особенно хороша. Ограничение количества поставщиков услуг, как в форме количественных квот, монополий, эксклюзивных поставщиков услуг, так и требований проверки экономических потребностей. Именно этим способом будет рушиться экономика Украины.

Кого не задавят квотами, закроют стандартами. Раздел 4, глава 3 "Технические барьеры в торговле" говорит об обязательном переводе всех предприятий Украины, включая сельское хозяйство. Сроки жёсткие, а денег на это никто выделять не будет. И возникнет ситуация, когда на нашу территорию будут беспошлинно ввозить массу продукции, часть предприятий закроют квотами, а часть не сможет продавать товар на Украине же из-за несоответствия стандартам, а ненужные более квалифицированные и не очень кадры потекут на Запад.

А, и у нас на территории начнет согласно главе 9 "Стандарты, касающиеся интеллектуальной собственности" действовать европейский закон об авторском праве. А там за торренты или ex.ua вообще неиллюзорно сажают. И за ваш палёный фотошоп и украденный Microsoft Office - тоже.

Вообще со стороны Украины в документе, в основном обязательства. А со стороны Евросоюза - в основном требования.

Ладно. Когда осилю документ полностью, тогда и буду полемизировать. Давайте сделаем по-другому. Посмотрим на опыт последних стран, присоединившихся к Евросоюзу.

Литва.

Возьмём, например, Литву, член ЕС с 2004 года. Литва сейчас - председатель Совета Евросоюза, получает премии, числится участником антикризисного движения, приводится в пример другим союзникам.

Страна превратилась в "купи – продай", поскольку развитая в советские годы промышленность загублена напрочь, сельское хозяйство, стянутое путами квот и ограничений, до сих пор не может оправиться.  Кто-нибудь помнит вильнюсское объединение "Сигма"? Десятки тысяч ее работников выпускали добротную промышленную и военную электронику. Сейчас в ее просторных корпусах - торговые лавки и выставочные павильоны. В Клайпеде ушли в небытие ткацкая и чулочная фабрики, консервный завод, одно из крупнейших в Союзе рыбопромысловых объединений, в составе которого находилось более 400 судов. Стремительно сокращается поголовье крупного рогатого скота, а чтобы держать экспортную планку на взятой высоте, молокопереработчики прибегают к реэкспорту или гонят суррогат из порошка. Десятки тысяч грамотных, толковых работников выброшены на улицу, сидят на нищенском пособии или побираются и бомжуют. Более 20% семей прозябают за чертой бедности (вот хотя бы здесь). Такого не случалось ни в суровые годины войн или вселенских эпидемий: села и города, особенно малые, обезлюдели: страна официально потеряла более полумиллиона жителей. Вообще с вступлением в Евросоюз страны Прибалтики не в силах остановить массовую миграцию населения. Демография ужасающая — страны рискуют исчезнуть с карты мира.

Среди мигрирующих большинство — люди в возрасте от 20 до 30 лет. Если эмиграция будет продолжаться такими же темпами, то через 20-30 лет может сложиться ситуация, когда некому будет платить налоги в объеме позволяющем выплачивать пенсии.

Литва увеличила дефицит государственного бюджета с 2007 по 2009 г. в 8 раз и довела его до 12,1 % от размера ВВП. По итогам 2010 года дефицит бюджета составил уже 7,1% от ВВП. Государственный долг Литвы за этот же период вырос с 16,9 до 29,5%, это при том, что темпы экономического роста в этом государстве в 2012 году еще более замедлились. 

Патриоты Украины, вы именно этого хотите?

Латвия.

Уничтожено производство сахара. Полностью. Латвия не то, что не импортировала сахар, в этой маленькой Латвии было пять сахарных заводов. Это было национальное производство, часть их "самости". Закрыли и теперь Латвия импортирует сахар. Последний сахарный завод в Риге получил от ЕС 13.5 миллионов евро компенсации за закрытие. 

Квоты на сельское хозяйство и рыбную ловлю заставляют латышей наниматься на рабский труд на британские рыбные заводы. И это в стране, которую, я уверен, многие читатели помнят за шпроты. "Рижское золото" до сих пор ставят на качественных шпротах. 

Это я тем, кто говорит, что зато Евросоюз сохранит национальную культуру, язык и прочую "самость". Да пожалуйста! Пока это бизнесу не мешает, сохраняйте! А если мешает - извините.

В Латвии дефицит государственного бюджета вырос с 2007 по 2009 годы в 28 раз и достиг величины 10,2% от ВВП. По итогам 2010 года дефицит бюджета составил 7,7%. Государственный долг увеличился за это же время с 9,0 до 36,7%. В 2011 году размер дефицита бюджета страны сократился с 8 % до 5,5% от ВВП. Существует опасность того, что к 2014 году государственный долг этого государства возрастет до 52%.

И, естественно, за счёт уничтожения латвийского производства, Евросоюз решает свои проблемы перепроизводства и распределения.

Прибалтика в целом.

Уже через два три года после вступления стран региона в ЕС было зафиксировано увеличение инфляции. В 2007 году среднегодовая инфляция в Латвии – 10,1 процента, в Эстонии составила 6,6 процента, в Литве – 5,7 процента.

Сильно выросли цены на продукты питания, предметы повседневного спроса и лекарственные средства. В декабре 2007 года по сравнению с тем же периодом 2006 годом рост потребительских цен в Эстонии составил 9,6%, в Латвии – 14,1%, в Литве – 8,1%. В Латвии данный уровень является самым высоким за последние десять лет.

В Эстонии безработица выросла за период с 2007 по 2010 годы с 4,7% до 14,6%, в Литве – с 4,3% до 17,3%, а в Латвии – с 6,0% до 17,1%. Согласно статистике Eurostat, в первом полугодии 2011 года уровень безработицы в Латвии составил 16,2%, В Литве – 15,6%, в Эстонии – 12,8%, при среднем показателе по ЕС в 9,5%. Можно говорить о том, что в 2011-2012 годах ситуация с занятостью населения не улучшилась.


Венгрия.

Кто-нибудь помнит, что такое "Икарус"? Увы, их больше не делают. Квоты не позволяют серийное производство. 

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

Да и в целом там картина стандартная для новых членов Евросоюза - вот, почитайте.

Греция.

Закрыли судостроительные заводы. Решили, что лучше корабли строить в Германии. Уже после вступления в Евросоюз, греческие предприниматели заказывали у Германии более 700 кораблей для собственных нужд. Кораблей, которые раньше строили в Греции.

И как обычно из-за квот сократили фермы по выращиванию маслин и... вырубили виноградники. Это вообще трагедия, я считаю. Ну, о том, что сейчас в Греции добровольно заражаются СПИДом, чтобы получать пособие, не писал только ленивый.

Польша.

В свое время угольная промышленность Польши составляла основу экономики.

Хотя в последние годы перед членством в Евросоюзе отрасль нуждалась в государственной поддержке. В Польше после 2004 года закрыли 90% своих угольных предприятий, на которых работали 300 тысяч человек. Остальные 10% - были приватизированы и реорганизованы. В целом, большинству польских шахтеров пришлось искать другую работу.

Если кто-то думает, что в современной энергетике уголь не нужен, вам к школьному учителю или к врачу. Европа покупает уголь у США в огромных масштабах - например, в 2012 году он составил 66 миллионов тонн.

Еще жертвы - Гдыньская и Гданьская верфи. Гдыньская закрыта вообще. Гданьская сейчас разделена на два частных предприятия и почти не работает.


Прогноз.

Мы вступаем в ЕС в качестве ассоциированного члена. Закрываются производства, растёт потребление импортных товаров, особенно в той сфере, где мы никогда в них не нуждались - например, уголь или продукты. Огромное количество трудоспособного населения уедет работать на поля Испании, заводы Англии и стройки Ирландии. Взамен, если повезёт, нам сделают укол обезболивающего кредита, деньги быстро вернутся в качестве оплаты за импорт, а Украина останется должна ещё больше.

Россия.

Это заслуживает отдельной статьи, но если вкратце, Россия готова открыть свой рынок для нас и сохранять наше производство - стратегия Таможенного Союза другая. У нас со времён Союза даже остались общие технологические цепочки, которые и сейчас нетрудно восстановить. взяв курс на Россию, Украина вполне могла бы стать через какое-то время одним из самых развитых регионов будущей объединённой страны. Но, увы, украинские политики и украинские СМИ куплены Западом. Поэтому Украина будет сопротивляться всеми копытами курсу на сближение с Россией: это сближение категорически невыгодно Штатам и Евросоюзу. Таким образом, Россия будет вынуждена сейчас занять сугубо прагматичную позицию по отношению к Украине. А именно: вести себя с ней так, как она ведёт себя по отношению, скажем, к Турции. То есть, не пускать украинские товары на свой рынок, не давать скидок на газ. Строить на своей территории заводы, которые позволят в некоторой перспективе отказаться от высокотехнологичного импорта из Украины. Например, самолётных двигателей.

А ведь всё могло бы быть по-другому...

F.A.Q.

Этот раздел будет постоянно обновляться по мере поступления возражений. Я обещаю по мере свободного времени рассмотреть их все.

Невидимая рука рынка.

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

По этому поводу высказывались специалисты и аналитики, гораздо умнее меня. Однако же, я постараюсь.

Невидимая рука рынка не работает. Рынком нужно управлять, и этим занимаются буквально все государства. Антимонопольные комитеты, госпошлины, собственно, евростандарты производства, о которых говорилось выше. Зачем это нужно? Чтобы бизнес, целью которого является максимизация прибыли, не начал максимизируя её вредить интересам общества. Конкурентная борьба — это ведь не только производство более востребованного товара экономически более эффективным способом. Это ещё и уничтожение конкурентов, демпинг, промышленный шпионаж, патентные войны...

Американская автомобильная промышленность в своё время выстояла только благодаря грубейшим нарушениям законов свободного рынка. Пошлины на ввоз японских автомобилей уже были равны по стоимости самим автомобилям, и только тогда американцы начали иногда покупать американские автомобили. А если бы тогда была какая-нибудь интеграция и свободным рынком, в США автомобильной промышленности сейчас бы не было. А это огромные деньги, оставшиеся внутри страны, это рабочие места, это налоги, которые не уходят на сторону, это инженерные и научные кадры...

И вот тут такое дело, что для страны почти всегда лучше производить, чем импортировать. Это очевидно. И я не хочу импортировать в Украину продукты питания — иначе и цены на них выростут, как в Прибалтике, и денег у крестьян станет значительно меньше. А уж высокотехнологичное производство, даже, если оно и отстаёт, что не факт, от мирового, нужно сохранять - иначе из страны уедут специалисты и перспективы стать банановой республикой будут уже не за горами.

Да и, в конце концов, по законам рынка, неэффективные предприятия должны закрыться сами. Что это за "евростандарты производства", чтобы помочь этим жертвам рынка сдохнуть побыстрее.

В России только трубы.

Я процитирую. "Россия (и ее кореша лузеры из ТС) - болото, и там будет только хуже, все эти иллюзии про совковые производственные схемы, космические двигатели для марсианских ракет и тд, оно никому не надо - Россия живет на газе и нефти, кроме труб там ничего не надо никому."

Короткий ответ: http://www.sdelanounas.ru/. Полистайте подольше, если захотите придраться к одному-двум пунктам. Я отдам их вам, согласившись, что да, это никому не интересная чушь. Вряд ли вам удастся сделать это хотя бы с четвертью списка.

ВВП на душу населения.

В упомянутых странах растёт экономика. Это видно по росту подушевого ВВП. Доля частного сектора в экономике тоже сильно выросла, то есть, реально всё стало лучше. Естественно, ("нет нужды лишний раз пояснять"), выросла покупательская способность.

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

Ключевая штука здесь в конце: вне зависимости от национальной принадлежности. Аналогия тут такая: я вступаю с вами в соглашение и выращиваю на вашем поле силами ваших работников морковку, принадлежащую мне. Потом продаю. Потом, так и быть, что-то плачу вашим работникам. Ну и в конце прихожу вам рассказывать, какой замечательный урожай этим летом созрел на вашем поле. Вот это и есть ВВП. С покупательской способностью он связан слабо. Потому что много платить наёмным рабочим вы не обязаны.


пятница, 22 ноября 2013 г.

Функциональное реактивное программирование

Это примерный текст выступления "Функциональное реактивное программирование", которое Алексей Осипенко и я проводили на ноябрьской встрече Донецкого Лямбда-клуба.

Мир основан на изменяющемся состоянии. Мы живём во времени и думаем во времени, и разнообразнейшие события прилетают откуда ни возьмись, и тоже во времени. Как организовать приложение, для которого эти события являются вводом - то есть, любое интерактивное приложение? А если хочется не иметь изменяемого состояния, иметь чистые функции и работать как можно более декларативно? На эти вопросы отвечает функционально-реактивное программирование (functional reactive programming). Мы постараемся рассказать о таком подходе.


Нам очень нравится определение функционального программирования из выступления Дэниэла Спивака “Жизнь в пост-функциональном мире”. Это “функция как основная единица абстракции”. Основа основ функцинального программирования - это композируемые примитивы для решения широкого класса задач. За счёт этого о программах, написанных полностью в функциональной парадигме, легко рассуждать в терминах математики - они предсказуемы и даже доказуемы.


“Функциональное программирование объединяет гибкость и мощь абстрактной математики с интуитивной понятностью абстрактной математики”.


Реактивное программирование - это программирование приложений, управляемых событиями. Очень, надо сказать, востребованная штука, потому что это почти все приложения, которыми мы пользуемся - то, что называется, интерактивные приложения. В качестве примера исключения я бы привёл утилиты командной строки. Почти всё остальное - интерактивно и управляется событиями.


События имеют свойство происходить в произвольное время и совершенно не предсказуемы заранее. Чаще всего их обрабатывают как-то так:


function Counter(element) {
  var that = this;
  this.count = 0;
  this.clicked = function () {
    this.count += 1;  
  }
  $(element).click(function() {
    return that.clicked();
  });
};

Код простой и в нём можно очень быстро разобраться, но у него есть несколько проблем. Во-первых, ненавистное нашему сердцу изменяемое состояние, а во-вторых - очень плохая композируемость. Плохая композируемость означает, что код будет очень сложно использовать повторно в других частях приложения и объединять с другими подобными элементами.


Это рушит наш уютный мирок, лишая его доказуемости и предопределённости, запрещая весомую часть привычных техник построения программ и взамен порождая массу неприятных эффектов.


Однако же мы не зря сюда пришли. Реактивное программирование, о котором мы хотим рассказать, основывается по большому счёту на концепциях из функционального программирования и его способах заводить примитивы.
Лирическое отступление: почему мы про UI и где ещё это применимо. Это применимо везде, где есть входящие события. Ну вот просто везде. Однако UI ставит наиболее интересные и сложные задачи по комбинации потоков: хоть сколь-нибудь развесистый пользовательский интерфейс начинает требовать всё более и более новых и интересных комбинаторов.


Идея примитивов ФРП вкратце. Естественный код:


$("#login").on("click", function (event) {
  var value = $(event.target).val();
  if (value.length > 0) {
    $("#notice").text(value);
  }
});


Вводим один уровень абстракции:


var clickE = $("#login").events("click");
clickE.onValue(function (event) {
  var value = $(event.target).val();
  if (value.length > 0) {
    $("#notice").text(value);
  }
});


На первый взгляд кажется, что мы ничего не добились. Однако же, теперь с этим примитивом мы можем кое-что делать.


var clickE = $("#login").events("click");
var values = clickE.map(function (event) {
  return $(event.target).val();
});
var nonEmptyValues = values.filter(function (value) {
  return value.length > 0;
});
nonEmptyValues.onValue(function (value) {
  $("#notice").text(value);
});


Ну и инлайним лишние переменные:


var values = $("#login").events("click").map(function (event) {
  return $(event.target).val();
}).filter(function (value) {
  return value.length > 0;
});

values.onValue(function (value) {
  $("#notice").text(value);
});


Лирическое отступление: point-free style или tacit programming. Если функция - гражданин первого класса, то имея богатую библиотеку хелперных функций, можно писать, избегая термов. Получается элегантно.

var values = $("#login").events("click").map(
  chain(pluck('target'), $, method('val')
).filter(nonEmpty);
values.onValue(function (value) { $("#notice").text(value); });


Теперь немного разберёмся с денотационной семантикой. Функциональных примитивов для ФРП - два. Традиционно их называют Event и Behaviour, мы же по своих соображениям возьмём другие - Stream и Box.


Stream представляет собой дискретный поток событий и семантически эквивалентен списку кортежей “время-значение” при неубывающем времени:


type Stream[a] = [(T, a)]


Box представляет собой непрерывное значение, изменяющееся во времени и семантически эквивалентен просто функции времени.


type Box[a] = T -> a


На этих двух китах стоит функциональное реактивное программирование. Отличаются они тем, что при подписке на поток вы не получаете значения последнего произошедшего события - только следующее. Подписавшись же на коробку, вы сразу получаете значение последнего произошедшего события, и начинаете ждать следующего.


Лирическое отступление: push против pull. Семантика, которую я дал для события и поведения приводит нас к pull-driven реализации, которая, хоть её очень легко понять, чисто и красиво написать и замечательно о ней рассуждать и доказывать, по эффективности сильно проигрывает push-driven реализации. Однако же, push-driven реализация в целом более некрасива и сложна. Мы будем говорить в основном о push-driven реализации, потому что только ей можно нормально пользоваться в реальном мире.


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


Что у нас в потоке может быть? Всё. Простейшие конструкторы:


Box.nothing();   // empty box
Box.unit(10);    // box with constant value of 10
Stream.never();  // empty stream
Stream.unit(10); // stream with immediate value of 10


Самоочевидно. 

В дополнение - конструктор ошибки. Тема обработки исключительных ситуаций в функциональном программировании сама по себе интересна, но если вкратце - это просто возвращение некоего особого значения, которое при дальнейшей обработке, если с ним пытаются работать, как с нормальным возвратом, игнорируется и не изменяясь плывёт по цепочке вычислений в ту точку, где кто-то поинтересуется-таки, а не лежит ли там ошибка - и что-то с этим сделает.


Введём и мы у себя понятие Error события, которое раз появившись будет нетронутым проходить все нормальные ошибки и комбинаторы до тех пор, пока кто-то специально не поинтересуется. Естественно, нужен элементарный способ сконструировать ошибку.


Stream.error(10); // stream with immediate error of 10
Box.error(10);    // box with constant error of 10

Это UI, поэтому, здесь будет полезна работа с временем. Базовый конструктор для этого:


Stream.later(1000, 10); // in a second pops out 10


Более интересно - из DOM-события. Это логично добавить в jQuery.fn:


$('input').stream('keyup'); // stream with keyup events


Ещё хочется привязать стрим к какому-то событию в будущем. Можно создавать стрим из функции обратного вызова (callback), а можно воспользоваться другой абстракцией из функционального мира: promise.


Вот это способ обработать конец асинхронного действия с функцией обратного вызова:


asyncCall(data,function(response){
  // You have response from your asynchronous call here.
}, function(err){
  // if the async call fails, the error callback is invoked
});


А вот так это делается с помощью promise:


var promise = asyncCall(data);

promise.done(function(response){
  // You have response from your asynchronous call here.
}).fail(function(err){
  // if the async call fails, you have the error response here.
});


То есть, всё как обычно в функциональном программировании - возвращаем специальное значение и можем с ним что-то такое делать. В новом jQuery обещания используются очень активно - все вызовы ajax и все анимации возвращают такой promise, который успешно завершается при хорошем ответе от сервера или конце анимации, а неуспешно - при плохом ответе от сервера (мне трудно придумать fail для анимации).


Интерфейс очень удобный и очень нужный, поэтому мы им и возпользуемся:


Stream.fromPromise($.ajax(params)); 
// pops a success event on successful response
// or the error event on error response

Итак, что мы можем сделать с нашими потоками?


Самое простое - отобразить, или пропустить через функцию. Для коллекций, наверно, это всем знакомо.


_.map([1, 2, 3], function (x) { return x*x; }); // returns [1, 4, 9]


На потоке и коробке map работает совершенно очевидным способом - получается новый поток или коробка, значения на которой соответствуют исходным, пропущенным через функцию.


priceValue.map(function(x) {
  return x > 1000;
});


В примере мы получим булевое значение, характеризующее, достаточно ли денег предлагает пользователь.


Лирическое отступление. Вы только что познакомились с функтором. В функциональном программировании, функтор - это контейнер, позволяющий выполнить некоторую функцию над своим содержимым и завернуть результат в такой же контейнер. Эта операция обычно называется fmap. В этом смысле, список и наша коробочка со значением (или поток событий) - одно и то же. Потому что их можно обработать единообразно - пропустить значения через функцию, сохранив форму контейнера. fmap там ещё должен удовлетворять двум простеньким законам, и если будет интересно, мы про них расскажем. А пока продолжим.


Рядом со словом map часто звучит что? filter. Rails программисты знают его под словом “select”.


priceValue.filter(function(x) {
   return x >= 1000;
});


Мы получим значение цены, если оно превышает 1000. Опять-таки, хорошая аналогия с коллекциями.


Ладно. Перейдём к по-настоящему интересной функции. flatMap. Это тоже преобразование значений в контейнере, но существенно более общим образом. Функция, которая передаётся в flatMap должна возвращать не значение, а значение, завёрнутое в новый контейнер. Сам же flatMap каким-то образом связывает результаты этой функции и возвращает опять-таки обёрнутое значение. Что-то на словах сложно получается.


Ну вот, например, есть список пользователей, а у каждого из них есть список комментариев. Нужно получить список всех комментариев.


_.flatMap(users, function (user) { return user.comments(); });
// returns flat list of all the comments the users have


Обратите внимание, что comments() может вернуть и пустой список - что, в свою очередь, никак не вложится в результат.


В нашем случае функция внутри flatMap должная возвращать не список, а поток или коробку. А вот связывать результат можно по-разному. Можно в результирующий поток замешать события всех дочерних потоков. Это наиболее логичная стратегия для потока. А можно подписаться на первый дочерний поток, а при появлении второго, переключиться на него и отписаться от первого. Это наиболее логичная стратегия для коробки.
Заканчивать же результирующий поток будем тогда, когда все дети и родительский поток закончились.


Зачем нам такое преобразование? Хм. Сейчас покажу. Это невероятно мощная штука. Для начала, её можно использовать для прозрачного связывания локальных потоков с асинхронными действиями.


var requests = usernames.map(function(u) {
  return { url: "/check-username/" + u; }
});
requests.flatMap(function (params) {
  return Stream.fromPromise($.ajax(params));
});
// usernames  => "John",          "Peter"
// requests   => {"url": "/check-username/John"}, ...
// flatMap    => {correct: true}, {correct: false}

Во-вторых, с её помощью можно написать map и filter.


Stream.prototype.map = function (f) {
  return this.flatMap(function (x) {
    return Stream.unit(f(x));
  });
}

Stream.prototype.filter = function (f) {
  return this.flatMap(function (x) {
    if f(x) {
      return Stream.unit(x);
    } else {
      return Stream.nothing();
    }
  });
}


Но наибольшие возможности flatMap открывает для написания комбинаторов. Давайте предположим, что нам нужно скомбинировать две коробки в одну. Например для получения значения “пароль и подтверждение” заполнен корректно.


password.map2(passwordConfirmation,
              function(l, r) { return l == r; });


Давайте попробуем написать map2:


Box.prototype.map2 = function (other, f) {
  return this.flatMap(function (x) {
    return other.map(function (y) { return f(x, y); });
  });
}



Поскольку операция map2 имеет смысл только на Box, мы используем версию flatMap, которая берёт только последний поток.


Те, кто сейчас сказал “о, я всё понял!” - поздравляю, вы познакомились с монадами.


Да, монада - это способ положить простое значение в контейнер и способ его достать и сделать новый контейнер. Ну плюс три простых как азимовские закона, о которых мы тоже можем рассказать. Положить простое значение в контейнер принято называть unit (мы его назвали так же), а сделать новый контейнер принято называть bind (мы его назвали flatMap и это имя реально имеет шанс победить, потому что на Scala тупо больше человек пишет код за деньги).


Поскольку мы получили монаду, мы получили аппликативный функтор, и это подтверждается наличием map2 (который позволяет нам тривиально определить apply).


Box.prototype.apply = function (other) {
  return this.map2(function (f, x) { return f(x); } );
}

Собственно, аппликативный функтор позволяет скомбинировать функтор, содержащий функцию с функтором, содержащим значение, и получить функтор, содержащий преобразованное значение. Ну и 4 мудрёных закона, о которых можно поговорить отдельно.


Для потоков же, вместо map2, мы сделаем не менее полезную функцию - merge, позволяющую просто объединить потоки в один. Используя flatMap для потоков, который слушает все дочерние потоки, сделать это очень просто.


var id = function (x) { return x; }
Stream.merge = function (streams) {
  return Stream.fromList(streams).flatMap(id);
}


merge - полезная штука.


var inc = plusClicks.map(function() { return 1; });
var dec = minusClicks.map(function() { return -1; });
var change = plus.merge(minus);


Ещё пара примеров фокусов с flatMap:


Stream.prototype.debounce = function (delay) {
  return this.flatMapLast(function (value) {
      return Stream.later(delay, value);
  });
}

debounce сглаживает слишком частые события. То есть, если на исходном потоке события будут возникать чаще, чем delay, то отправится только последнее из них. Здесь мы используем неродной flatMap, на что и указываем.

Или вот - очень полезный комбинатор sampledBy:


Box.prototype.sampledBy = function (sampler) {
  var that = this;
  return sampler.flatMap(function () { return that.take(1); } );
}


О нём легко думать, как об отображении (map) потока на коробку.


Вот как просто и красиво таким способом записывается такая сложная и неприятная вещь, как drag and drop. Получаем события мыши:


mousedown = $(document).stream('mousedown', '.item')
mouseup = $(document).stream('mouseup')
mousemove = $(document).stream('mousemove')


Легко и непринуждённо получаем такую прелесть, как событие mouseDrag:


mousedrag = mousedown.flatMapLast( 
  (e)-> mousemove.takeUntil(mouseup)
)


Ловим положение курсора...


cursorPosition = mousemove.box().map(
  (e)-> { x: e.clientX, y: e.clientY }
)


...в нужные нам моменты…


startPosition = cursorPosition.sampledBy mousedown
currentPosition = cursorPosition.sampledBy mousedrag


Потом немножко считаем…


shiftPosition = startPosition.zip(currentPosition)
  .map((s, m)-> 
    { left: m.x-s.x, top: m.y-s.y }
  )


...и двигаем объект, куда просили:


shiftPosition.onValue( (pos)-> $('.item').css(pos) )


Ах, да, и просим браузер не умничать:


mousedown.onValue (e)-> e.preventDefault()

Можно ещё вместо этого обойтись замыканиями:


newPosition = mousedown.flatMapLast (md)->
 target = $(md.target);
 {left, top} = target.offset();
 [startX, startY] = [md.clientX - left, md.clientY - top]
 mousemove.map (mm)->
   target: target
   left: mm.clientX - startX
   top: mm.clientY - startY
 .takeUntil mouseup


В общем, инструмент гибкий и мощный, и единственное, чего ему не хватает - это паттернов применения. То есть, нужно, чтобы побольше людей так писало - тогда они будут ставить и решать реальные проблемы и пустота начнём кое-как заполняться.


Итак, как теперь жить:


  • Bacon.js, полностью рабочая библиотека.
  • jnoid, демонстрационная библиотека, которая была написана, чтобы разобраться в теме и другие тоже могли в ней разобраться. Для этого даже было использовано “литературное программирование”.
  • javelin для closurescript от пользователя с одним из самых зачётных ников на Гитхабе.
  • elm для маньяков. Красивая вещь для поразбираться, не очень подходит для работы - там jQuery нету.


Берите любой и пробуйте решать проблемы. Не пожалеете.

Другие ссылки: