company_banner

Функциональное программирование: в Java и C# слишком много церемоний

    Многие, наверняка, слышали о функциональном программировании, некоторые пробовали написать свой Hello World, а кто-то даже завел свой «функциональный» pet-проект. Но многие ли пользовались функциональными языками в продакшене? Какие у ФП преимущества и недостатки? Оправдывает ли парадигма функционального программирования ожидания разработчиков? На эти и многие другие вопросы нам смог ответить человек, открывший для себя преимущества функционального подхода после 20 лет ООП-разработки.



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

    В ООП мы себя заранее заставляем работать в узких рамках


    — Вагиф, Вы пришли в функциональное программирование после процедурного и объектно-ориентированного? Или все-таки первым было именно функциональное? Что стало для вас отправной точкой?

    Вагиф Абилов:
    Когда я начал смотреть в сторону функционального программирования, у меня за плечами уже было около 20 лет опыта объектно-ориентированного программирования. В 90-е годы это был C++, а потом, с появлением .NET, я программировал на C#. Я типичный backend-разработчик. Работал с сервисами и, в основном, с проектами, где была важна масштабируемость и быстродействие.

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

    Неким сопутствующим фактором является то, что было сформулировано как «закон Амдала», который устанавливает зависимость быстродействия параллельной системы от доступности ресурсов для параллельной обработки. На конкретном примере он звучит так: «если у вас появляется 10 процессоров, но вы распараллеливаете лишь 40%, то быстродействие увеличивается в 1,56 раза». Таким образом, для распараллеливания систем, в которых большой фокус идет на ограничения доступа к данным со стороны различных потоков, есть не так много возможностей. Это меня в какой-то момент перестало устраивать, и я стал больше посматривать на возможность решать это такими средствами, которые позволили бы избавиться от shared state. Преимуществом многих функциональных языков как раз и является то, что у них по умолчанию все данные не мутируют, их нельзя изменять. Это и была первая причина, по которой я стал смотреть в сторону функционального программирования.

    Где-то лет шесть назад я получил приглашение выступить на достаточно большой международной конференции NDC. К тому времени я уже начал работать в хобби-проектах с ФП и представлял там свой опыт F#. Это был романтический период, когда на доклады о функциональном программировании приходили в основном разработчики C#, с удовольствием слушали, а потом спрашивали: «ну где это в реальных системах применяется? Возможно ли это вообще применить?». Нередко докладчики сами говорили, что в реальных системах не используют ФП, но собираются. Я был примерно в таком же состоянии, то есть работал во всех проектах на C#, но ради удовольствия знакомился с F#.

    Мой доклад назывался так: «Playing functional Conway's game of life», то есть реализация игры Конвея «Жизнь» методом функционального программирования. Достаточно известная игра. Я показал, как написать её на F#, сделал и, начав разбираться, сам удивился. Надо сказать, перед этим я нашел проект реализации этой игры на C# на CodeProject. Этот проект состоял из пяти классов, пяти свойств и методов, там было больше 300 строк кода, при этом эффективного. Даже если убрать все скобки, оставалось около 100 строк. Когда я написал «Жизнь» на F#, у меня получилось исполняемого кода 14 строк: семь функций в среднем по две строки (если вы думаете, что это предел — посмотрите код английского программиста Фила Трелфолда, который уместил решение игры Конвея на F# в 140 символов и выложил в твиттер). Компактность разработки на F# меня потрясла. Это первое, что меня впечатлило. Затем я начал рассматривать свой код. Я стал думать, а где вообще в этом коде говорится о том, что решение сделано для двумерной доски? Я обнаружил, что лишь одна функция, которая вычисляет соседей клетки, говорит о том, что это 2D доска. Если эту функцию заменить на работу с трехмерной или даже многомерной доской, то все будет также работать.

    Во многих функциональных языках, в частности в F#, существует так называемый вывод типов: когда вы не задаете напрямую тип ваших данных, а компилятор, в зависимости о того как вы их используете, сам определяет что подставить. Благодаря этому вы пишете сразу обобщенный код. Если в Java или C# нужно специально идти к тому, чтобы обобщать ваши классы, то в F# это получается по-умолчанию. Это дает очень большие преимущества, в чем я смог убедится лично, работая над различными проектами.

    Когда я в итоге приготовил свой доклад и выступал с ним на конференциях, то обращался к залу и спрашивал, с чего следует начать написание игры Конвея. Практически все предлагали с определения классов и свойств, например: надо ввести класс «Доска», «Клетка», определить свойства «Клетки». То есть все привыкли к тому, что нужно начать с определения типов и их взаимосвязей. Мы так приучены. Но на самом деле введение заранее такого большого количества определений накладывает существенное ограничение на дальнейшую работу с ними. Третий важный аспект в реализации игры «Жизнь» на F# было то, что там я не ввел ни одного типа. Все делалось путем задания функций. Это дает полную свободу того, как «играться» с исходными данными. Я осознал, что при ООП мы заранее «заставляем» себя работать в рамках тех определений, которые ввели.

    Подходящий для описаний этой ситуации твит я нашел, когда работал с презентацией своего доклада. Какой-то англоязычный программист написал: «программировать на Java — все равно что заниматься русской литературой: вам нужно определить сотню имен, прежде чем начнут происходить какие-то события». Этот комментарий неплохо определяет подход ООП. Мы должны изначально хорошо все описать, и только потом начнут происходить какие-то события, а мы сможем на наши определения «нанизывать» какие-то методы. И, зачастую, наш дизайн уже нас ограничивает.

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

    В Java и C# слишком много «церемоний»


    — На ваш взгляд, следует ли человеку, который долгое время занимается объектно-ориентированным программированием, знакомиться/переходить на функциональное?

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

    Если мы посмотрим на объектно-ориентированные языки типа Java или C#, то они претерпели достаточно большие изменения в последние годы. Если я ничего не путаю, в C# версии 3.0, когда появился LINQ, появились лямбда-выражения, это был уже заметный ход в сторону внедрения элементов функционального программирования.

    Возникает такой аргумент: «а зачем мне изучать сами функциональные языки, если мы многое можем сделать и в C# с элементами функциональных языков?». По крайней мере один из ответов на это уходит в область изменяемости структур данных, поскольку и C#, и Java всегда останутся языками с мутациями. Когда данные, которые вы определяете по-умолчанию, доступны для изменении, то, какие бы элементы функционального программирования вы не вносили, принципиальной сущности этих языков это не изменит. В последних версиях C# вы можете «играться» с элементами ФП, но, конечно же, имеет смысл попробовать поработать с настоящим функциональным языком, таким как Erlang, Haskel или F#. Последний я бы особенно рекомендовал, поскольку это язык очень хорошо встраивается в .NET. Достаточно разобрать какие-то примеры, посмотреть насколько лаконичным получается код. Это, на мой взгляд, серьезный аргумент — компактность кода. Чем более опытен программист, тем больше он должен осознавать, что в таких языках как Java и C# слишком много «церемоний». Избежать их можно если уменьшить код вдвое, потому что обычно программы на F# вдвое компактнее, чем на C#.

    — Какие преимущества дает ФП по сравнению с ООП?

    Вагиф Абилов:
    Первое, как я уже сказал, это отсутствие мутации данных — это очень важно. В программах, которые пишутся на функциональном языке, нет переменных. В каком-то смысле они сразу получаются «жесткими».

    Если вы посмотрите на объектно-ориентированный код, там будут какие-то переменные, какие-то данные, потом их куда-то посылают, и ко всему этому осуществляется доступ из многих потоков. В функциональных языках, поначалу, это немного сбивает с толку: как вообще можно работать, не вводя переменные? Но все реализуется с помощью методов «функциональных трансформаций». Это как раз и создает основы параллелизма. Когда вы получаете какие-то данные, вам не нужно отвечать на вопрос: «это thread-safe или не thread-safe? Переживет ли это доступ из многих потоков или нет?». Вы по определению знаете, что переживет. Вам даже не надо делать никаких тестов для проверки с доступом из многих потоков.

    В связи с отсутствием переменных и с тем, что вы все пропускаете через функциональные трансформации, очень упрощаются тесты. Как следствие, логические ошибки гораздо чаще отлавливаются компилятором. Одна из «хороших» проблем при работе с F# такая: я могу несколько часов потратить просто на то, чтобы программа скомпилировалась, но когда это произойдет, она будет работать без ошибок. Это настолько «убаюкивает», что начинаешь меньше писать тесты. Вначале я пытался с этим бороться и писал также много тестов, как на C#. Потом я понял, что в этом нет необходимости, так как большинство логических ошибок отлавливается компилятором, который гораздо менее «прощающий», чем компиляторы объектно-ориентированных языков.

    Пожалуй, это и есть основные преимущества: параллелизм, отсутствие мутаций, большее взаимодействие с компилятором, который восприимчив к логическим ошибкам. Кроме этого меняется стиль работы. Если я работаю на C#, то часто использую классическую TDD методологию. С F# я работаю в режиме REPL (read-eval-print loop). Такая работа очень эффективна.

    — Есть ли что-нибудь, что не под силу ФП в сравнении с ООП? Какие у него (ФП) недостатки?

    Вагиф Абилов:
    Для каждой задачи должны применяться свои средства. Что касается преимущества функциональных языков при разработке масштабируемых систем с большим быстродействием это понятно, общеизвестно. Но для меня не очевидны преимущества функционального программирования при работе с визуальными интерфейсами. Если у вас программа однопоточная и сводится к редактированию форм, то, в общем-то, здесь будет естественно применять объектно-ориентированный подход, так как формы легче ложатся на модели данных. F#, Clojure, Erlang используют и для разработки user-interface, но преимущества мне кажутся неочевидными.

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

    — Если обобщить, на решение каких задач ориентировано ФП?

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

    — Вагиф, какую инфраструктуру (tooling) необходимо собрать для реализации проекта на языке ФП?

    Вагиф Абилов:
    Поскольку я работаю с C# и F#, то для меня Visual Studio — это наиболее часто используемый инструмент. Но, все чаще я замечаю, что пользуюсь другими, менее «тяжелыми», средствами. Например, что касается языка F#, если речь идет о .NET-разработке, то есть Visual Studio Code с плагином Ionide. Это потрясающая среда для работы с F#.

    Я бы порекомендовал использовать такие редакторы, как Atom + Ionide, VS Code + Ionide, Paket, Fake. Для тестов есть F#-дружественные фреймворки: FsUnit и библиотека Expecto, которая очень хорошо встраивается в работу с функциональными языками. И вот буквально на днях появилась информация, что новый IDE JetBrains Rider, который пока находится в бете, будет поддерживать F#. Это примечательное событие, поскольку JetBrains — вообще практичные ребята, и они долго отнекивались, когда их спрашивали о поддержке F#, мотивируя это сложностью встраивания принципов языка в платформу Resharper (как я понимаю, сложности относятся к внедрению типов, чего нет в объектно-ориентированных языках). Но лед тронулся, F# стал слишком важным в среде .NET языком, чтобы его можно было и дальше игнорировать.

    Если вам необходимо написать веб-приложение, то есть замечательный фреймворк Suave. Он позволяет очень компактно, буквально в несколько строк, написать веб-аппликацию или веб-сервис. Если же говорить о реализации микросервисов, то очень хорошо функциональные языки работают вместе с моделью актеров (Actor-model). Я последние полтора года занимаюсь разработкой системы на F# с использованием Akka.Net, в которой эта модель реализована.

    Кроме всего прочего, важными составляющими будут провайдеры типов (type providers), которые на F# реализованы и позволяют очень эффективно работать с базами данных. Они заменяют такие тяжеловесные библиотеки, как Entity Framework.

    Кстати, интересный пример. Есть на F# open-source библиотека SQLProvider, которая необычна тем, что включает в один модуль сразу семь драйверов: MSSQL, Oracle, SQLite, PostgreSQL, MySQL, MsAccess, ODBC. И все это весит лишь 1,3 мегабайта. И драйвер каждой из баз данных составляет примерно от 600 до 800 строк кода. Это, к слову, о том, насколько компактно можно писать многие вещи на F#.

    — Если ли на вашем личном счету большие и серьезные проекты, реализованные с помощью функционального программирования?

    Вагиф Абилов:
    Да. Небольшой группой последние полтора года на F# с помощью Akka.Net мы пишем систему, которая имеет высокие требования к быстродействию и масштабированию. Эта система разрабатывается для норвежского государственного радио-телевидения. Она оперирует многими сотнями терабайт файлов, работая с облаком. Код получается очень компактный, несмотря на сложность системы.

    — Как вы считаете, станет ли ФП популярным настолько, чтобы конкурировать с ООП?

    Вагиф Абилов:
    Что касается конкуренции, то уже сейчас функциональное программирование успешно конкурирует с объектно-ориентированным и во многих проектах заменяет его. Если же говорить о количественном сравнении, то надо понять, о какой временной перспективе мы говорим. Наверно, в ближайшие пять лет ФП не выйдет на сравнимое с ООП количество проектов по разным причинам. Одна из них заключается в том, что начинать обучение программированию гораздо легче с объектно-ориентированных языков. Плюс, имеется большое количество задач с пользовательским интерфейсом, где, как я уже сказал, преимущества ФП не очевидны.

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

    — Что вы можете посоветовать тем, кто решил начать изучать функциональное программирование?

    Вагиф Абилов:
    Посоветовал бы не относиться к этому выбору как к какому-то серьезному жизненному шагу. Я заметил, что пробовать изменить основной язык у программистов считается каким-то радикальным шагом в отличие, например, от смены базы данных или какого-то фреймворка. Например, разработчики на javascript меняют библиотеки, которыми они пользуются, как перчатки. И это не выглядит каким-то серьезным изменением. Если вы перешли с реляционной базы данных на document-базу, это во многом более серьезный шаг, чем перейти с одного .NET языка на другой.

    Мне довелось однажды поговорить с ребятами, которые писали систему для одного из заказчиков на F#. Я спросил, насколько было легко убедить заказчика, что вы будете делать проект на F#. Они сказали, что заказчику об этом не говорили. В контракте было написано, что система должна работать под .NET. В этом подходе, на самом деле, что-то есть. Если вы пишете для данной операционной среды, то мой совет: как можно активнее и больше пробовать. Пробовать другие языки, библиотеки и модели программирования. От всего этого будет только польза.

    — О чем будет ваш доклад на питерской конференции DotNext в мае?

    Вагиф Абилов:
    Нынешний мой доклад не будет иметь непосредственного отношения к функциональному программированию, но он будет, в каком-то смысле, связан со сменой парадигм. Я буду рассказывать о том, как разработчику сделать API таким образом, чтобы он был в равной степени легко используемым как теми, кто предпочитает типизированное программирование, так и теми, кто использует программирование с динамическими типами. Как известно, с появлением .NET 4.0, появилась возможность встраивать динамические типы в C# (тип dynamic). В каком-то смысле я буду говорить о том, что нужно быть готовым к смене парадигмы. И это роднит мой доклад с темой нашего сегодняшнего разговора.

    Полностью тема доклада Вагифа Абилова, который будет выступать на нашей конференции DotNext в Питере 20 мая, звучит как Typed or dynamic API? Both! Мы будем рады видеть вас на этом мероприятии, где нам удалось собрать около 30 замечательных докладчиков из самых разных уголков мира и по самым актуальным темам.
    JUG.ru Group
    616.45
    Конференции для программистов и сочувствующих. 18+
    Share post

    Comments 83

      +8
      Во многих функциональных языках, в частности в F#, существует так называемое внедрение типов: когда вы не задаете напрямую тип ваших данных, а компилятор, в зависимости о того как вы их используете, сам определяет что подставить.

      Если не ошибаюсь, это обычно называют выводом типов (type inference).
        +2
        Спасибо за поправку. Я не знал. Долго думал, как же это перевести, остановился на «внедрении».
          0
          Оу. Я почему то думал это оригинальный текст на русском и замечание оставил что бы уточнить сопоставимость терминов. Мало ли, вдруг что то новое.
            0
            Так это и есть оригинальный текст, мы беседовали по-русски.
              +2

              Так зачем тогда переводить? )

        +3

        Похоже пора расчехлить свой pet project на F#. Спасибо за интервью, вдохновляет шагнуть в функциональное программирование.

          –2
          Я правильно понял, что на данный момент из реально завершенных «фунциональных» проектов у автора только «Жизнь» размером в 14 строк?
            –1
            Вы до конца дочитали?

            Небольшой группой последние полтора года на F# с помощью Akka.Net мы пишем систему, которая имеет высокие требования к быстродействию и масштабированию. Эта система разрабатывается для норвежского государственного радио-телевидения. Она оперирует многими сотнями терабайт файлов, работая с облаком. Код получается очень компактный, несмотря на сложность системы.
              +7
              из реально завершенных «фунциональных» проектов

              последние полтора года на F# с помощью Akka.Net мы пишем систему

              Пишут, а значит пока не написали, а значит, согласно статье, у автора нет законченных проектов на функциональных языках, кроме игры «Жизнь».

                +19
                Пишут, а значит пока не написали

                Выходит, у Торвальдса, например, тоже нет законченных крупных проектов на Си.

                  +1

                  Это значит написали, работет и продолжают развивать. Вот ссылка на доклад Вагифа "Опыт внедрения актёров на F#"
                  https://www.youtube.com/watch?v=wRxO5ky7S8g

                    +1
                    Наверное это скорее акторы, а не актёры.
                  –5
                  Конечно дочитал. Сам факт того, что они полтора года что-то пишут, ни о чем не говорит. Без демонстрации доведенных до ума продуктов все звучит жутко неубедительно.
                +7
                Спасибо за интересный вопрос :-) Нет, вы неправильно поняли. На проект на F# для норвежского телевидения уже скоро год как в продакшн, сейчас готовится вторая фаза. Считать ли такой проект завершенным — это уже вопрос точки зрения. Но это 100% функциональный проект, который заставил многое пересмотреть. Основная причина, по которой в этой беседе я ссылался на довольно простую программу — при всей ее простоте на ней очень многое вскрывается, по крайней мере вскрылось для меня. К тому же она всем знакома. Ну и к тому же когда принимаешь для себя решение осваивать новую парадигму, то по определению нет завершенных проектов, надо на основании ограниченного опыта решать, в какую сторону двигаться.
                  0

                  Нет, вы поняли неправильно.

                  +2
                  Вначале я пытался с этим бороться и писал также много тестов, как на C#. Потом я понял, что в этом нет необходимости, так как большинство логических ошибок отлавливается компилятором, который гораздо менее «прощающий», чем компиляторы объектно-ориентированных языков.

                  с чего это? компилятор знает как бизнес логика работает?
                    +4
                    Если использовать domain driven design то будет знать
                    http://fsharpforfunandprofit.com/ddd/
                      +2
                      Многое ловится, если использовать discriminated union, особенно если включить опцию warnings as errors. Это вообще очень полезная категория типов для описания предметной области. Очень помогает и что тип record требует задания значений всех полей при декларации данных. Нет нулей, на NullReferenceException наталкиваешься лишь когда принимаешь данные из C#. И еще что существенно сказывается — нет никаких переменных, стараешься писать все в виде функциональных трансформаций, получается меньше, так сказать, движущихся частей.

                      При этом я мгного лет был в каком-то смысле TDD-junkie, старался писать тест на все что угодно.
                      +6
                      Мне казалось, что я уже много подобных статей прочитал. Но впервые, я увидел для себя реальные причины начать изучать F#. То ли в этой статье написали что-то, чего не было в других, то ли в других было не так понятно.
                        +3
                        Если вы посмотрите на объектно-ориентированный код, там будут какие-то переменные, какие-то данные, потом их куда-то посылают, и ко всему этому осуществляется доступ из многих потоков.

                        По моему это не про ООП, а о том, как многие его неправильно используют. :)
                          +1
                          Да, это не относится ни к ООП, ни к его использованию, это про императивный подход к программированию.
                          +1
                          Я для себя взял за правило не слушать тех кто пытается тебя в чем-то убедить. Нормальный доктор никогда не скажет кушать только фрукты потому что они витамины. ООП там где оно нужно, это сила. ФП на своем месте — наверное тоже. Игры делают не на ооп и не на фп, а на нечто среднем. Так же личный опыт подсказывает что разные программы требуют разные подходы. Когда я пишу ui я радуюсь mvvm, когда сервер mvp, когда простую игру, я наслаждаюсь mvc, когда большую игру ecs, когда я пытаюсь связать части архитектуры, я просто тащусь от реактивных библиотек. То место где мне нужна иммутабельность, я использую иммутабельност.ь. Но так же я знаю что везде иммутабельность просто не нужна, а если пытаться использовать архитектуру не там где положено-парадигму, а лишь из-за того что это модно, то это начала серии статей о том что все это ужас.
                          Но самое непонятное мне, это то, что лично я бы не стал писать 20 лет на том что мне не нравится.
                            0
                            «Но самое непонятное мне, это то, что лично я бы не стал писать 20 лет на том что мне не нравится.»

                            Если из моих слов сложилось такое впечатление, значит я совсем туманно выражаюсь.
                              0
                              Если из моих слов сложилось такое впечатление, значит я совсем туманно выражаюсь.

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


                              Это разве не означает что 20лет Вы писали на убогом-неудобном и неправильном подходе?

                              И ещё вопрос — функциональные библиотеки которые Вы используете, так же не имеют переменных и не хранят состояние свойствах?
                                0
                                Нет, не означает, конечно. Я и сейчас немало пишу на C#, переходил на него с Си++ с удовольствием. Что же до чужих библиотек, то речь идет непосредственно о коде, который пишем мы сами, о его прочности, о пригодности к параллелизации. Сопровождать же нужно будет свой домашний код, а не другие библиотеки.
                            +1

                            Если вы переживаете по поводу производительности и иммутабельности — в f# достаточно средств оптимизации и можно втыкать mutable и кусочки ООП туда, где вам нужно.


                            Когда я попробовал f#, посмотрев лекцию VagifAbilov об экторах, я понял, что ФП — это именно то, как я стремился писать программы. И если в ООП языках я иногда чувствовал сопротивление инструмента, то в случае с f# все "как ножом по маслу".


                            ФП сейчас конечно форсится жутко, но это не делает парадигму плохой.

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

                              Адепты концепций высокого уровня любят высокомерно бросать «ну а если вам нужна производительность — можно подключить си библиотеку». Можно подумать пользователям не нужна производительность. Или что отдельная библиотека — это не часть программы. Существенная часть программы, написанная в другой парадигме. И это даже не наводит их ни на какие мысли. Слишком много развелось узкоспециализированных программистов, не только не способных построить с нуля всю программную систему, но даже не представляющих как она устроена. Отсюда и утверждения, что только их болото достойно внимания.

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

                              Вот как магически можно решить проблему общего состояния в игре? Каждый актор должен знать состояния всех соседей и поля боя в целом. Можно использовать локи, а можно копить кучу копий и обеспечивать синхронизацию этих копий. И никак нельзя облегчить задачу — объединить несколько мутаторов в общий логическую систему и разбить обновление этой системы на фазы, внутри некоторых из которых можно ослабить гарантии пареллельного доступа. Что-то наподобие идемпотентного оператора, только более сложное и в масштабах системы, а не отдельного кусочка данных.
                                +2
                                За гарантии работы программа платит скоростью работы.

                                Почему вы считаете, что это истинно всегда?


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

                                Кто мешает это сделать достаточно умному компилятору?

                                  0
                                  > Почему вы считаете, что это истинно всегда?

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

                                  > Кто мешает это сделать достаточно умному компилятору?

                                  Таких компилятов ещё не написали. И напишут не скоро.
                              0
                              Как там обстоит дело со скоростью например при работе с охрененно гигантскими матрицами? Ну там крутить-вертеть их туда-сюда, перемножать и т.д. и паралельно крутить мозгокрутные конечные автоматы, ну и так далее?
                                0
                                На счет F# не знаю, но в Haskell вроде как есть возможность подключать высокопроизводительный код на c++. Аналогичную черную магию практикует питон. Из тех бенчмарков что я видел производительность хуже на 0-10%.
                                Так же есть так называемые «Небезопасные» функции для работы напрямую с памятью, думаю будет примерно то же самое.

                                P.s. Сам не имею опыта в таких вещах.
                                  0

                                  Да, в хаскеле каноничная hmatrix дергает всякие gsl, blas, lapack и тому подобные вещи.

                                  0
                                  Сразу оговорюсь, что опытом кручения гигантских матриц не обладаю, но судя по разным бенчмаркам, при особых требованиях к быстродействию F# уступит не только Си++, но и некоторые его модули (List, Map) проиграют по скорости структурам данных из .NET/C#. Если не ошибаюсь, Ayende (Oren Eni) делал сравнения, писал в своем блоге.
                                  В более традиционных системах, однако, закон Амдала, указывающий на потери быстродействия залоченных данных, имхо, делает код с замками в многопоточной среде более уязвимым в плане быстродействия, чем функциональные алгоритмы без блокировки.
                                0

                                Так же не понятно, как реализовать кеширование без разделяемого состояния.
                                Отдельный кэш для каждого потока?
                                При обновлении кэша пересоздавать его заново?

                                  –1
                                  Думаю, обобщенный ответ на этот вопрос лежит вне парадигмы программирования. Хотя в рамках некоторых моделей есть свои решения. Например, мы пользуемся моделей актеров — она доступна и из ООП, и из ФП, но поскольку актеры (или акторы, тоже не уверен, как лучше по-русски выразиться) не делят состояния, то по духу ФП ближе, не даром Akka пришла из Скалы. Так вот, в рамках этой модели у нас некоторые актеры хранят разделяемое состояние, доступ к котрому осуществляется путем обмена с ними сообщениями. Это может выглядеть тяжеловесно, позволяет полностью обходиться без замков.
                                  +3
                                  — Что вы можете посоветовать тем, кто решил начать изучать функциональное программирование?

                                  Вагиф Абилов: Посоветовал бы не относиться к этому выбору как к какому-то серьезному жизненному шагу. Я заметил, что пробовать изменить основной язык у программистов считается каким-то радикальным шагом в отличие, например, от смены базы данных или какого-то фреймворка. Например, разработчики на javascript меняют библиотеки, которыми они пользуются, как перчатки. И это не выглядит каким-то серьезным изменением. Если вы перешли с реляционной базы данных на document-базу, это во многом более серьезный шаг, чем перейти с одного .NET языка на другой.


                                  Отлично сказано!
                                    +3

                                    Вообще говоря, если ориентироваться на доминирующую архитектуру процессоров, то она императивна и мутабельна :)


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

                                      0
                                      В прошлом году Джо Армстронг (Erlang) делал доклад на NDC о принципах, заложенных в язык, и он как раз упирал на то, что в основе успеха Эрланга у телеоператоров и прочих систем, требующих масштабируемости и надежности — соответствие Эрланга (функционального языка) железу, на котором будут запускаться программы на нем. «We do not have ONE web-server handling 2 millions sessions. We have 2 million webservers handling one session each.» — сказал он же в другом месте. То есть несмотря на императивность и мутабельность архитектуры процессоров процессы на них имеет смысл запускать функциональные и неизменяемые.
                                      • UFO just landed and posted this here
                                        0

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

                                          +1

                                          Так или иначе эти чистые функции изменяют состояние системы — ОЗУ и дисков, даже если про регистры не вспоминать. И делают это в императивной мутабельной манере, просто транслятор и(или) рантайм языка это скрывает.

                                            +1
                                            Не стоит все доводить до крайнестей. Очеидно тут говорится об одном уровне абстракции, а вы опускаетесь на другой.
                                        +1
                                        Третий важный аспект в реализации игры «Жизнь» на F# было то, что там я не ввел ни одного типа.

                                        Это какое-то неправильное функциональное программирование. "Правильное" современное ФП обычно начинается с типов и управляется типами.

                                          0
                                          Это верное замечение, мне следовало лучше обговорить, что я имею в виду под важностью аспекта. ФП действительно управляется типами, но благодаря type inference их часто можно не вводить для промежуточных вычислений. Partial application тоже помогает на них не сосредоточиваться. Но ФП именно что начинается с типов — дальше они пускаются в плавание по трансформациями. ООП и начинается, и продолжается, и заканчивается типами, собственно парадигма такая, от нее не убежать. И это приводит к тому, что в реальной практике мы часто слишком многое определяем заранее, связывая свой дизайн.
                                          0
                                          Кстати, в telegram есть чат по F#

                                            +1
                                            > Первое, как я уже сказал, это отсутствие мутации данных — это очень важно.

                                            Конечно важно. От этого асимптотика страдает. Частенько и коэффициенты при О-большом тоже.
                                              0
                                              Если честно, то немного не понимаю как функциональное программирование связано с быстродействием? (Пока вижу один плюс — простое распараллеливание участков кода — за счет отсутствия сайд эффектов) Насчет дизайна для меня очень спорно, наш мир не идеален, и работать в контексте персистентных структур очень сложно. Простой пример — работа с графами, мне сложно себе представить, как можно эффективно с точки зрения памяти и времени работы алгоритма работать с такими объектами (если учитывать, что это персистентная структура). Возможно императивный подход уже наложил сильный отпечаток, и это все предубеждения.
                                                0
                                                Как мне кажется, связь скорее всего связь отрицательная. Иммутабельность существующих объектов = постоянное создание новых, что врядли может положительно сказаться на производительности.
                                                  0

                                                  Если вы думаете, что нужно обязательно создать полную копию структуры (графа), чтобы внести небольшое изменение — вы ошибаетесь. Вот как-то так в двух словах. Совсем не хочу при этом сказать, что эти алгоритмы простые — скорее наоборот, они сложные (классическая книжка Окасаки — это просто идеальный способ сломать мозги). Но с точки зрения памяти и времени не все так мрачно.

                                                    0
                                                    > Если честно, то немного не понимаю как функциональное программирование связано с быстродействием?

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

                                                    Реанимация ФП произошла когда мы уперлись в пределы вертикальной масштабируемости и начали активно осваивать горизонтальную.

                                                    И оказалось, что проще дать +10% нод на оверхед парадигмы и получить scalability factor ~=1 из коробки, чем без оверхеда иметь scalability factor 0.4 и дикий гемморой и баги на реализацию совместного доступа в других парадигмах.

                                                    фп, как и nosql оооочень старые подходы
                                                    0
                                                    … а у меня на ассемблере около 70 строк занимает. Как можно на С# напилить больше я не понимаю, может там фреймворк какой? По ООП языкам в принципе Оккам плачет, может из-за этого.
                                                      +3
                                                      «обычно программы на F# вдвое компактнее, чем на C#»

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

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

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

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

                                                      А код получается компактным — это обычно псевдосимвольная графика в коде, которая не читается.
                                                        +1
                                                        Ну вот возьмите async/await. Вроде бы это всего лишь синтактический сахар. Но реально уменьшает количество кода, а раз так, то и шанс сделать логическую ошибку. Я себя причисляю к той части программистов, которая чем больше пишет, тем больше ошибается, для таких, как я, компактность кода — это благо.
                                                          +1
                                                          Если же брать примеры непосредственно из ФП, то какой-нибудь List.fold поначалу может и требует задуматься, что же там внутри происходит. Но пройдя через это и поняв, выходишь на иной уровень абстракции, выигрывая на нем в компактности.
                                                            0
                                                            Чем короче код тем проще его понять (при условии что это не просто переменные названы короче а сам алгоритм проще). В случае F# он проще за счет того что для того чтобы сделать одно и то же нужно как раз таки меньше логики описывать.

                                                            Сравните хелло ворлд хотя бы. На F# это одна строчка кода, на C# тебе и классы и полиморфизм, и аргументы из командной строки, и статик и синглтон — все в каше.
                                                              +1
                                                              > (при условии что это не просто переменные названы короче а сам алгоритм проще).

                                                              > Сравните хелло ворлд хотя бы. На F# это одна строчка кода, на C# тебе и классы и полиморфизм, и аргументы из командной строки, и статик и синглтон — все в каше.

                                                              Так вы определитесь, алгоритм разный или строчки? Алгоритм-то hello world один и тот же во всех случаях.
                                                                0

                                                                Формально подходя, вроде к ФП понятие алгоритм в целом не применимо, алгоритм — описание шагов в определенном порядке, а ФП не задает порядок вычислений в функциях.

                                                                  0

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

                                                                    0

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

                                                                      0
                                                                      Функциональное программирование полно по Тьюрингу, а значит, умеет представлять алгоритмы.

                                                                      Функции в ФП мощнее тех, которыми пользуются в математике. Тут главное — функция выбора
                                                                      IfThen( condition, trueValue, falseValue )

                                                                      В математике, при комбинировании функций, значение функции не определено, пока не определены все аргументы. У функции IfThen в ФП фундаментальное отличие — если condition==true, то значение falseValue может быть не определено (а может и быть определено). На этом строится и рекурсия (условие выхода из рекурсии), отсюда и проблемы с вычислимостью (аналог проблемы остановки), это и даёт возможность записывать любые алгоритмы.
                                                                      • UFO just landed and posted this here
                                                                          0
                                                                          Речь о том, что в ФП есть специальные функции, не вычисляющие все аргументы.
                                                                          Работающие, как тернарный оператор (condition? value1 : value2) в C/C#.

                                                                          Взять классический не ленивый LISP, с его функцией (cond) — там независимо от ленивости вычислена будет только одна ветка.
                                                                          • UFO just landed and posted this here
                                                                +1
                                                                Помните BASIC?
                                                                10 PRINT "Hello, World"

                                                                Тут helloworld так короткий, потому что BASIC — язык для начинающих (согласно расшифровке его аббревиатуры), т.е. язык для helloworld-ов. Дизайн языка говорит новичкам: смотрите, как просто — 1 строка и программа работает!

                                                                С другой стороны, если посмотреть на алгоритмы архивации, то доказано, что нет универсального — такого, который бы обошёл все алгоритмы на любых данных. Всегда найдётся лучше архиватор под специфические данные. С языками то же самое.
                                                                • UFO just landed and posted this here
                                                                0
                                                                А мне показалось убедительно, конечно не так, что бы все бросить и бежать, но серьезно изучить вопрос и начать осваивать — почему бы и нет? Спасибо за статью, правда хотелось бы больше примеров и сравнений, но можно и самому поискать
                                                                  +1

                                                                  Вагиф, ФП очень даже востребовано в программировании интерфейсов, форм и прочего. Если вам интересен функциональный подход к написанию клиентских приложений, то очень интересный в этом плане язык — Elm http://elm-lang.org
                                                                  Это, можно сказать, наследник Haskell для написания сложных frontend web-приложений, но при этом очень простой в освоении. Чистые функции, отсутствие null reference exceptions и вообще исключений в runtime для сложных SPA, где владычествует великий и ужасный js, — согласитесь, заманчиво

                                                                    0
                                                                    Да, точно. Спасибо за уточнение.
                                                                    0
                                                                    Вагиф, поясните, если «правильно» писать на ОПП — 1. Вместо мутабл — неизменяемое состояние
                                                                    2. Вместо shared state — инкапсулировать состояние
                                                                    и т.д…

                                                                    То какие преимущества у ФП, кроме краткости, которое часто сомнительно?

                                                                    Раскройте. за счет чего выше производительность при ФП, будет, если расход памяти больше на порядок (ведь состояние неизменяемое)? Как удается быстро освобождать неиспользуемую занятую память?
                                                                      +2
                                                                      Влад, так в том-то и дело, что такой стиль написания на ОПП не считается однозначно правильным. Я согласен, что не надо очень напирать на краткость кода на ФП, это в конце концов не самоцель, хоть и полезно, но церемониальность многих языков ООП не связана с ООП как таковым, и при желании их тоже можно сделать более компактными. Вот здесь — набор слайдов, где предлагается синтакс языка C# Light, при использовании которого код становится сравнимым по компактности с фшарпом:

                                                                      https://www.slideshare.net/ScottWlaschin/c-light

                                                                      Но если брать другие, более важные моменты, то основная проблема языков ООП — это то, что они навсегда останутся языками мутирующих данных. Мы можем требовать от разработчиков команды высокой дисциплины, но нельзя заменить основы языка дисциплиной. Вот пересказ отзыва создателя Clojure Рич Хики о проблемах мутаций в ООП:

                                                                      «It should also be obvious that OO languages have „no proper notion“ of values in this sense. As Rich Hickey points out, you can create a class whose instances are composed of immutable components, but there is no high-level concept of immutable value implemented as a first class construct within the class.

                                                                      This is one of the main causes of headaches when doing OOP. How many times have you pulled your hair out trying to figure out how an object's attribute got changed? The fact is, in OO languages there is no built-in mechanism to ensure that the object you're dealing with is stable.

                                                                      This is the big reason why concurrent programming is so difficult.»

                                                                      Безусловно, современные C# и Java куда более пригодны для функционального стиля программирования, чем пятнадцать лет назад. Но такой стиль всегда будет оставаться для этих языков если не инородным, то по крайней мере не самым идиоматическим.
                                                                      • UFO just landed and posted this here
                                                                          0

                                                                          D сиподобный и в нём есть неизменяемые структуры:


                                                                          immutable struct Person {
                                                                          
                                                                              /// Full name
                                                                              string name;
                                                                          
                                                                              /// Birth day
                                                                              DateTime birthday;
                                                                          
                                                                          }

                                                                          auto person = Person( 'Alice' , DateTime( 2000 ) )
                                                                          • UFO just landed and posted this here
                                                                              0

                                                                              Вообще говоря, автор c#light упростил лишь синтаксис для одного конкретного кейса — иммутабельные структуры, а для этого хватило бы обычных классических структур с модификатором. Тут не нужны классы, гетеры, модификаторы доступа и прочий высший пилотаж.

                                                                          0
                                                                          > Но если брать другие, более важные моменты, то основная проблема языков ООП — это то, что они навсегда останутся языками мутирующих данных

                                                                          Так и компьютер — это механизм с мутирующими данными. И алгоритмы затачиваются именно под мутирующие данные. При переходе на немутирующие ленивые могут даже асимптотику потерять, доступную в императивном стиле.

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

                                                                          > The fact is, in OO languages there is no built-in mechanism to ensure that the object you're dealing with is stable.

                                                                          А как же константы? Очень удобная декларация, с самого начала программистам понравилась. Просто не надо все поля класс делать константными.

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

                                                                          Так он в любом случае будет императивным. Ну вот заменили все функции на структуры данных, запихали их во Free, получили ad-hoc синтаксическое дерево. Только вот оно само ничего не вычисляет. Его надо интерпретировать потом. И внезапно оказывается, что интерпретатор с большим энтузиазмом меняет состояния.
                                                                        –3
                                                                        ребята
                                                                        проблема не в том в какой парадигме суть языка программирования
                                                                        а в том что суть языка есть Синтаксис
                                                                        я до сих пор не могу понять Почему синтаксис все не вобрал в себе на Упрощения кода
                                                                        пусть будет синтаксис совмещен от всех парадигм и от ООП и от Функций и от Прологов и всех остальных язычеств
                                                                        программисту вообще нет дела до того как реализуются уже библиотеки и модули
                                                                        ему есть дело Как решить ту или иную задачу за минимум времени чтобы получить максимум премию)
                                                                        никто не хочет работать
                                                                        так создайте Си шарп с синтаксисом Питона и Наскела))
                                                                        столько не нужных скобок и типов которые в Айдле уже сами выставляются
                                                                        упрощайте синтаксис и не хамите что это Сахарный десерт
                                                                        и начинайте упрощать с ассемблера
                                                                        и почему нельзя в ассемблере применять циклы)) по синтаксису — сами себе вы программисты хамите
                                                                        вот что я думаю
                                                                          +4
                                                                          Я вижу, вы и в русском языке синтаксис упростили — ни одной запятой
                                                                            –5
                                                                            так прибавь по моде твердые знаки)) в конце слов русского языка чтобы тебе вторить)
                                                                            если ты против упрощений то пиши все на ассемблере)) и забудь вообще другие языки
                                                                            ведь можно в том тебя же укорить что Упростил себе жизнь используя любой другой язык а не ассемблер)
                                                                          0
                                                                          Пример с игрой Жизнь на F# не очень показательный. На C# с помощью linq получается примерно также:
                                                                                  static void Main()
                                                                                  {
                                                                                      var rnd = new Random();
                                                                                      var size = 10;
                                                                                      var grid = Enumerable.Range(0, size * size).Select(_ => rnd.Next(2)).ToArray();
                                                                                      Func<int, int, int> match = (v, n) => n == 3 ? 1 : n == 2 ? v : 0;
                                                                                      var neighbours = new[] { -1 - size, -size, 1 - size, -1, 1, -1 + size, size, 1 + size };
                                                                                      while (true)
                                                                                      {
                                                                                          Console.Clear();
                                                                                          foreach (var s in Enumerable.Range(0, grid.Length / size)
                                                                                                              .Select(y => string.Join("", Enumerable.Range(0, size).Select(x => grid[y * size + x]))))
                                                                                              Console.WriteLine(s);
                                                                                          grid = grid.Select((v, i) => match(v, neighbours.Sum(o => grid[Math.Abs(i + o) % grid.Length]))).ToArray();
                                                                                          Thread.Sleep(300);
                                                                                      }
                                                                                  }
                                                                          

                                                                            0
                                                                            Переходя на LINQ, мы уже зачастую переходим на элементы функционального программирования. В вашем примере цепочка из Enumerable.Range -> Select -> Select — это ли не функцинальные трансформации? Обратите еще внимание, что вы решаете задачу для доски конечного размера, в то время как игра Конвея предполагает бесконечную доску (решение на фшарпе, о котором я говорил, не ограничивает размер доски).

                                                                          Only users with full accounts can post comments. Log in, please.