Обычный Си уделывает Хаскель по производительности, потому что компилятор gcc тоже прекрасно понимает, что такое чистота, и умеет сокращать чистые вычисления. Таким образом, введённые в Хаскель концепции не привели к росту производительности.
Хотя, насколько я понял, монада - это по сути переопределяемая «точка с запятой» из Си. Смысл монады в том, что она позволяют абстрагировать процесс порождения побочных эффектов и взять его под контроль. За счёт этого якобы можно реализовать внутри языка транзакции, call/cc, undo и т.п..
Насколько это в реальности возможно и насколько технологически сложно - я пока не выяснил.
Например, я спросил (тему не буду искать), легко ли сделать с помощью монад имитацию языка с исключениями (throw/catch), и оказалось, что на Хаскеле это можно, но нетривиально.
Обычный Си уделывает Хаскель по производительности, потому что компилятор gcc тоже прекрасно понимает, что такое чистота
Зато не понимает что такое ленивость, и тупо будет считать массив из 100 миллионов элементов, хотя нужны только первые 10
т.е. нельзя иметь функций типа (take 10 (range 1..))
В обычном языке ты стейт помещаешь в переменную? Так? Вот это ссылка и есть по факту. Просто в haskell ссылки явные. Вот здесь оверхеда точно нет. Оверхед может быть в другом месте, если ты пытаешься составить слишком сложный композиционный стейт
Обычный Си уделывает Хаскель по производительности, потому что компилятор gcc тоже прекрасно понимает, что такое чистота
Только сейчас прочитал. Чушь какая-то. Все зависит от задачи, от ситуации. Может быть, и наоборот. К примеру, у меня код на haskell часто быстрее аналогичного кода на rust в сложных задачах.
В общем, лучше рассуждать о haskell тем, кто на нем хотя бы программирует. А то на ЛОРе тут даже смачное словечко придумали для таких «теоретиков»
Пользуясь случаем, хотел спросить. Вот есть у меня цепочка проверок Check1...CheckN :: a -> Either Error a и монадическая цепочка x >>= Check1 >>= Check2 ... >>= CheckN. Превратится ли это там где-то глубоко под капотом в что-то такое?
If !Check1(x) then Error="Check1 failed"; goto @End
If !Check2(x) then Error="Check2 failed"; goto @End
...
If !CheckN(x) then Error="CheckN failed"; goto @End
End: ...
Ну и то же самое про монаду стейт. Превратится ли это во что-то си-подобное, с присваиванием <- get и put?
Просто вопрос в том, на низком уровне, в готовом виде, оно там всё оптимальное или не очень?
Если компилировать с опцией "-O2", то на счет State скорее, что да, будет соптимизировано. На счет Either точно не знаю.
Здесь важно, есть ли у функций прагмы INLINE или INLINABLE. В рамках одного модуля компилятор GHC обычно сам достаточно хорошо инлайнит, но если функция вызывается из другого модуля, то бывает иногда, что ему нужно помочь.
Во время инлайна очень хорошо сокращаются разные применения лямбд и тому подобное.
Ну, если Вы используете монадные трансформеры, то тоже крайне желательно использовать эти прагмы там, где используются ограничения (constraints) по классам типов. Вот здесь без прагм компилятор часто тупит без подсказки.
А так можно и посмотреть, во что превращается хаскелевский код. Описано в книге Real World Haskell ближе к концу книги. Я сам такой псевдокод не использую, обхожусь постоянным тестированием скорости, но возможность такая есть. Показывается что-то типа промежуточного языка до машины-исполнителя. Может, с этого и стоит начать, посмотрев что же получится
То есть, если есть ограничения по классам типов, то компилятор часто пытается использовать обобщенный код, немного схалтурив. Грубо оценивая, такой код будет в раз 6 медленнее, чем код с прямыми типами. Поэтому в таких случаях очень помогают вот те две прагмы, и код становится таким же быстрыми, как если бы были прямые типы без ограничений. Но не все библиотечные функции к этому готовы. Это все касательно ограничений по классам типов.
А так в общем иногда бывает, что и NOINLINE помогает, когда мы точно указываем, что инлайнить, а что - нет
Спасибо за подробный ответ. Мне нравится писать некоторые свои пет-прожекты на хаскеле, сейчас вот вникаю в extensible effects. Вообще язык богат на интересные идеи. И всё нравится, кроме аспекта производительности: нужно во многом разобраться, чтобы писать эффективный код. Тот же seq в нужные места ставить, понимать, что имеет смысл попробовать заинлайнить, boxed-unboxed и т.д.
Когда я говорю что сложно писать эффективный код, имею в виду какие-нибудь конечные разности, например.
Да, с boxed-unboxed тоже надо аккуратно. И на эту тему есть прагмы. Вообще, если посмотреть на код в стандартных библиотеках, то он обычно весь напичкан прагмами.
И строгие вычисления тоже могут помочь ускорить код там, где они имеют смысл. Как в определениях полей типов, так и в let.
Еще есть rewriting rules, когда можно самому задать, как сокращать код в некоторых конкретных случаях. Тоже через прагмы.
По себе могу сказать, что писать на haskell эффективный код вполне можно, но писать такой код на любом языке непросто, даже на java и си++. Везде есть нюансы