
Constantine
28.07.2018
12:35:37
Я бы примкнул к обсуждению после изучения хаскеля)

Friedrich
28.07.2018
12:35:39

Andrei
28.07.2018
12:37:35
В плюсах уже сносный синтаксис для функторов. С учетом auto и шаблонных лямбд. Всё то же самое можно делать на одних функторах, к примеру.
Почему именно монады?

Google

Andrei
28.07.2018
12:38:45
Мне лично кажется, что для плюсов естесвеннее были бы scoped-continuationы.
Они и ложатся в целом проще на модель плюсовых вычислений чем монады.


Александр
28.07.2018
12:42:47
IO - контроллируемые эффекты. Полезны даже в С++. Как вы знаете, в Scala тоже можно делать эффекты "по месту", но они все-таки решили, что нужно использовать IO по максимуму.
State - для потокобезопасной эмуляции состояния, где отдельные части можно использовать в разных контекстах.
Reader - для эмуляции окружения, которое не придется определять в виде какого-нибудь глобального объекта или таскать через аргументы функции.
List - для комбинаторики.
Future - для "таск-подобных" вычислений.
Promise - для "async/await-подобных" вычислений.
Maybe (std::optional) - для типобезопасной и удобной замены nullptr.
Either (std::expected) - для типобезопасной и удобной замены кодов ошибок.
STM - а вот здесь уже начинается интересное и близкое мне, потому что эта конкретная концепция (транзакционная память), выполненная в виде монад, предоставляет ряд плюшек, которые не может предоставить обычная императивная транзакционная память. Так, нельзя ошибочно синхронизировать отдельные транзакции, потому что такого понятия - неправильно сделанная синхронизация - попросту нет по построению монады. Также транзакции становятся компонуемыми: из маленьких вы строите большие, а потом еще больше. Однако в традиционном подходе к STM для этого могут быть какие-то странные и неконсистетные правила; посмотрите хотя бы пропозал для С++: там вводится 3 дополнительных синтаксических блока! Нужно учить, как они работают по отдельности, и как их можно/нельзя использовать. Можно ли их вкладывать один в другой, какие будут эффекты, будет ли это потокобезопасно... Монадическая STM, существующая в Haskell, Clojure и Scala, попросту устраняет подобные проблемы.


Andrei
28.07.2018
12:45:12
Хорошо, почему программисты продолжают использовать unsfePerformIO, продолжают использовать runST для вытаскивания значений из памяти.

Александр
28.07.2018
12:45:27

Andrei
28.07.2018
12:45:34
Это выглядит так, как будто им чего-то всё-таки не хватает.
Или как будто язык забывает на какой реальной среде он исполняется.
Но у нас _другой_ язык.
Бесплатно эти монадические вычисления не даются.
Я ж не спорю, всё очень хорошо, но это не c++ way. Я не вижу монадических zero-overhead вычислений.

Google


Александр
28.07.2018
12:50:38
Хорошо, почему программисты продолжают использовать unsfePerformIO, продолжают использовать runST для вытаскивания значений из памяти.
unsafePerformIO - это плохая практика. Такое возможно только в недрах каких-либо библиотек для производительности, но и то не очень приветствуется. В нормально спроектированном приложении этого не делают.
runST - это очень хитрая штука, которая основана на идее, что функция может делать что угодно со своим локальным состоянием, если его не вытаскивать наружу, и если она по-прежнему зависит только от своих аргументов. Можно мутировать какие-то внутренние массивы in-place, можно делать мутабельные алгоритмы, но так, чтобы пользователь снаружи ничего об этом не знал. Он дает аргумент, а функция его вычисляет. Конечно, в Haskell без некоторых "секретных" операций мутабельное локальное состояние сделать нельзя, но монада ST эффективно от нас скрывает всю черную деятельность функции. Насколько это безопасно? Примерно так же, как и другой чистый код. Ведь на самом деле, Haskell - не язык, где совсем ничего плохого не может в чистых функциях произойти. Там есть исключения, и всегда есть опасность OOM, так что в целом, это приемлемо.
Но у нас _другой_ язык.
Это неважно. Ни у кого нет мандата насаждать использование C++ так, как ему хочется. Если для кого-то неидеоматичный С++ решит проблему с ФП дешевле, - его будут использовать. И такие примеры есть, посмотрите хотя бы на то, что делает @DenisKormalev .
Оставаясь в одной догме "Это не по правилам языка!" можно не заметить интересных решений, которые могут подойти конкретно здесь.
Например, что если я вам скажу, что boost::spirit - это плохо? И что монадические парсеры могли бы сделать парсинг более приятным и легким? Ведь есть задачи, в которых нельзя отказаться от С++, но где позволительно выбрать такой подход, который избавит от проблем в будущем, и не важно, является ли он zero-cost или нет. Пока ниша С++ активно подвергается атаке со стороны других экосистем, новые идеи могут все еще удержать его в некоторых областях, поэтому - why not?


Andrei
28.07.2018
12:56:43
Другой вопрос — у нас есть библиотеки, чужой код и API к нему. С привычным IO.
Еще вопрос: как отлаживать монадический код? Мы теряем весь стейт с привычного нам стека.
Это неважно. Ни у кого нет мандата насаждать использование C++ так, как ему хочется. Если для кого-то неидеоматичный С++ решит проблему с ФП дешевле, - его будут использовать. И такие примеры есть, посмотрите хотя бы на то, что делает @DenisKormalev .
Оставаясь в одной догме "Это не по правилам языка!" можно не заметить интересных решений, которые могут подойти конкретно здесь.
Например, что если я вам скажу, что boost::spirit - это плохо? И что монадические парсеры могли бы сделать парсинг более приятным и легким? Ведь есть задачи, в которых нельзя отказаться от С++, но где позволительно выбрать такой подход, который избавит от проблем в будущем, и не важно, является ли он zero-cost или нет. Пока ниша С++ активно подвергается атаке со стороны других экосистем, новые идеи могут все еще удержать его в некоторых областях, поэтому - why not?
Why not? Потому что это core-фича языка. Не платить за то, что не используешь. С монадами притащится новый механизм обработки ошибок, когда в языке уже есть один. Притащится новый контрол флоу без циклов, но с рекурсией.


Alex
28.07.2018
13:01:26
о_О

Andrei
28.07.2018
13:01:26
Из c++ 4-х языков (обычный, макросы, темплейты, констэкспр) будет с++ с 5 языками.
Не поймите меня неправильно, я сам фанат написания парсеров на монадах. В плюсах этого не хватает. Но для этого у меня есть Haskell и Parsec!

Alex
28.07.2018
13:03:00
Я думаю впиливание в язык конструкций для удобного описания монадичнских вычислений не предполагает отказа от привычных инструментов

Andrei
28.07.2018
13:03:25
Язык общего назначения не означает, что он должен ВСЕ задачи решать хорошо.
В плюсах огромное количество более срочных дизайн проблем. Поэтом мне просто кажется нереалистичным в скором времени ожидать что-то такое в языке.


Александр
28.07.2018
13:06:26
Привычное IO можно обернуть в IO-монадические функции. Проблема здесь, скорее, в том, что в С++ нет возможности тегировать функцию как _по-настоящему_ чистую. Увы, ни одна из конструкций (const, constexpr, nothrow, и тд.) не удовлетворяет требованиям. Разве что функции на типах, но цена у такого "очищения" функций весьма высока. Однако, монада IO все еще могла бы быть полезна для явного указания, что программист должен быть осторожен. Во всех остальных случаях, конечно, от IO большого прока не будет.
Отлаживать монадический код в С++ можно точно так же, как и обычный, потому что это обычный код. Вероятно, из-за природы монад, будет очень много скачков дебаггера по внутренностям, но в остальном монадические функции - это просто функции и лямбды. Любой монадический код можно развернуть в цепочку функций, примененных одна к другой. Что и происходит на самом деле. Поэтому больших отличий в отладке не будет, кроме, возможно, длинного стека вызовов.
Но обычно говорят, что монадический код, как и всякий функциональный код, не нужно отлаживать. Его нужно тестировать, - а это просто, ведь мы имеем дело не с состоянием, которое существует в рантайме императивного кода, а мы работаем с функциями, в которые передаем начальный аргумент. Внутреннего состояния у монадической цепочки нет, - кроме тех переменных, что приходят как аргументы. Поэтому мы легко можем подавать в монадическую цепочку "боевые" данные либо же тестовые, - и смотреть, что происходит на выходе. Этого достаточно, чтобы понять, как ведет себя вся (потенциально длинная) монадическая цепочка.
Why not? Потому что это core-фича языка. Не платить за то, что не используешь. С монадами притащится новый механизм обработки ошибок, когда в языке уже есть один. Притащится новый контрол флоу без циклов, но с рекурсией.
Это фича стандарта, но ни один стандарт не указывает, как мне применять язык. Не бывает такого, чтобы все С++ разработчики избегали какого-либо кода лишь потому, что в нем что-то оказалось "не zero-cost" с точки зрения языка. Ведь подобный подход может оказаться "zero-cost" с точки зрения бизнеса. Не существует практик, которые бы запрещали использовать абстракции, если по какой-то причине эти абстракции не вполне согласуются с духом языка, но при этом могут решить конкретную задачу.


Andrei
28.07.2018
13:12:58
С этим никто не спорит. Речь о том, чтобы не тащить это в стандарт.
Не делать это обязательной к исполнению для авторов компиляторов частью языка.

Xessao
28.07.2018
13:13:35

Andrei
28.07.2018
13:14:15
Не знаю насчёт Александра, но я видел пару пропозалов на добавление do-нотации в язык.

Александр
28.07.2018
13:14:18
Why not? Потому что это core-фича языка. Не платить за то, что не используешь. С монадами притащится новый механизм обработки ошибок, когда в языке уже есть один. Притащится новый контрол флоу без циклов, но с рекурсией.
Циклы не противоречат ФП, если речь идет о внутренних циклах, о которых не знает внешний пользователь. Можно сделать чистую функцию, внутри которой будет сидеть цикл, и отрабатывать коллекцию точно так же, как это бы сделала рекурсия. Да, рекурсия - это идеоматичный способ обхода коллекций или вычисления по шагам, но если присмотреться, то определение чистой функции ничего не говорит о циклах. Это ровно та же ситуация, что и с монадой ST.


Andrei
28.07.2018
13:16:43
А. И еще такой вопросик. С учетом того, что в Хаскеллевском коде я вижу время от времени coerce, и с учетом слабой типизации плюсов, как на них без неожиданных выстрелов в ногу писать монадический код?


Серж
28.07.2018
13:22:13
Это не главная мотивация монад сегодня. Позвольте пояснить.
Изначально в Хаскеле был странный и неудобный способ делать эффекты. Все из-за него страдали, и он ощущался как костыль. Потом пришел Вадлер и товарищи, и они придумали, что тут очень подходит концепция монад из некоей математической теории. Попробовали применить, - пришлось переосмыслить, что есть чистое и нечистое в Haskell. Придумали "RealWorld" как некое немутабельное состояние, над которым эта монада (IO) бы работала. Потом придумали do-нотацию, чтобы сократить запись.
А потом началось. Люди поняли, что монады - гораздо более полезный инструмент, чем просто обслуживать цепочку вычислений с эффектами ввода-вывода. Оказалось, что они эффективно выражают различные концепции, такие как состояние, а do-нотация еще и делает код кристально чистым. И монады стали ценны не потому, что они закрывают какую-то там дыру в дизайне Haskell, а потому, что являются мощной абстракцией, паттерном если хотите, который упрощает код в разы. Так, с их помощью можно избавиться от callback hell, можно сделать асинхронные вычисления не хуже, чем с async / await, которые в других языках являются частью синтаксиса. Можно хэндлить ошибки в цепочке операций (Either; в C++ это std::expected), можно явно выражать отсутствующее значение (Maybe; в С++ это std::optional), и тоже связывать операции-возможно-без-значения в цепочку, не перегружая код. А в последствии еще увидели, что с помощью монад можно делать полный аналог Dependency Injection. И нет, иметь переменные - это не мотивация для монад в Haskell.
Зачем это в С++? По тем же причинам. С помощью монад упрощается код, который ранее мы бы писали очень развесисто. Посмотрите, например, на Go-шников с их вездесущей проверкой результата на err. И хотя в С++ есть исключения, возврат ошибок все же используется, и вот такой код может быть значительно упрощен для чтения и понимания с помощью монады. С помощью монад можно писать более надежный и предсказуемый код. Там, где обычный набор инструкций позволяет допустить ошибку по невнимательности, например, программист не обработал результат функции, монадический код это не позволит. Просто потому, что проверка результата функции будет вшита в конкретную монаду (std::optional, std::expected), и потому "пройти мимо" уже будет нельзя.
Или взять std::future. С помощью монадического связывания очень легко пишется такой код, где таски будут вычисляться либо одна за одной, либо параллельно: программист, оперируя монадическими функциями, выбирает, что ему нужно. При этом ему не нужно специально писать, какую фьючу когда надо дождаться, где синхронизоваться и куда передать дальше. Он просто пишет монадический код, а соответствующая монада возьмет эти манипуляции на себя и сделает их "в бэкграунде". Это очень удобно, так как по сути, программист лишь работает со своей вычислительной моделью, которая не "загрязнена" деталями от std::future.
На дворе 2018 год. Без монад уже нельзя, потому что более мощной и полезной абстракции еще не рождалось за последнее двадцать лет.
даже в 2018 в эмбеддеде и системном программировании обходятся без монад, скорее всего есть какая-то причина, абстракция не всегда хорошо

Google

Andrei
28.07.2018
13:23:01
Мой поинт в том, что может быть плюсы плохо подходят для монадических вычислений. Почему бы просто не писать на Хаскелле? (или на других языках, которые я здесь скромно не буду рекламировать)

Xessao
28.07.2018
13:23:45

Серж
28.07.2018
13:23:56
и с++

Xessao
28.07.2018
13:23:59
А кое где и вообще плюсы избегают.

Andrei
28.07.2018
13:24:11

Xessao
28.07.2018
13:24:51

Серж
28.07.2018
13:24:58
стейтфул это каковские? у меня с английским не очень, всмысле прикладные? реальные?

Andrei
28.07.2018
13:25:11


Александр
28.07.2018
13:28:25
coerce в хаскельном коде тоже сообщает, что дизайн не очень хороший; либо что программист не знал о дополнительных возможностях системы типов, которые могли бы избавить от coerce; либо о том, что система типов работает не совсем хорошо (и это так в тех высоких областях Haskell, где обитает вся магия вычислений на типах).
В С++, как верно замечено, слабая типизация, - но и сильная тоже, - это очень зависит о каком из нескольких подъязыков мы говорим. Возможность писать типобезопасный код все же есть, и прежде всего, конечно, это шаблонная магия. Монадический код, как и, например, код, использующий STL, может быть в равной степени типобезопасным. Лучше всего, конечно, этих целях избегать динамического полиморфизма, неявных кастов, работы с указателями. Можно писать код полностью на "статике". То бишь, со структурами, создаваемыми на стеке, только статически полиморфные, - что, возможно, принесет дополнительный оверхед для копирования, но позволит не беспокоиться о странных инвлидациях указателей, о неверных кастах между родителями/потомками (то бишь, нарушении LSP), ну и прочих прелестях, которые мы так любим в С++.
На самом деле, в своей монадической библиотеке STM я делаю ровно это. Конечно, там возникает копирование сохраняемых в STM значений, и сами структуры, обслуживающие монадический код, тоже копируются. Но тем не менее, код остается весьма безопасным и сравнительно простым. (По чести сказать, библиотека Wyatt-STM от взрослых дядей ведет себя точно так же).


Серж
28.07.2018
13:29:25
в с++ строгая типизация, так в википедии написано, в си слабая

Александр
28.07.2018
13:30:03

Antony
28.07.2018
13:30:16

Александр
28.07.2018
13:31:07
Стейтфул — это в противовес стейтлесс. Языки с состоянием, с моделью вычисления на машине Тьюринга, а не с моделью типизированного лямбда-исчисления как в Хаскеле.
Но стейтлесс не отменяет того, что в какой-то части программы у вас будет этот самый стейт. Вообще, "стейтлесс" относится только к цепочкам скомпонованных функций, но если у вас есть мутабельная переменная, и вы ее передали в такую цепочку, а результат снова присвоили этой переменной, то ваш стейт никуда не исчез. Вместо этого у вас еще появился отдельный стек из стейтлесс функций, которые, при прочих равных, могут работать точно так же, ведь это обычные функции.


Серж
28.07.2018
13:36:33
эти ваши монады еще небось и на шаблонах все, бинарник на 100 мегабайт небось потом получается

Andrei
28.07.2018
13:37:08
Про golang?
Не. Свой язык уютный разрабатываем, куда я активно тащу фп фичи.

Серж
28.07.2018
13:37:23
а потом постоянные подгрузки в кеш инструкций процессора все тормозят

Александр
28.07.2018
13:37:58

Google

Серж
28.07.2018
13:39:03

Александр
28.07.2018
13:39:11

Andrei
28.07.2018
13:40:07
котлин штоле? ты из житбрейнс?
Да нет. Боже упаси. Что бы я в таком случае тут модератором делал? :D Частная компания, делаем язык для своих нужд, потихоньку опенсорсим.
Хаскелль тоже вынужден тормозить в рантайме из-за этого.

Александр
28.07.2018
13:41:44
Кстати, как вы находите ranges от Эрика Ниблера? Может быть, у вас закрадывается ощущение, что это не идеоматичный zero-cost C++? А вдруг это и правда не идеоматичный zero-cost С++?

Andrei
28.07.2018
13:42:46
Я к сожалению перестал следить за последними пропозолами. Опять же не хочу делать рекламу, но мы в языке сделали ranges частью стандартной библиотеки без проблем с zero-cost овостью.

Серж
28.07.2018
13:43:23
скажи уже как называется ваш убийца с++?

Andrei
28.07.2018
13:43:53
Но это только потому что нет никакого легаси и систему типов или абстракцию вычислений можно ломать хоть каждый день.

Александр
28.07.2018
13:44:06
Хаскелль тоже вынужден тормозить в рантайме из-за этого.
В каком-то смысле да.
Однако быстрые вещи в ФП тоже есть, и с ними можно добиться очень интересных результатов. Например, взятие MVar стоит пару десятков наносекунд. Хотелось бы, например, сравнить со взятием std::mutex в С++.

Andrei
28.07.2018
13:44:22
MPL называется. Не думаю, что о чём-то скажет или что можно что-то релевантное найти сейчас.
В местах критических по производительности не грех покопаться в целевой платформе и посмотреть на её примитивы. Руками спинлоки покрутить. Атомикам правильный мемори ордер прописать.

Серж
28.07.2018
13:46:58
ничего не нашел навскидку, мне бы интересно было над таким работать, желаю удачи

Александр
28.07.2018
13:47:13
А есть ли все еще код на С++ в местах, которые не требовательны к производительности? Или из этих ниш его вытеснили другие языки?

Серж
28.07.2018
13:47:36
есть еще легаси на с++
которое компилируют с -О0 потому что иначе не работает

Andrei
28.07.2018
13:47:58

Серж
28.07.2018
13:48:09
нирикаминдую вскрывать эту тему, хоть ты молодой и шутлиый, хоть старый и грустный

Andrei
28.07.2018
13:48:22
То есть понятно, что из рилтайма, игр, серверов и прочих вещей ожидать вытеснения с++ не приходится.

Google

Andrei
28.07.2018
13:48:52
Но бизнес-логику нерилтаймовых приложений пишут от питона до агды.

Серж
28.07.2018
13:49:18
зайди на хедхунтер и набери в поиске агда или идрис

Andrei
28.07.2018
13:49:21
Ну и кровавый джава энтерпрайз конечно.

Серж
28.07.2018
13:49:23
сколько там вакансий будет?
откуда вообще про эти языки узнают и кто позволяет на них писать?

Andrei
28.07.2018
13:49:55
Питона будет много. Котлина. Джавы. Скалы.

Александр
28.07.2018
13:49:55
Я здесь вижу некоторую проблему. ranges и кое-какие другие фичи (корутины) явно таргетированы на С++ "клиентского уровня", вроде десктопных приложений или какого-нибудь бэкенда. Но если в этих нишах доля С++ сокращается, то значит, польза от нововведений будет все меньше и меньше?

Серж
28.07.2018
13:50:17
на хаскель наверное столько же вакансий будет

Andrei
28.07.2018
13:50:55

Серж
28.07.2018
13:51:23
осталось понять куда этот поезд так летит

Александр
28.07.2018
13:51:25
У меня такое же ощущение.

Andrei
28.07.2018
13:51:36
Но если сейчас продолбаться с модулями, с 22-м стандартом — просто новое поколение джуниоров не вырастет.

Серж
28.07.2018
13:52:08
а почему сишка никуда не запрыгивала, а уверенно стоит на ногах
вы же не будете с этим спорить?

Andrei
28.07.2018
13:52:44
Ниша другая. Практически схлопнулась до эмбедеда и системного программирования.
И то, уже там её теснят всякие расты.

Серж
28.07.2018
13:53:25
а с++ это бекенд что-ли?
раст очень много для чего просто не канпилится

Александр
28.07.2018
13:54:00
Язык Си очень сфокусирован и не претендует на звание очередного индустриального комбайна. Очень понятно, когда язык нужен, и когда лучше не надо.

Серж
28.07.2018
13:55:24
я одно время пытался понять когда брать с++, а когда си, так вот все советы в интернете говорять что надо всегда брать с++ и использовать то что тебе надо (си с классами)
только если нет компилятора с++, брать си

Andrei
28.07.2018
13:55:41
Си можно рассматривать как новый ассемблер. Просто очень-очень близкий к железу язык с минимально необходимым набором абстракций.