Показаны сообщения с ярлыком Функциональное программирование. Показать все сообщения
Показаны сообщения с ярлыком Функциональное программирование. Показать все сообщения

четверг, 8 сентября 2016 г.

[prog.flame] Давно что-то не писал про ООП vs ФП, надо исправиться... ;)

Довелось опять пересечься с приверженцами функционального подхода к программированию (для которых отстой все -- начиная от ООП и заканчивая исключениями). Поймал себя на давно забытом ощущении: у функциональщиков большинство примеров сильно оторванны от прикладных целей. Во времена, когда ООП завоевывало себе место под Солнцем апологеты ООП расказывали о достоинствах ООП на простых и понятных примерах. Вот, типа, вам нужны средства для рисования кругов, квадратов, треугольников и т.д. Или вот вам нужны средства для показа менюшек и диалогов. И эти примеры нормально заходили, т.к. без ООП все это приходилось делать на процедурных языках вроде C или Pascal. Это было сложно, плохо расширяемо, трудоемко в поддержке и т.д. Тогда как с ООП все оказывалось ощутимо проще. Ну, если хотите, не проще, а управляемее. Появлялась более удобная структура в коде, больше устойчивости (т.е. правки в одной части вовсе не обязательно сказывались в остальных частях), расширяемость улучшалась и т.д.

Понятно, что ООП -- это никакая не серебрянная пуля. И даже на заре проникновения ООП в мейнстрим (по крайней мере в наших Палестинах) было очевидно, что на ряде задач выигрыш от ООП может быть только если задача объемная и предполагает развитие в течении длительного времени. Помнится, некоторые аппологеты, вроде Гради Буча, даже предупреждали, что с ООП придется потратить гораздо больше сил и времени по сравнению с процедурным подходом на начальном этапе разработки. Т.е. на C или на Pascal-е вы бы уже могли написать худо-бедно работающий прототип, а на C++, Eiffel или SmallTalk еще бы только-только завершали фазу ОО-анализа и, возможно, фазу ОО-проектирования. И, скорее всего, не написали бы еще никакого кода.

Тем не менее, при всех своих проблемах ООП всегда (по крайней мере так было в начале 90-х) крутился вокруг практических вещей. Вот здесь у нас будет класс Строка. Ok, понятно что это и зачем. Вот здесь -- класс Файл. Снова Ok, снова понятно что и зачем. Вот здесь -- класс COM-порт. И снова Ok, снова все понятно. Аналогично с классами Меню, Диалог, Конфиг, Таймер, Нить и т.д. И вот уже из понятных частей собирается GUI-приложение для работы с неким внешним устройством через COM-порт.

Поэтому ООП было более-менее просто объяснять людям. Мол, смотри, у тебя будет COM-порт. Мы сейчас точно не знаем, что именно будет у него внутри, но более-менее понимаем, как с ним работать. Поэтому давай сделаем набросок класса COM-порт с несколькими очевидными публичными методами и пойдем дальше... На какой-то N-ой итерации мы возвращаемся к нашему классу и уточняем еще чуть больше. А потом еще и еще раз.

Т.е. ООП можно было объяснять "на пальцах", отталкиваясь от практических нужд конкретного разработчика.

Тогда как с ФП лично мне таких объяснений, которые бы отталкивались от практики, особо не попадается. Если говорить совсем грубо, то получается что-то вроде:

-- Вот смотри, у нас есть функция f a b -> c...
-- А что это за функция? Для чего она?
-- Это не важно. У нас просто есть функция f a b -> c. У нее два аргумента. Но, на самом деле, ее можно представить как последовательность функций с одним аргументом f a -> b -> c...
-- И что?
-- И мы может делать каррирование, когда мы из f a b -> c, получаем g b -> c с зафиксированным значением аргумента a. А все месте нам это дает возможность композиции функций!
-- Композиции функций для чего?
-- Для чего угодно. Вот смотри, допустим, нам нужно применить к данным функции x, y и z...
-- К каким именно данным и что именно делают функции x, y и z?
-- Это не важно, важно то, что через композицию мы можем сделать так, что разультат x идет в y, а разультат y идет в z. Мы просто описываем композицию функций декларативно в коде и все.
-- Что все? Какие у нас данные? Как именно мы их обрабатываем? Зачем мы это делаем?
-- Это не важно, я тебе принцип объясняю.

Вот, скажем, как вам такая статья на Хабре: Монады и do-нотация в C++? Но не просто как статья, как демонстрация принципа, на котором можно связать в цепочку последовательность операций. Например, B*x + C, где B и C -- это матрицы, а операции умножения и сложения могут завершаться неудачно.

Собственно, сей пост не является попыткой бросить камень в само ФП. Как по мне, так изрядная часть фишек ФП оказалась в императивном мире давным давно (например, если посмотреть на языки SmallTalk и Ruby, в которых блоки кода есть ни что иное, как лямбды, а изрядная часть методов в стандартной библиотеке базируется на использовании функций высших порядков). И использовалась программистами задолго до возникновения хайпа вокруг ФП, причем многие до сих пор не подозревают об этом, дергая Enumerable#map. Ну и вообще, очень многие вещи из ФП вполне себе просты и понятны, если их объяснять без чрезмерного использования математических формул :)

Речь о том, что рекламе ФП и раньше недоставало приземленности. И сейчас ничего особо не изменилось, как по мне. Поэтому если человек не фанат изучения новых парадигм и языков, и его не прет от реализации моноидов на шаблонах C++, он не тащится от того, насколько генерики+трейты в Rust-е удобнее генериков в Java, а ему тупо нужно взять набор байтиков отсюда и переложить их вот туда, то осознать полезность фишек ФП ему сейчас ничуть не проще, чем 10 лет назад. Имхо, конечно.

PS. Кстати говоря, ничуть не удивлюсь, если для современной молодежи, которая начала учиться программировать сейчас или даже лет пять назад, ситуация видится прямо противоположной. Вероятно, для нонишних новичков в программировании базисом, с которого они стартуют, являются такие вещи, как функции высших порядков, алгоритмы вроде foldr/foldl/zip, алгебраические типы данных и т.д. А вовсе не правила структурного и модульного программирования, с осознания важности которых довелось начинать моему поколению.

понедельник, 13 июня 2016 г.

[prog.flame] Любопытная заметка "Disadvantages of purely functional programming"

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

Имхо, статейка Disadvantages of purely functional programming как раз в эту же тему. Суть -- чистое ФП классная штука, конечно. Но тормозит. И, в принципе, в определенных местах не может не тормозить.

Отдельно отметил бы ссылочку, которая есть в этой статье: Naive parallelism with HLVM. Особенно мне понравились пути повышения производительности HLVM-программы: Disabling bounds checking provides a substantial 20% performance improvement. Disabling the shadow stack (and, therefore, disabling GC) provides another 25% performance improvement. With these two changes, HLVM is only 24% slower than C++. Ну, т.е., берем язык более высокого уровня, чем C++, и гораздо более безопасный, чем C++, а потом отключаем нафиг те фичи, которые как раз и делают его более высокоуровневым и более безопасным. И все только для того, чтобы не тормозить так сильно.

Ну и вообще, как мне кажется, в последние год-полтора прослеживается интересная тенденция: если раньше во главу угла ставилась скорость разработки и на передний план выходили безопасные языки программирования (причем даже не Java, а всякие Python-ы, Ruby, Erlang-и, Scala, Clojure и прочие OCaml-ы с Haskell-ями), то теперь просто скорости разработки мало. Нужно еще, чтобы результат работал быстро и/или жрал мало. Отсюда и интерес к тому же Go, который позволяет писать что-то так же быстро, как и на Python-е, но работает затем это что-то намного эффективнее, чем на Python-е. И возродившийся интерес к старой ламповой сишечке (а так же вспыхивающие вновь споры C vs C++). И пристальное внимание к Rust-у и Nim-у...

Объяснение сей тенденции я вижу в том, что делать быстро на Python-е (Ruby, Erlang, Scala, Clojure,...) теперь могут практически все. И одним из важнейших факторов становится снижение издержек на эксплуатацию. Для чего нужны не только быстро выходящие на рынок решения, но еще и эффективно работающие. А вот это умеют делать пока не только лишь все :)

PS. Кстати говоря, то, что все стараются друг другу что-то продать (кто-то "продает" Haskell, кто-то "продает" Clojure, я, например, "продаю" SObjectizer) -- это нормально. Правильно сделанная "покупка" сильно облегчает жизнь. Однако, важно понимать, что в процессе "продажи" мало кто будет на 100% откровенен и будет честно и по собственной воле рассказывать не только о достоинствах, но и недостатках. Так что никому нельзя верить. Мне можно ;)

PPS. Прошу воспринимать написанный выше текст с изрядной долей юмора и иронии.

понедельник, 8 декабря 2014 г.

[prog.flame] ФП приходит в мейнстрим и... занимает нишу COBOL?

Просматривая свежий пост про пример использования парсинга XML в Scala на eax.me, поймал себя на неожиданной мысли: где-то что-то отдаленно похожее я уже видел. Кстати говоря, пост хороший. Имхо, именно так нужно привлекать внимание людей к языкам/технологиям/подходам/парадигмам. Небольшой, но практичный пример, вполне себе самоценный и самодостаточный. Собственно, пупуляризаторам чего бы то ни было (в том числе и ФП), на заметку.

Довольно симпатично выглядят некоторые фрагменты кода в примере, скажем, вот этот:

val postsMap =
  postsXml.map{
    n => ( (n \ "thread" \ dsqid).text.toLong,
           (n \ "createdAt").text,
           (n \ "author" \ "name").text,
           (n \ "message").text,
           n
         )
  }.filter{
    case t =>
      (! (t._5 \ "isDeleted").text.toBoolean ) &&
        (! (t._5 \ "isSpam").text.toBoolean )
  }.groupBy(_._1).filter{
    case (k,v) => threadsMap.get(k).isDefined
  }.map{
    case (k,v) =>
      ( threadsMap(k),
        v.map(t => (t._2, t._3, t._4) ).toArray.sortBy(_._1)
      )
  }

Сперва подумалось, что, блин, обработка данных посредством конструкций из ФП выглядит весьма привлекательно. Хотя какая-то доля лишнего "синтаксического оверхеда" (с) таки присутствует. И что от него можно было бы избавиться. Но, если синтаксис будет сильно заточен под манипуляции над данными, то....

...то в памяти всплывают отрывочные воспоминания о такой штуке, как CODASYL. Был когда-то такой мертворожденный стандарт для работы с данными, представленными в иерархической и сетевой моделях данных. Базировавшийся на COBOL-е. А COBOL -- это же как раз язык для обработки данных.

Конечно, если сравнивать примеры кода на COBOL/CODASYL с современными примерами на Scala, на первый взгляд будет казаться, что это совершенно разные вещи. Но мне представляется, что по сути, это лишь очередная попытка записать что-то подобное:

      PROCEDURE DIVISION.
       CONTROL-PARA.
      *
      * CONTROL PARAGRAPH. DATABASE IS INVOKED AND PRIVACY
      * KEYS SPECIFIED. READS FIRST RECORD FROM INPUT FILE
      * AND CALLS MAIN PROCESSING ROUTINE UNTIL A DATABASE
      * ERROR OCCURS OR AN END OF FILE CONDITION IS SIGNALLED.
      * WHEN AN ERROR OCCURS THE ENTIRE TRANSACTION IS
      * ABORTED OTHERWISE IT IS SECURED.
      *
           OPEN INPUT CARINP.
      #    INVOKE DBMS.
      #    PRIVACY KEY FOR UPDATE OF ALL AREAS IS 'CAR-LOCK'.
      #    PRIVACY KEY FOR STORE OF RECORD DEPOT IS 'NEW-DEPOT'.
      #    PRIVACY KEY FOR INSERT OF SET DEPOT-CARS IS 'CAR-INPUT'.
      #    PRIVACY KEY FOR INSERT OF SET MECHANICS IS 'MECH-AMEND'.
      #    OPEN AREA CAR-AREA USAGE-MODE IS UPDATE.
      #    START TRANSACTION TRAN-ID, UPDATE.
           PERFORM DATA-INPUT.
           PERFORM PROCESS-TRANS UNTIL EOF OR DB-ERR.
           IF DB-ERR PERFORM ABORT-TRANS
           ELSE PERFORM END-TRANS.
      #    CLOSE ALL AREAS.
      #    EXIT DBMS.
           CLOSE CARINP.
           STOP RUN.

Но только в современном, модном и молодежном, ФП-шном стиле :)

PS. Ну и раз уж в кои-то веки затронул тему ФП, то чтобы два раза не вставать. Посмотрел видео доклада Сергея Зефирова (aka thesz) с недавней конференции f(by). Повеселил ответ Зефирова на вопрос про использование Haskell-я в mission critical приложениях (приблизительно на 48:00). Типа, напишу на Haskell-е генератор, который сгенерирует уже прикладной код на низкоуровневом языке приложения из Haskell-евского DSL-я. Ну, может в моделировании процессоров этот подход разумен. Однако, для какого-нибудь OZON-а или Pleer.Ru (не говоря уже про eBay и Amazon) их система онлайн-торговли есть самое что ни есть mission critical приложение. Забавно было бы посмотреть на попытку реализовать что-то подобное на Haskell-евском DSL-е, из которого бы генерировался PHP или Java, или даже C-шный код :) Понятно, что вопрос задавали про несколько другие mission critical приложения, но, AFAIK, современные системы управления сложным оборудованием, разрабатываемые с использованием стандарта DDS, это тоже очень и очень объемные задачки (например).

PPS. На счет COBOL-а, давным-давно достигшего "состояния бессмертности": #1 и #2.

понедельник, 19 мая 2014 г.

[prog.c++11] Еще на тему использования функционального стиля в современном C++

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

Если нужно, что бы заявкой в очереди сообщений был какой-то самостоятельный объект, то в традиционном объектно-ориентированном подходе нужно было бы определить базовый класс, для примера, demand_t с виртуальным методом handle(), от которого нужно было бы наплодить столько наследников, сколько вариантов поведения требовала бы прикладная логика. И, в старом-добром C++, все это делалось бы на объектах, да еще и с применением какого-то варианта умного указателя, т.к. в очереди заявок сохранялись бы указатели на demand_t (а в самом хардкорном варианте в очереди были бы голые указатели, за временем жизни которых нужно было бы следить вручную).

Но стандарт C++11 уже несколько лет как утвержден. И теперь у C++ разработчиков есть выбор: делать ли такие заявки посредством объектных иерархий или же задействовать std::function (а так же лямбда-функции с замыканиями). Но, если делать выбор в пользу std::function, то не придется ли платить за это больше, чем за объекты и std::shared_ptr?

Ради проверки этого я написал для себя небольшой тест, исходный текст которого упрятан под кат. Проверял под VC++11, VC++12 и GCC 4.8.2 под 64-х битовым Windows 7.

Upd. Ув.тов.Андрей Валяев обнаружил ошибку в моем коде. После ее исправления выяснилось, что код на std::function где-то на 35-40% медленнее кода с std::shared_ptr и объектами.

Upd2. Посредством небольшого трюка можно разогнать очередь std::function очень сильно и std::function начнут обгонять std::shared_ptr.

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

И еще одно небольшое дополнение напоследок: думаю, ситуация, когда заявка будет иметь всего один метод handle, т.е. когда объект-заявка может быть представлена функциональным объектом один-в-один, является частным случаем. На мой взгляд, велика вероятность, что у объекта-заявки будет несколько виртуальных методов. Если не сразу, то в процессе эволюции кода. И если иерархия конкретных заявок изначально представлена в виде объектной иерархии, то произвести модификацию кода с объектами будет проще, чем с std::function.

среда, 8 января 2014 г.

[prog.flame] Хватит тратить миллиарды долларов на код на неправильных языках...

G+ подкинул ссылочку на пост, от которого у меня бы зашевелились волосы на голове, будь они еще там: Stop wasting billions of dollars using the wrong software languages. Его смысл очень прост: Хватит тратить миллиарды долларов на код, написанный на плохих языках программирования. Начните тратить их на код, написанный на расово чистом... упс, не так богоугодном... опять не то, единственно правильном... не, просто на Хаскелле.

Блин, желание продать то, что делаешь, конечно, понятно (а делают и продают FPComplete). Но "Мифический человекомесяц" появился в 1975-м. А "Человеческий фактор" в 1987-м. Так что, мать вашу за ногу, к 2013-му году можно было придумать что-нибудь поинтереснее, чем тупые рекламные заявления вида "Наши серебрянные пули самые серебрянные пули в мире! И мы можем подтвердить это десятками телеграмм от наших счастливых покупателей по всему миру..."

суббота, 26 ноября 2011 г.

[prog] Разработчики Scala хвастаются ростом популярности своего языка

Свежая заметка на www.scala-lang.org: Scala - Popularity and Use Grow.

Мол и используется Scala в крутых конторах (Twitter, LinkedIn, Foursquare, the guardian, Morgan Stanley, Credit Swiss, UBS, HSBC), и количество посетителей сайта scala-lang.org выросло, и количество загрузок Scala увеличилось, и вакансии для Scala-разработчиков уже не редкость… Лепота, в общем.

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

Меня же интересует вот что: либо я совершенно не в теме, либо же Рунет со своими профессиональными форумами (RSDN, LOR, OpenNet) совершенно не отражает тенденции в буржуинии. Поскольку у меня сложилось впечатление, что чем дальше, тем меньше в Рунете говорят о Scala (а если говорят, то не очень хорошее). Куда больше мне доводится слышать про Erlang и Haskell, чуть реже про OCaml и различные Lisp-ы.

Так вот интересно – есть ли рост какой-нибудь востребованности Scala на просторах бывшего Союза? Или то, о чем рапортуют Scalaделы, касается только Запада?

среда, 21 сентября 2011 г.

[prog] Презентация Мартина Одерски State of Scala с конференции Scala Days 2011

PDF-ка на 42 страницы. (Ссылка найдена здесь, но там висит предупреждение, что в будущем URL может измениться. Пока же там можно найти ссылки на статьи, слайды и видео других докладов).

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

После просмотра презентации осталось несколько впечатлений.

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

def encode(number: String): Set[List[String]] =
  if (number.isEmpty)
    Set(List())
  else {
    for {
      splitPoint <- 1 to number.length
      word <- wordsForNum(number take splitPoint)
      rest <- encode(number drop splitPoint)
    } yield word :: rest
  }.toSet

Чем хороши языки вроде C++, Java, C#, Eiffel, D – почти не имея опыта работы с языком все-таки можно прочти сразу понять, что вызывается и с какими параметрами. А вот в функциональщине, если с ней плотно не работать каждый день – фигушки :( Ближе нужно быть к народу, господа ученые :)

Во-вторых, есть у меня сомнения в том, что такие штуки, как параллельные и персистентные коллекциидолжны быть введены раз и навсегда в языке (хотя я не уверен, что Одерски под персистентными имел в виду именно хранящиеся во внешней памяти). Имхо, такие вещи должны быть на уровне библиотек – попроще и посложнее, с ориентацией на различные задачи, с разным уровнем адаптации под нужды разработчиков. Хотя, если цель завлечь в параллельное программирование бесплатной первой дозой – тогда становится понятно. Мол, просто берешь стандартный SortedSet, потом просто вызываешь par и ты уже в Хопре! А что там – под кaпотом – разве кого-то сейчас это интересует, по большому-то счету? ;)

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

Думаю, что такое мелкое DSL-естроение не есть хорошо, поскольку ведет к усложнению процесса сопровождения программы разными людьми, у каждого из которых есть собственное чувство прекрасного (подробнее см.Social Problems Of Lisp).

Но, полагаю, Одерски говорит о “большом DSL-естроении”, когда DSL-и разрабатываются не под конкретные мелкие сиюминутные нужды отдельных разработчиков. А как основа для больших проектов. Например, какая-то лаборатория нуждается в серии вычислительных экспериментов по определению надежности ленточных фундаментов. Экспериментов будет много, каждый довольно дорогой, поэтому от вычислительных программ требуется максимум эффективности. В сочетании с достаточной гибкостью изменения параметров и методов расчетов. Для чего лаборатория разрабатывает или заказывает у кого-нибудь DSL под свою задачу. Чтобы параметры экспериментов описывались на DSL-е, а описания затем транслировались в эффективный код, использующий различные технологии вроде MPI.

В такой интерпретации я ничего не имею против DSL-естроения. Хотя и не очень понимаю, чем здесь internal DSL лучше external DSL. Но это уже другой вопрос.