Всем привет!
Недавно в Новосибирске прошла очередная C++ Siberia 2019. На конференции была уютная атмосфера и много хороших докладов. Пользуясь случаем, я побеседовал с двумя нашими докладчиками, которых совсем скоро вы сможете увидеть и в Москве.
Иван Чукич — один из разработчиков KDE, преподаватель и исследователь дизайна языков программирования в Белградском университете.
Александр Гранин (graninas) — известный спикер и разработчик, специализирующийся на ФП, организатор новосибирского ФП-сообщества LambdaNsk.
Сергей: Всем привет, давайте знакомиться. Александр — кейноут-спикер на этой C++ Siberia, а Иван был кейноут-спикером в прошлом году. Давайте поговорим о функциональном программировании. Насколько помню, ФП в C++ было темой твоего предыдущего кейноута, Иван…
Иван: Не целиком, но частично — да.
Сергей: А у Александра тема доклада именно о функциональном программировании. Поэтому я подготовил парочку вопросов, и первый — как вы определяете ФП?
Александр: Я не думаю, что существует какое-то одно «правильное» определение ФП, но если говорить лично обо мне, то ФП — это нечто с функциональной композицией и функциями первого класса.
Иван: Я согласен, но добавил бы ещё функции высшего порядка — те, которые могут принимать другие функции в качестве аргументов и возвращать как результат.
Cергей: Ссылка на функцию в Си — считается?
Иван: Нет, Си не является функциональным языком программирования :-)
Сергей: Расскажи, почему?
Иван: Потому что нельзя из комбинации указателей на функции создать новую функцию, можно только указывать на уже существующие. Конечно, если в ход не пошли какие-то ассемблерные хаки.
Сергей: Отлично, теперь у меня есть официальный ответ! Люди постоянно задают этот вопрос, почему Си — не язык функционального программирования, ведь там есть полноценные функции. А вот почему C++ является функциональным языком, уже понятней…
Александр: Я бы не сказал, что C++ — это прямо-таки функциональный язык программирования, он поддерживает кучу парадигм, влитых в один язык.
Сергей: Имелось в виду — C++ поддерживает функциональную парадигму, конечно. Кстати, почему? Он поддерживает потому, что можно манипулировать функциями высшего порядка?
Иван: Ну, мне кажется, он всегда таким был, ведь даже в C++98 у функции высшего порядка уже имеются, даже в STL. Это не самый удобный функциональный язык — есть языки, которые реализуют ФП и получше. Но для моих нужд он всегда был достаточно функциональным.
Сергей: А вот с этого места поподробней. Что у тебя за нужды такие?
Иван: Это сложно. Давай, расскажу историю. Когда я учился в университете, мы проходили LISP, и все ненавидели этот LISP, потому что он уродливый. Но что я из него понял, это как симулировать конструкции Си прямо в коде на LISP. А потом однажды вышла новая версия Java, которую выпустили ради вещей вроде встроенного цикла foreach
, и я подумал: ого, вам нужен новый компилятор, новая версия языка и всё такое новое только для того, чтобы реализовать нечто, что я делал в университете на LISP, который вообще-то даже не поддерживает циклы. В тот момент я осознал, что ФП — довольно приятная штука для построения высокоуровневых абстракций, и вот поэтому я использую функциональный C++ в 2019 году.
Cергей: По сути, ты используешь ФП для высокоуровневого дизайна.
Иван: Точно.
Александр: Прямо сейчас я не работаю на C++, но не отказался бы использовать его для манипуляций над данными, это куда приятней, чем в императивном подходе. Даже если сравнивать с ООП — в отличие от него тут приемлемы только трансформации, и это удобно.
Сергей: Ну, ты можешь использовать ФП не только с C++, правда? Хорошо, тогда следующий вопрос: какую часть C++ вы используете? Если важны только вопросы дизайна, можно выбрать только ту часть, которая хорошо сочетается с ФП, ту, что вы реально используете.
Иван: Это точно будут лямбда-функции. И даже важней — шаблоны, ведь они позволяют передавать другие функции как аргументы и всё остальное, а лямбды — это только приятный синтаксис для записи функциональных объектов.
Сергей: Да, мы уже поняли, что лямбды тебе очень нравятся :-)
Иван: Не то чтобы они нравились очень, но это явно лучше всего, что у нас было в C++98, с ними удобней работать.
Александр: Да, мне тоже нравятся лямбды — это фича настолько универсальная, что можно было бы писать только на них одних. Это что-то вроде универсального комбинатора, позволяющего конструировать любую логику — может, не так красиво, как в других языках, но не менее полезно.
Сергей: Иван, ты тут отметил, что есть стандарты до C++11, например — C++03, весьма распространённый, и там уже есть функциональные фичи. И функциональных фич стало больше в новых стандартах… Можно ли утверждать, что C++ движется в сторону ФП? Продолжится ли это движение, или прекратится? И к чему тогда приведет?
Иван: Есть хороший доклад Simon Peython Jones о языках программирования в целом, и он там рисовал граф, показывающий множества безопасных языков и используемых языков. Haskell начал свою историю как совершенно безопасный язык, с которым нельзя ничего сделать, — потому что там нет I/O, и вообще ничего такого. SPJ отнес язык Си и языки ассемблера к тем, которые очень полезны, но вместе с тем и чрезвычайно небезопасны. С тех пор Haskell начал двигаться в сторону большей безопасности. С другой стороны, те фичи, которые появляются в C++ — они появляются, в основном, для увеличения безопасности, чтобы можно было писать корректные программы более просто. Так случилось, что большинство этих вещей приходят из языков функционального программирования.
Сергей: Как думаешь, почему так? Из-за самой природы ФП?
Иван: Да, может быть, по самой природе… но я не знаю точно.
Александр: Думаю, что ФП так захватывает всех просто потому, что мы устали от борьбы с шаблонами, с переставлением байтиков, с какой-то низкоуровневой мутью — нам хочется что-то достойное, чтобы приложить собственный интеллект.
Сергей: То есть, для тебя это что-то вроде интеллектуального упражнения?
Александр: Да, типа того.
Сергей: Понимаю. Куда как интересней разработчику думать о высокоуровневых абстракциях, чем реализовывать тупые стандартные фичи — это неприятно. Тогда такой вопроС: есть ли у вас опыт практического применения ФП в C++? Какие-то проекты продакшене?
Иван: Конечно. Один из самых больших в мире проектов, KDE, имеет внутри себя несколько частей, интенсивно использующих функциональный стиль. Конечно, это смесь, как бы так сказать, более традиционного объектно-ориентированного C++ с набором концепций из функциональщины. Я никогда не собирался быть пуристом или чем-то таким. Я всегда стараюсь объединять лучшее из разных миров.
Сергей: А что насчет Haskell или Scala? Они ведь широко используются в продакшене. Как вам такая идея, что Haskell сейчас считается эталоном функционального языка? Это особенно отмечают пуристы.
Иван: Да, я согласен, что Haskell на сегодняшний день — это синоним ФП. По сути, любая фича из Haskell воспринимается людьми как нечто, относящееся к ФП. Это не обязательно правда, но думаю, что Haskell действительно стал самым популярным академическим языком функционального программирования. Знаю, что несколько банков в Лондоне и Северной Европе широко используют Haskell, но всё же Scala в данный момент куда более популярна.
Александр: Согласен, что Scala более популярна, но Haskell выглядит более функциональным языком, большинство его фич реализовано более корректно. То есть, когда у тебя есть каррирование, которое просто делать, когда есть простой способ сделать композицию, программировать становится легко и просто, это как гулять по лесу и наслаждаться видами.
Иван: Но иногда в лесу есть медведи. Если вы в России.
Сергей: Как думаете, C++ в основном черпает вдохновение тоже в Haskell? Стоит это делать?
Иван: Ты намекаешь на каких-то конкретных участников комитета? :-) Кто-то намекал, что концепты появились как результат осмысления тайпклассов, но Бьёрн прекратил эти слухи и даже написал какой-то документ о том, в чём концепты отличаются от тайпклассов. С моей точки зрения, они отличаются всем, но служат одной цели. Просто разные подходы.
Сергей: А future/promise как-нибудь связаны с ФП? Кажется, Бартош утверждал, что это плохо реализованые монады.
Иван: Ну да, они реализованы как монады, передающие продолжения, но я не уверен, что самое важное в данном вопросе.
Сергей: А нужна ли нам улучшенная поддержка монад в C++?
Александр: Безусловно, это наиболее важная фича, которая может превратить C++ в действительно хороший язык.
Иван: Кстати, раз уж мы на конференции, давай я тебе тоже задам вопрос, Сергей. Ты сказал, что конференции — это увлекательная работа. Поделись, чего в ней такого интересного, и посоветовал бы ты мне или товарищу Гранину самостоятельно организовать конференции в других частях мира?
Сергей: Организация конференций — это действительно круто, ты встречаешься с множеством интересных людей, но это только вершина айсберга. А там внизу — куча работы, вся эта подготовка зала, еда для участников, не говоря уж о поиске спикеров. C++ Russia — всё ещё не самая известная в мире конференция, и докладчикам приходится объяснять, что мы — новая конференция, что здесь происходят интересные вещи. Нужно убедить докладчика, особенно известных звездных докладчиков, которым не особо интересно лететь в Россию только чтобы посмотреть новую страну. Организаторская работа — тяжелая, в особенности если ты работаешь еще и на основной работе. Но всё окупается общением с этими замечательными людьми. Тем не мене, я сейчас дошёл до того, что скорее я побываю на чьей-то чужой конференции, чем буду делать свою.
Иван: То есть, ты предлагаешь посещать конференции, а не делать их.
Сергей: Да, если можно избежать организации конференции — стоит воспользоваться шансом. При организации на тебя свалится огромный груз, это как ещё одна 8-часовая работа. Лично я примерно за 3 месяца до конференции начинаю работать дополнительные 8 часов в день. Делать это весело… но надеюсь, моей семье так же весело. Спасибо что спросил!
А теперь, назад к теме. Мы говорили о функциональном программировании, и вы почти убедили меня, в смысле — ваши доклады убедили. Есть подозрение, что функциональных подход в C++ поможет мне с многопоточностью, когда нужно синхронизировать разные вещи. Правда ведь поможет?
Иван: Конечно.
Александр: Несмотря на то, что у меня ограниченный опыт работы с функциональной многопоточностью конкретно в C++, имею сказать, что когда у тебя есть мир чистых функций, о них куда проще рассуждать в многопоточной среде. Если ты пишешь логику, например, конкурентную, можно не задумываться о синхронизации всех этих штук, о мьютексах, о критических секциях, о чем угодно. Всё это просто исчезает, поскольку ты думаешь о коде как об обычном последовательном коде, а вся многопоточность и синхронизации прячутся где-то внутри. Есть куча подходов и к многопоточному программированию, и к функциональному. Я не уверен, что так происходит в совершенно всех подходах, но например, software transactional memory — отличный подход для уменьшения сложности в конкурентных приложениях. К сожалению, это вопрос выбора правильных компромиссов.
Иван: Когда компилятор делает за тебя всю работу, за это приходится платить эффективностью.
Александр: Ну, есть разные проблемы. Для начала, нужно разобраться во всех этих вещах вроде STM, и дальше передать это тайное знание коллегам. А потом найдутся ошибки в конкретной реализации и места, которые могут работать сильно лучше при использовании ручного управления тредами. Зато вы сможете писать быстрей и проще, чем при таком ручном управлении. Выполняться будет медленней, но зато код будет с меньшим количеством ошибок. Кстати, Иван, что ты думаешь о том, насколько этот вопрос хорошо освещен на конференциях?
Иван: Эта тема процветает. За последние годы все большие конференции — CPPConf, C++ Russia, Meeting C++, и т.п. — получили доклады или напрямую о ФП, или об алгебраических структурах данных, или о чём-то таком. Иногда докладчики даже не подозревают, что в своём докладе они рассказывают о какой-то концепции из ФП. В C++ приходят вещи из самых разных мест… Люди обычно не пишут только на одном языке. Представим Ивана Иванова, работающего над проектом, написанном на Erlang и C++. Потом приходит Татьяна Петровна, и она уже работает на Haskell с чистыми функциями и всем таким, они берут свои любимые механизмы и портирует их в C++, и в результате огромное количество людей из разных сообществ привносят в C++ всё новые и новые вещи. Всё это происходит у нас на глазах. Как минимум, это происходит в сообществе разработчиков C++, а вот насчёт C++-компаний я не столь уверен. Тем не менее, множество людей из C++-сообщества сейчас работают над функциональными концепциями.
Александр: Я правильно понял, что множество топовых C++-разработчиков изучают Haskell только для того, чтобы понять, что творится с C++?
Иван: Не уверен, что они учат Haskell именно по этой причине. Думаю, C++-разработчики просто очень эгоистичные. Они так хорошо изучили C++ только потому, что он сложный. И если ты хочешь выучить что-то действительно новое, твой путь лежит явно не в какую-нибудь Java, которая специально создана быть простой. Тебе нужно искать в области необычных и странных языков, самых странных, и Haskell автоматически окажется среди самых популярных ответов. Человек видит его, понимает: о, это что-то более сложное, чем C++, нужно выучить. Когда я изучал Haskell, со мной было то же самое, и у меня есть знакомые, которые прошли точно по такой же цепочке рассуждений.
Александр: Когда Эрик Ниблер был у нас в Сибири и показывал свою библиотеку для ренжей, его часто спрашивали — что же было источником вдохновения. Он отвечал, что это Haskell. Может быть, все фичи подряд из него брать не стоит, но некоторые явно нужны в сообществе.
Иван: Это что-то вроде эволюции. Генетический материал. И Haskell тоже можно улучшить, взяв что-то из C++. Большинство языков сейчас проходят такую эволюцию. Java попыталась приспособить себе LINQ из C#, а создатели LINQ из C# черпали вдохновение в Haskell, и т.п. Получается такая красивая запутанная сеть взаимовлияния между разными языками.
Александр: Тем не менее, C++ всё ещё низкоуровневый язык?
Иван: Большинство считает, что да.
Сергей: О какой «низкоуровневости» вы сейчас говорите?
Иван: Компилируется в низкоуровневый код. Но при этом работает с высокоуровневыми абстракциями. Весь смысл и цель в том, чтобы такие абстракции не приводили к излишним накладным расходам в смысле производительности. C++ должен генерить из своих абстракций такой код, который был бы не хуже написанного вручную. По крайней мере, теоретически.
Александр: Что будет, если кто-то нарушит это правило? Например, ренжи.
Иван: Страдает производительность компиляции — да. Все новые фичи, особенно — фичи, поставляемые в библиотеках, увеличивают время компиляции. Но нет никаких причин, по которым ренжи должны быть медленными. Если ренжи тормозят, то единственное, кого тут можно винить — это компиляторы, не оптимизированные на этот конкретный случай.
Сергей: Тормозят не сами ренжи, а ренжи в режиме отладки — в этом суть всей дискуссии. В релизном режиме они работают отлично.
Иван: Это нормально.
Сергей: Не все с этим согласны :-)
Иван: Да, знаю на практике. В одной компании, в ее самой часто используемой библиотеке… не буду говорить, что это за компания и библиотека… есть алгоритмы, которые асимптотически существенно медленней в отладочном режиме. И кто теперь будет жаловаться, что ренжи делают всё то же самое?
Сергей: В той статье, которую мы сейчас обсуждаем, суть была не в самих ренжах, они были только примером для автора, которого взбесило, что подобная ситуация становится трендом, низкая производительность в режиме отладки. В его предметной области, игровых движках, это просто неприемлемо.
Александр: Эти люди вообще не любят STL, потому что он работает медленней, чем им нужно. Ренжи просто расширяют библиотеку в том же направлении, и для них это выглядит как ещё одна бесполезная фича, которую они не смогут использовать.
Сергей: Haters gonna hate.
Иван: Именно. Например, все используют сортировку. Просто представьте, что в стандартной библиотеке больше нет сортировки. Как бы вы её реализовывали?
Сергей: Я такой вопрос обычно задаю на интервью :-) Это очень частый вопрос.
Иван: Да, частый вопрос — как реализовать сортировку. И дальше ты рассказываешь какую-то базовую версию быстрой сортировки (quick sort), которая на самом деле не используется вообще нигде в мире, потому что она может быть очень медленной в определённых случаях. Стандартная библиотека и не позволяет её использовать, потому что она требует гарантированного N log N, а быстрая сортировка так не может. По большей части может, но стандарт в этом месте очень жесткий, и не говорит об аккумулированном N log N, это должно быть чистое N log N, и как ты будешь реализовывать такой алгоритм? Нужно провести исследование, найти множество разных оптимизаций быстрой сортировки, и слить их в один алгоритм, состоящий по крайней мере из трех разных алгоритмов, как это сделано в libstdc++. В этом и есть смысл стандартных библиотек — тебе не нужно знать все эти вещи, чтобы программировать. Не нужно разбираться, как реализовать всё на свете наиболее эффективным способом, кто-то другой уже позаботился за тебя об этом. Поэтому мне и не нравится такой подход, когда люди заявляют: «STL — очень сложная, давайте не будем её использовать и напишем всё с нуля вручную».
Сергей: Мы подходим к концу интервью, поэтому последний вопроС: как вам в России?
Александр: Похолодало.
Сергей: Даже для тебя? Ты же местный!
Иван: А для меня здесь куда теплей, чем ожидалось.
Сергей: Сейчас -16, а мы сказали тебе, что будет -40.
Иван: Да, вы обещали! Я специально готовился к минус сорока. А потом смотрю на градусник, а там всё теплей и теплей.
Сергей: Ну что ж, теперь мы встретимся только на C++ Russia 2019, это будет в Москве, и там будет плюсовая температура. Спасибо за интервью и до встречи!
Минутка рекламы. 19-20 апреля пройдёт конференция C++ Russia, на которой Иван выступит с докладом «Move-only C++ design», а Александр расскажет про монадические парсеры. Кроме того, Иван проведёт один из трёх больших тренингов — «Applied functional programming in C++». До начала конференции остаётся месяц и программа продолжает уточняться. На официальном сайте можно посмотреть, какие доклады уже попали в программу и приобрести билеты. Обратите внимание, что билеты бывают разных типов, и выбрав правильный, можно существенно сэкономить.
Автор: Sergey Platonov