Программирование и параллелизм: как функциональные языки стали незаменимыми

Функциональные языки становятся популярнее, и все знают хотя бы о некоторых их элементах, которые есть даже в С++ или С#. Почему функциональные языки пригодятся любому программисту, рассуждает Ярослав Каркунов, Senior Software Developer в компании «Интетикс».

 

Элегантные примитивы

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

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

ООП-языки имеют тенденцию к императивности – последовательному выполнению действий. Грубо говоря, чтобы понять действия программы, ты должен интерпретировать её в голове. Функциональный же язык обычно состоит из примитивов, которые прячут всё это «под капот». Его сила в декларативности и выразительности.

Допустим, в С++ ты будешь использовать циклы и думать про себя: этот «for» делает свертку и обработку данных в новый результат, а тот – отображает старую коллекцию в новую.

В функциональных языках это будет обозначаться каким-то глаголом – например, стандартными функциями «map» и «fold». Первое – для  отображения коллекции в некоторую другую коллекцию с помощью некоторого действия, второе – для свертки коллекции с помощью некоторой функции в новый результат/тип.. Сразу будет понятно, что мы сделали, и это будет записано в виде одной строчки, а не большой конструкции с цифрами.

Это выглядит достаточно элегантно.

 

Функциональные конструкции настигают

Думаю, что можно быть хорошим программистом и не знать ни одного функционального языка. Специфика работы будет, скорее всего, другая: ты не сможешь писать распределённые системы, не зная модели акторов (actor model) или Erlang. Но с некоторыми классами задач ты справишься легко.

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

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

Самый модный из заимствуемых в ООП элементов, думаю, лямба-функция. С ней не нужно определять отдельную функцию, чтобы, например, добавить двойку. Ты просто пишешь «x -> x +2». И всем ясно: мы принимаем «х», возвращаем «х+2». Это действует как функция, и она очень читабельна.

Ещё один функциональный элемент появился и в последнем стандарте С++ . Он нужен, чтобы избежать недостатка статически типизированных ООП – громоздкого описания сложных типов, с контекстами, которые надо обозначить везде. Появился автоматический вывод: ты можешь не писать тип, а просто указать директиву «auto». Он сам выведет нужный, и если что-то не так – скажет тебе перекомпилировать, и ты найдёшь ошибку.

Или взять, к примеру, язык С# – мультипарадигменный, но скорее ООП. В нём есть библиотека LINQ, которую часто используют разработчики для обработки коллекций, работы с данными, запросов к базам. Её создатель был вдохновлён языком Haskell и функциональным программированием.

 

Языки меняются с развитием процессоров

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

Но процессоры достигли некоторого потолка в плане частоты выполнения операций, и появилась новая тенденция – параллелизация. Начали появляться процессоры наподобие Core 2 Duo, в которых было 2, 4, 8 и более независимых ядер.

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

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

 

Программист С++ делает прототип на Haskell

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

Есть программист С++ Бартош Милевский (Bartosz Milewski – независимый предприниматель и популяризатор ФП из США/Польши). Он делает прототип на Haskell, прежде чем писать код на С++. И получается программа, которая без проблем выполняется параллельно и имеет минимальный стейт. Если бы программист писал исходник на С++, понадобилось бы гораздо больше времени хотя бы потому, что гораздо больше места занимает код.

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

 

 

Функциональные языки кажутся сложными из-за композиции

Есть мнение, что освоить функциональные языки сложнее, чем объектно-ориентированные.

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

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

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

 

Математику знать полезно, но не обязательно

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

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

Неестественным и поначалу диким кажется и тот факт, что нужно отказаться от мутабельности. Для моделирования эффекта – например, вывода результата на экран – в функциональных языках есть «костыли»: в Haskell – с помощью монады IO – input-output. Нужно понять, зачем это надо и почему стоит делать новую переменную с новым значением, несмотря на перерасход памяти.

В математике разбираться желательно, но не обязательно. Программист, который не разбирается, подумает: «Ну и высшие материи. Это же надо использовать сотни формул! А зачем они мне?»  Но эти формулы не так сложны и доступны каждому.

 

Насколько жизнеспособны ОО-языки?

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

Взять тот же JavaScript – для web очень мало хороших функциональных фреймворков. Есть Ruby и Ruby on Rails – объектно-ориентированные, последний из них используется везде. С другой стороны, есть их функциональные аналоги – Elixir и Phoenix, основанные на Erlang. В них легко работать с многопоточностью и дистрибутивными системами.

Я бы не сказал, что ООП – это плохо, но никогда бы не начал писать дистрибутив на Ruby. Много суеты с параллельностью: потоки ресурсоёмкие, их нужно сильно ограничивать. Для web лучше использовать Phoenix.

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

 

Сотни серверов по всему миру должны работать как целое

Сугубо моё мнение: интерес к функциональным языкам в последние 10 -20 лет повышается. Но вакансий всё равно мало и комьюнити небольшое. Если ты JavaScript-программист – найти работу будет значительно легче.

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

Специалист по дистрибутивным системам – очень хорошо оплачиваемая работа. Можно найти хорошее место: есть много сервисов, которые раскиданы на сотни серверов по всему миру, но должны работать как одно целое. Таких систем сейчас очень много, и их количество будет только расти. А специалистов мало. Когда их недостаточно, а запрос есть – будут и деньги.

 

Закрытый язык не тормозит развитие

Раньше я работал с объектно-ориентированными и мультипарадигменными языками: С#, С++, JavaScript . Но 4 года назад пришел в «Интетикс». Тогда нас в команде было 2 человека, сейчас – 30.

Язык внутри проекта – Flow. Он закрытый, не open source, и используется только у нас. Такие языки называют DSL, или Domain-specific language. Они предназначены для специфических областей, и наш лучше всего подходит для разработки графических интерфейсов.

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

С точки зрения функциональных языков я получил шанс посмотреть на программирование более математично. То, что язык закрытый, не тормозило моё развитие. С некоторыми идеями – например, реактивное программирование – я познакомился именно здесь.

Я себя не ограничиваю только Flow. В другом месте он мне вряд ли пригодится, но всё, что я вынес из работы с ним – да. Как специалист я вырос, хотя рост специалиста больше зависит от того, вкладывается ли он в себя.

 

Вакансии: как пополнить ряды функциональных программистов

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

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

Ну а чтобы окончательно убедить сомневающихся, компания также предлагает тренинги, которые позволяют быстро погрузиться в язык Flow, а процесс «вливания» в сам проект в среднем занимает около месяца.

Для тех, кто хочет попробовать себя в новом стеке технологий, открыта вакансия.

Версия для печатиВерсия для печати

Рубрики: 

  • 1
  • 2
  • 3
  • 4
  • 5
Всего голосов: 6
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!

Комментарии

Аватар пользователя mike

Почитал и думаю: а кто автор -- Анна Волынец или Ярослав Каркунов?

laugh

"Мутабельность"... smiley

Аватар пользователя savely

> Corel 2 Duo

wink

Аватар пользователя mike

"Очень маленький стейт"

Нечто из сексологии? А по-русски?

wink

Аватар пользователя mental

Нет смысла тратить время на такие языки. Проект заканчивается - вас выкинут на мороз, и кому вы нужны со своим Flow? 

Аватар пользователя mike

Нет смысла тратить время...

Есть. Хотя бы для общего развития. А для распределённых веб-сервисов и для многопроцессорных (многоядерных) систем функционалка может стать мейнстримом. Если уже не стала. :) К тому же и в ООП элементы функционалки упрощают написание кода.

+1

mental пишет:

Нет смысла тратить время на такие языки. Проект заканчивается - вас выкинут на мороз, и кому вы нужны со своим Flow? 

А почему потом не может начаться другой проект? Ограниченность в мышлении видна невооруженным взглядом.