Search
Write a publication
Pull to refresh

Comments 134

Функциональное программирование основано на отрицании времени.

Только возможные факты и правила их соответствия. Правила дают гарантии ещё до запуска программы.

Когда эта мантра будет усвоена дальнейшее изучение ФП станет легче

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

Императивное программирование можно противопоставить ему. Ещё нет правил, но уже есть желание дойти до цели. Умный командир, глупые исполнители. Такой стиль провоцирует что командир занимается не своим делом, а разбирает проблемы исполнителей на лету в зависимости от контекста и без гарантий.

ФП основано на чистых функциях, которые соединяют выход и вход. Они ничего не меняют: внешний контекст (файлы и прочий IO), состояние (например, члены класса) и не кидают исключений. Такие функции легко композировать.

Из практических соображений имеет смысл не отрицание (как в Haskell)

Так хаскель не отрицает. Хаскель как раз за счёт системы типов проверяет, что всё нормально раскинуто либо в чистые, либо в

эффектные слои

Из практических соображений имеет смысл не отрицание (как в Haskell)

В Хаскеле всё красивее. Есть тривиальная монада State, которая позволяет читать и писать контекст. А Хаскельная IO, в которой крутятся эффекты - это просто State, с контекстом #RealWorld. То есть, ввод с клавиатуры, сигналы, вызовы fopen, fread, fwrite, время и прочее и прочее - это просто контекст.

В хаскелле просто нашли способ запихать глобальный синглетон "реальный мир" в простую синтаксическую и семантическую обёрточку - в аргумент с типом, который невозможно случайно создать с нуля или копировать. (И больше ничего с ним делать невозможно, только передавать дальше по цепочке выполнения). А поскольку такой аргумент никому не нужен и только мешает, как синтаксический шум, тут-то нотации работы с монадами (конвеер и особенно do) пригодились.

Можно философски думать об IO как о State, где состояние хранится во внешнем мире, - но это не совсем справедливо. Во-первых, State можно безболезненно копировать, а мир - нельзя. Во-вторых, State неизменно, если его не менять, а мир меняется сам по себе.

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

Можно философски думать об IO как о State, где состояние хранится во внешнем мире, - но это не совсем справедливо. Во-первых, State можно безболезненно копировать, а мир - нельзя. Во-вторых, State неизменно, если его не менять, а мир меняется сам по себе.

Потому что ваш исходный собеседник опечатался, и вместо State имел в виду ST, который копировать тоже нельзя.

Не опечатался, но пытался упростить. Конечно, IO = ST #Realworld, но st монада - не математический объект. С другой стороны, её интерфейс такой же как у State и смысл тот же - контекст с get и put методами. ST - это не-персистентный State, который позволяет, например, честные массивы с О(1) inplace записью.

С другой стороны, её интерфейс такой же как у State и смысл тот же - контекст с get и put методами.

ИМХО это так не стоит говорить, потому что ИМХО логично определять интерфейс как семантику операций, а она у ST и State разная (даже если беглое определение неформальным языком использует похожие слова).

Очередная статья на хабре на тему "Что такое ФП". Если честно, ни из одной из них я так и не понял, что такое ФП и чем оно принципиально отличается от императивного foo(bar(arg)) или, как вариант, ООП-шного getFoo().bar().baz().

В русскоязычном сегменте нет нормального baseline-гайда

Потому что в русском языке нет слова baseline?

императивного foo(bar(arg)) 

Почему ты решил, что оно императивное?)

Почему ты решил, что оно императивное?)

Потому что GOSUB в Бейсике был уже лет 40 назад.

Ага, и те же 40 лет назад в бейсиковских подпрограммах не было способа вернуть результат :-). Всё, что можно – это изменять (мутировать) состояние. Даже стека для локальных переменных не было – только для адресов возврата.

Ага, и те же 40 лет назад в бейсиковских подпрограммах не было способа вернуть результат :-)

Оно просто работало иначе :)
В ассемблерном call тоже только один параметр - адрес перехода.

В большинстве ассемблеров был push/pop.

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

В большинстве ассемблеров был push/pop

Обязательств использования которыми вообще-то не накладывалось. ;)
Можно было передавать параметры и через регистры.

В Бейсике же подпрограммы только и могут, что что-то менять

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

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

Реально, очередная статья рассказывающая сложно о простом в перемешку с каким то бредом. Люди придумывают себе концепции чтобы загонять себя в их рамки и решать проблемы которые они же и создают. Вместо foo(bar(arg)) или по нормальному const b = bar(arg); const f = foo(b) они хотят писать arg.map(bar).map(foo). Для разных задач лучше подходят разные концепции, функциональщина действительно хорошо подходит для работы с потоками данных. Также не стоит забывать что для использования функциональщины в не предназначенных для этого языках необходимо сначала написать вспомогательный код, который накладывает дополнительные расходы и может оказаться трудно поддерживаемым. В общем, нормально делай нормально будет

Оба неправы, ФП - это не map/reduce/filter, и операторы через точку; автор пишет как раз о правильных вещах. Один из аспектов ФП - это найти в поведении данных математику и, доказав её, защититься от связанных ошибок. А ещё от ФП до ЯОП один шаг.

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

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

У меня сложилось ощущение, что это стëб над фп-фаперами 😁 Но если это не так, то в программировании бо-ольшие проблемы 🤣

Если in mass ФП понимают на таком говно уровне, то хлеб с икрой мне обеспечен надолго

Если честно, ни из одной из них я так и не понял, что такое ФП

Другие статьи мне не попадались (или мозг подавил эти травмирующие воспоминания), но вот эта - просто дичь какая-то, я ровным счётом ни хрена не понял, не понял какой смысл вообще, так что запишите меня тоже в ваш лагерь :)

вот эта - просто дичь какая-то

Особенно с учётом серии практически идентичных картинок в Акте 1 (отличия только в том, что некоторые блоки выделены немного более жирным) и вообще великолепной картинки El. Psy. Congroo, две версии которой отличаются только одной строкой.

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

Что думаете о функциональном программировании?

После таких статей стараюсь о нём не думать =)

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

От ФП Троггу грустно. ФП Троггу давать молоток для всего с алмазным напылением с большая инструкция на 500 страниц. ООП давать Троггу молоток, отвёртка, микроскоп. Всё подходит забить гвоздь!

А если серьёзно, ну давайте лучше с процедурным программированием сравним и ответим честно на 2 вопроса: что нельзя написать процедурно? В чём отличие, кроме "красоты"?
Хотите красоты - делаете DSL, не пытаясь решать сразу все проблемы мира

Трогг не математик, не архитектор и не юрист.

Трогг, бери лопату и копай!

что нельзя написать процедурно?

... и на ассемблере, ага. Дальше продолжать?

В чём отличие, кроме "красоты"?

В издержках. И компромисах.

Ассемблер - отличный пример. Есть места где ассемблер будет просто незаменим в практическом смысле. Незаменимого ФП я придумать не могу.

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

Есть места где ассемблер будет просто незаменим в практическом смысле.

Речь ведь не о том. Всё можно переписать на ассемблере.

Незаменимого ФП я придумать не могу.

А незаменимое императивное программирование можете? А ничего, что они эквивалентны?
Ну, или попроще - незаменимый питон можете представить? Отменяем?
Незаменимые плюсы? Отменяем?
Незаменимый плейн си? Отменяем?
Остается одно только телевидение ассемблер. Знакомая риторика? Я об этом говорил.

Ну а издержки и компромиссы буквально везде есть.

Так они и определяют выбор.

ментальную модель без переменных но с необъяснимыми монадами - спасибо, но нет.

Ну, я "ментальную модель" php переживаю так же - спасибо, но, нет. А кто-то жить без него не может и сейчас сольёт мне карму :). Или, вот, плоскоземельщики идею шарообразной Земли пережить не могут... ))

Незаменимого ФП я придумать не могу.

Кстати, не то, чтобы незаменимое... но, к примеру, (относительно) простое автоматическое(!) верифицирование.

А незаменимое императивное программирование можете? А ничего, что они эквивалентны?

Извиняюсь, что с чем эквивалентно?

к примеру, (относительно) простое автоматическое(!) верифицирование

Приведите, пожалуйста, практичный пример

Остается одно только телевидение ассемблер

Меня полностью устраивает, если останется только ассемблер. С ИИ всё к этому и идёт.

Извиняюсь, что с чем эквивалентно?

Императивная и функциональная парадигмы.

Приведите, пожалуйста, практичный пример

Практический пример полезности программы, не содержащей ошибок?

Меня полностью устраивает, если останется только ассемблер

Да, да, про ассемблерных апологетов я и говорю. Написали хоть одну практически полезную программу сложнее хелловорда? А две?

  1. Тут требуется очень подробное объяснение (или авторитетная ссылка), т.к. меня учили что это принципиально разные парадигмы, хотя в каком-то смысле...

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

3. Ну допустим, я написал пару десятков программ на NASM в лохматые годы. Это вообще никак не относится к идее, что ассемблер не единственный язык ТОЛЬКО потому, что код пишут человеки.

Тут требуется очень подробное объяснение (или авторитетная ссылка), т.к. меня учили что это принципиально разные парадигмы, хотя в каком-то смысле...

не знаю почему вас этому учили, если честно, и это удивительно. Меня учили, что Машина Тьюринга, Лямбда-исчисление Чёрча, Машина Поста и Нормальные Алгорифмы Маркова эквивалентны и тому имеются строгие математические доказательства. Доказательствам тоже учили, но за давностью лет я их не помню. В практическом же смысле, обычно, новые архитектуры или парадигмы сводят к тьюринг-полноте конструктивным способом, просто реализуя в этой парадигме машину Тьюринга. Реализаций машины Тьюринга, к примеру, на Лиспе - полно. В принципе, её несложно написать самому. Обратный переход ещё проще, поскольку много лисп-машин написано на каком-либо императивном/объектном/ещё-каком-то языке. Так что, да, они эквивалентны и строго математически и конструктивно.

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

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

Ну допустим, я написал пару десятков программ на NASM в лохматые годы. Это вообще никак не относится к идее, что ассемблер не единственный язык ТОЛЬКО потому, что код пишут человеки.

Это относится к пониманию почему это так. Если вы не поняли, значит ваши программы были по сложности уровня хелло ворда и их не надо было поддерживать. Либо конкретно вы - гений уровня Джона Кармака. Остальные люди существенно примитивнее и даже имеющий выразительнейший синтаксис, Пёрл считают write-only языком. И всилу ограниченности понапридумывали примитивных костылей в виде языков высокого уровня, чтобы хоть как-то умещать в голове решаемую проблему и её реализацию. Так-то языки высокого уровня компьютеру определённо не нужны.

Я застал времена, когда мы писали программы без каких либо тестов

Это Вы ещё времена передачи программ на бумажке/перфокарте в оперзал застали?
Во все остальные уж ручное тестирование программистом-то точно было.

только потому, что не умеем (слишком сложно) верифицировать программы формально

Не малую роль играет и "не сильно хотим".
Во-первых, не так уж хорошо обстоят дела с инструментарием. Языков, которые запрещали бы писать код без указания спецификации, очень мало и они далеки от популярности того же С.
Во-вторых, сложившаяся практика изучения программирования тоже мало способствует верифицируемому подходу. Учебник по какому-нибудь ЯП традиционно начинается прямо с Hello, world! Даже не с объяснения того, что нужен ввод-вывод, а прямо с кода. И далее в том же стиле "write code, not specs".

Это Вы ещё времена передачи программ на бумажке/перфокарте в оперзал застали?

и их тоже. Но речь и о существенно более поздних временах.

Во все остальные уж ручное тестирование программистом-то точно было.

так и с перфокартами запуск и являлся такой проверкой. А запуск программы программистом и тыкание по хэппи пассу в лучшем случае. Но это не тесты. Ну, ладно, смоук-тесты ))

Не малую роль играет и "не сильно хотим".

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

Во-первых, не так уж хорошо обстоят дела с инструментарием.

так о том и речь :).

И далее в том же стиле "write code, not specs".

У меня был какой-то учебник в котором была глава в которой натурально доказывалась программа. По-моему, какая-то сортировка.

и их тоже

Здорово. Нам их только издалека показывали, а тесты проводил лектор путём трансляции кода с листика бумаги собственным мозгом.

хотим, хотим

Хорошо, уточняю: массово не хотим, "as is" куда милее.

У меня был какой-то учебник в котором была глава в которой натурально доказывалась программа

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

У меня был какой-то учебник в котором была глава в которой натурально доказывалась программа. По-моему, какая-то сортировка.

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

Все это математически можно проанализировать и доказать что все это не выходит за рамки ограничений физических вычислителей, ну и выполняет определенную пользовательскую функцию. Вообще говоря все это ВСЕГДА НАДО анализировать и доказывать, альтернативой будет подход из известного мультфильма, называется "Так сойдет". Просто иногда код можно написать так что все это вполне наглядно и очевидно прослеживается по коду, но вряд ли это будет наглядно если у нас функция создает и возвращает функцию которая потом неизвестно где вызывается, хотя иногда и такая техника оправдана, но необходимость такой сложной конструкции точно надо доказывать в каждом конкретном применении!.

Все это математически можно проанализировать и доказать

всё так. только это сложно. а в случае императивных программ - суперсложно.

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

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

необходимость такой сложной конструкции точно надо доказывать в каждом конкретном применении!.

а с точки зрения ФП, ровно наоборот, итерация - неоправданно сложная конструкция и необходимость её использования точно надо доказывать в каждом конкретном применении ))

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

Так "какие ваши доказательства" :) ? Или это вы, реально, мамой клянетесь :) ?

Мне то особо доказывать ничего не надо, как бы, на С/С++ пишут с древних времен и до сих пор, практически нет програмных продуктов в которых бы не использовался С/С++.

Псевдо-код в стиле С/С++ используется в стандартах (MPEG например почитайте). ...

Так "какие ваши доказательства" :) ? Или это вы, реально, мамой клянетесь :) ?

зачем мне клясться, вы сами написали, что для вас это не наглядно и нужно доказывать необходимость. Ну, и, в принципе, это объективно так - в языках, вреде С++ это не наглядно, непонятно зачем (если есть ОО и полиморфизм) и нужно доказывать необходимость. Такой же, по сути, костыль, как цикл for в лиспе ))

и до сих пор, практически нет програмных продуктов в которых бы не использовался С/С++

И сколько из этого кода доказано? Правильно, нисколько.

Псевдо-код в стиле С/С++ используется в стандартах

Разве это делает его более доказываемым?

И сколько из этого кода доказано? Правильно, нисколько.

Этот код успешно используется десятилетиями, это конечно косвенное и статистическое доказательство, но с такими сроками и объемами вполне надежное. Но есть и куча книг в которых все конструкции языка С/С++ совершенно разжеваны, то есть напрямую доказаны, разве вы не знаете об этом?

С/С++ используется в стандартах

Разве это делает его более доказываемым?

Если вас не убеждает то, что язык используется для объяснения(!) положений стандартов, то есть этот язык фактически признан аксиоматическим, других аргументов у меня не будет.

Вы заставляете меня что-то доказывать про языки типа С/С++, и видимо считаете что ваши аргументы типа

в функциях, как объектах первого класса нет ничего сложного ненаглядного и необычного.

не требуют доказательств! Это ни так! Если к тому что вы написали не добавить:

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

Этот код успешно используется десятилетиями

а толку-то, если он насквозь дырявый?

Но есть и куча книг в которых все конструкции языка С/С++ совершенно разжеваны, то есть напрямую доказаны, разве вы не знаете об этом?

Во-первых, какое имеет значение что и как разжёвано? Все конструкции математики разжёваны, не так ли? А 2+2=5 - доказано?
Во-вторых, вы когда-нибудь слышали про undefined behaviour, UB сях и плюсях? А вы полюбопытствуйте! Узнаете много нового и интересного - обещаю :)

Если вас не убеждает то, что язык используется для объяснения(!) положений стандартов, то есть этот язык фактически признан аксиоматическим, других аргументов у меня не будет.

Вы вообще знаете что такое доказательсто? А вы полюбопытствуйте! Узнаете много нового и интересного - обещаю :)

не требуют доказательств! Это ни так!

только для того, кто не осилил в них.

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

в операторах цикла вообще ничего нет с практической точки зрения, они нафик никому не нужны на практике.
в вызовах подпрограмм и функций вообще ничего нет с практической точки зрения, они нафик никому не нужны на практике.
и тд
в принципе, в модели Тьюринга нужен единственный оператор - qiaj→qi1aj1dk

Этот код успешно используется десятилетиями, это конечно косвенное и статистическое доказательство, но с такими сроками и объемами вполне надежное.

А потом заголовки

  • В загрузчике GRUB2 выявлена 21 уязвимость

  • В библиотеке Libxml2, разрабатываемой проектом GNOME и применяемой для разбора содержимого в формате XML, выявлено 5 уязвимостей, две из которых потенциально могут привести к выполнению кода при обработке специально оформленных внешних данных.

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

  • Уязвимости в пакетах с Kea DHCP и cyrus-imapd, позволяющие повысить привилегии в системе

  • Уязвимость в KDE Konsole, позволяющая выполнить код при открытии страницы в браузере

  • Уязвимости в PAM и libblockdev, позволяющие получить права root в системе

  • Уязвимость в GNU screen, позволяющая выполнить код с правами root

Дальше мне надоело копипастить заголовки из моего фида opennet, фильтрованного по «уязви». Их там дофига (NB размер скроллбара справа):

Но есть и куча книг в которых все конструкции языка С/С++ совершенно разжеваны, то есть напрямую доказаны, разве вы не знаете об этом?

Я боюсь, что вы не понимаете, что такое доказательство.

Если вас не убеждает то, что язык используется для объяснения(!) положений стандартов, то есть этот язык фактически признан аксиоматическим, других аргументов у меня не будет.

Русский язык тоже можно использовать почти как псевдокод для объясенния(!) полжоений стандартов, но это не делает его доказанным.

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

Представляете, практический пример это код. Моё предположение в том, что автоматически верифицируемые программы настолько тривиальны, что польза в такой верификации совершенно незначительна. А для всего что "нечистое" всё так же нужно писать тесты. Ваш пример развеет моё невежество.

Разве для программиста или пользователя важна эквивалентность в смысле - эти две программы теоретически представимы одним набором инструкций и это можно доказать?

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

Парадигмы не по такому принципу надо сравнивать, а с учетом кучи нюансов реального мира.

Кому надо и что это это даст?

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

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

Это архиважно, если мы говорим о сравнении парадигм.

И именно поэтому это абсолютно не важно.

И, если вы помните, этот тред начался с вопроса "что нельзя написать процедурно?". Всё можно написать и "процедурно" и "функционально" всилу эквивалентности парадигм.

Моё предположение в том, что автоматически верифицируемые программы настолько тривиальны, что польза в такой верификации совершенно незначительна

Именно так. В случае программ в императивной парадигме. Где поэтому вместо верификации используют жалкую пародию на неё - юнит и прочие тесты.

Представляете, практический пример это код.

Так вам практический пример, а не практичный? Так бы сразу и сказали! :)

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

я тут задался вопросом о переходах в контексте с ассемблером и понял ассемблер по итогу, написав переходы как представлял тоесть конечный автомат если его описывать на ассемблере даст понимание как пользоваться ассемблером, ну я писал на nasm -f elf64

https://godbolt.org/z/169h9qG1n

А если серьёзно, ну давайте лучше с процедурным программированием сравним и ответим честно на 2 вопроса: что нельзя написать процедурно?

Любой алгоритм можно написать процедурно. И λ-исчисление, и машина Тьюринга, собсна, Тьюринг-полны.

В чём отличие, кроме "красоты"?

Отличие в том, какую информацию о функции даёт её тип, и, соответственно, что нельзя написать (а я утверждаю, что современное ФП — это не про «передачу функции в функции», что умеют почти все языки, а про выражение семантики в типах).

Если у меня в хаскеле написано foo :: Int → String, то я знаю (при некоторых дополнительных не важных для принципа вводных), что эта функция не общается с внешним миром, не отправляет запрос к БД, не читает и не пишет в глобальные переменные, и не отправляет мой ~/.bitcoin/wallet на сервер злоумышленника. Если у меня в плюсах написано std::string foo(int), то эта функция может делать всё что угодно.

Соответственно, рефакторить типизированный код куда проще и одно удовольствие. В моей практике хаскеля там действительно работает «if it compiles after refactoring, it works», в моей практике плюсов это не работает почти никогда.

Короче, отличие в том, что о коде проще рассуждать, и компилятор проверяет больше вещей.

Хотите красоты - делаете DSL, не пытаясь решать сразу все проблемы мира

Кстати, и eDSL, и DSL на хаскеле и подобных языках делать — одно удовольствие.

Вот это хорошее объяснение, мотивирует в 3 раз попытаться разобраться с Haskell =)

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

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

Да, особенно если речь о взаимодействии с внешним миром и/или об интеграционных тестах.

Для внутренней, чистой логики их нужно сильно меньше, потому что много чего ловится типами. Хаскель же по большому счёту популяризовал property-based testing, и, например, либа quickcheck — про них. Вы пишете выражение вроде \list → reverse (reverse list) === list, а либа сама генерирует примеры, проверяет выражение, и в случае нахождения контрпримера пытается его уменьшить. Где-то рядом model-based-тесты, где у вас есть медленная, но легко понимаемая и проверяемая модель вашей логики, и есть оптимизированная, но тяжёло проверяемая. Вы аналогично пишете prop-тесты, что их результаты совпадают.

Чтобы тесты (особенно property-based) на внутреннюю логику не писать и довериться тайпчекеру на все 100%, нужны ещё более сильные системы типов, вроде идриса или агды.

ИМХО хорошим ресурсом на тему будет книжка Сэнди Магвайра «Algebra-driven design». Там мало формальной теории (и её можно скипать), но идеи она передаёт выразимые и в более мейнстримных языках.

Если у меня в хаскеле написано foo :: Int → String, то я знаю, что.... не отправляет мой ~/.bitcoin/wallet

Нет, не знаете! Вы знаете о том, что она не возвращает во внешний мир результатов об этом. Да, компилятор Хаскеля (или другого ленивого (!) языка, но я знаю только о Хаскеле. Остальные фп-языки схожего дизайна и концепции настолько мало популярны, что не все поймут, если упомянуть их) декларирует это, но т к ваша программа реально выполняется на "императивном" железе, а не на космическом абстрактном квантовом фп-симуляторе, то никаких гарантий у вас нет. Можно пропатчить бинарный код (или компилятор) так, что это утверждение станет не верным. Кроме того, если внутри этой функции используется бинарная библиотека, о которой вы ничего не знаете (не важно каким инструментом она собрана - пропатченым компилятором хаскеля или вообще компилятором си, но в хаскель импортирована как "чистая"), функции которой объявлены чистыми, но таковыми не являются, то сказать что она не отправляет ваш кошелек уже не получится. Если же весь код открыт, то и в случае ооп можно сказать отправляет она что-то или нет.

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

Нет, не знаете! Вы знаете о том, что она не возвращает во внешний мир результатов об этом.

Какая операционная или денотационная разница?

Можно пропатчить бинарный код (или компилятор) так, что это утверждение станет не верным.

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

Во всех этих разговорах подразумевается, что никто не патчит компилятор или бинарь, чтобы сделать вашу жизнь хуже. Никто не ломает намеренно библиотеку для тестирования, чтобы она вместо failure выводила success, если хостнейм машины равен "Arlekcangr's work PC". Никто не подменяет работающий бинарь в продакшене на версию, написанную соседней командой, которая вроде должна делать то же, но не делает. Никто не делает #define int std::vector<std::string> перед объявлением функции.

Вы часто видели, чтобы в тредах об ООП на «инкапсуляция помогает скрывать данные и реализацию» кто-то всерьёз отвечал «нет! компилятор можно пропатчить, чтобы он игнорировал private! можно сделать #define private public!», и это воспринималось в условиях дискуссии как валидное возражение?

Кроме того, если внутри этой функции используется бинарная библиотека, о которой вы ничего не знаете (не важно каким инструментом она собрана - пропатченым компилятором хаскеля или вообще компилятором си, но в хаскель импортирована как "чистая")

Компилятор эти свойства проверяет транзитивно. И в safe haskell вы не можете импортировать нечистые библиотеки как чистые.

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

Только прямой и ручной инспекцией всего кода, а не компилятором.

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

Так и в обсуждаемом случае полной верификации нет. Просто отсутствие IO.

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

Наверное, потому, что ооп не обещает того, чего не может выполнить на практике. Где вы видели, что бы ООП-программист заявлял, что "знает что вернет функция" ? Нет такого. Хоть в смысле операционной, хоть в смысле денатационной семантики. Процессор - физическое устройство, у него могут быть не предусмотренные программой состояния. Ввод пользователя из внешнего мира также не предсказуем. Реальная большая программа из практических соображений должна быть модульной. Никогда один модуль не сможет гарантировать, что другой будет вести себя всегда детерминировано. Иначе это был бы детерминированный конечный автомат. Но проблема в том, что все программы к ним не сводятся. Как правило есть не детерминеированное поведение.

Вы часто видели, чтобы в тредах об ООП на «инкапсуляция помогает скрывать данные и реализацию» кто-то всерьёз отвечал «нет! компилятор можно пропатчить, чтобы он игнорировал private! можно сделать #define private public!», и это воспринималось в условиях дискуссии как валидное возражение?

Вы не поняли что я написал. Я лишь указал, что в любой современной программе есть внешние зависимости, которые вы, как автор своей программы, не контролируете или контролируете слабо.

Компилятор эти свойства проверяет транзитивно. И в safe haskell вы не можете импортировать нечистые библиотеки как чистые.

Ну т е произвольную Си-библиотеку вообще нельзя объявить как чистую, верно ? Если верно, то Хаскель - это игрушечная система сама в себе. А если не верно - то определение "чистая" ли библиотека лежит на программисте, который ее подключает. А он этого знать не может, т к не он ее автор.

Только прямой и ручной инспекцией всего кода, а не компилятором.

Да, никто с этим не спорит

Просто отсутствие IO.

И с тем, что это не плохо никто не спорит, просто вы пишите об этом, как о некой гарантии. А это не так.

Наверное, потому, что ооп не обещает того, чего не может выполнить на практике. Где вы видели, что бы ООП-программист заявлял, что "знает что вернет функция" ?

Я видел, как ООП-программисты заявляли, что ООП обещает «инкапсуляцию» (про что, собственно, и был мой исходный пример, а вы подменяете тезис). Дальше возвращаемся к моему исходному вопросу: насколько адекватным будет ответ на это про то, что можно пропатчить компилятор для игнорирования private?

Хоть в смысле операционной, хоть в смысле денатационной семантики.

Так можно всё-таки узнать, что имелось в виду под вашим «Нет, не знаете! Вы знаете о том, что она не возвращает во внешний мир результатов об этом.» ?

Но проблема в том, что все программы к ним не сводятся. Как правило есть не детерминеированное поведение.

Ну и отлично — у вас получается живущая в IO и работающая с внешним миром маленькая часть где-то в окрестности main, и большая, чистая, тестируемая бизнес-логика с контролем эффектов и прочими радостями. Чему это противоречит?

Вы не поняли что я написал. Я лишь указал, что в любой современной программе есть внешние зависимости, которые вы, как автор своей программы, не контролируете или контролируете слабо.

Не, сорян, это не «вы не поняли», это «я сейчас переобуваюсь в прыжке». Вы прямо написали, что можно пропатчить компилятор (по-видимому, чтобы он не проверял типы как надо). Это совсем не то же самое, что «внешние зависимости [вроде библиотек в привычных мне языках] могут делать произвольную ерунду».

Ну т е произвольную Си-библиотеку вообще нельзя объявить как чистую, верно ? Если верно, то Хаскель - это игрушечная система сама в себе.

Почему? Используете её как нечистую, в чём проблема?

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

Да, никто с этим не спорит

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

И с тем, что это не плохо никто не спорит, просто вы пишите об этом, как о некой гарантии. А это не так.

И это не так… почему, опять же?

насколько адекватным будет ответ на это про то, что можно пропатчить компилятор для игнорирования private

Ровно таким же адекватным, если человек начнет заявлять что "я всегда уверен, что делает чужой код "

Так можно всё-таки узнать, что имелось в виду

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

Ну и отлично — у вас получается живущая в IO и работающая с внешним миром маленькая часть где-то в окрестности main, и большая, чистая, тестируемая бизнес-логика с контролем эффектов и прочими радостями

В том и проблема, что так не получается. Даже если бы каким то чудом все писали на хаскеле, то на уровне железа нет различия. Для статической линковки это было бы полбеды. Но что делать с динамическим кодом ? Сделать его весь IO ? Так это половина кода будет - все подгружаемые dll или so библиотеки. Прежде чем мне сюда писать, попробуйте ответить на вопрос, почему за 30+ лет подход хаскеля с IO монадой не получил распрастранения не только в классических императивных языках, но и в подавляющем большинстве тех, которые считаются функциональными.

 Используете её как нечистую, в чём проблема?

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

Я об этом и говорю - мир в себе. Вам не надо, другим может быть надо

Нет, не тоже. Это как говорить, что автомобиль ничем не лучше велосипеда, потому что на велосипеде тоже можно доехать от Калининграда до Камчатки.

Не надо передергивать. Я сказал ровно то, что без анализа кода хоть на хаскеле, хоть на ооп, нельзя гарантировать как он работает. (а может там unsafe внутри ? Есть в хаскеле unsafe ? А может там библиотека подключена как не IO, а она на самом деле IO...) Хорошо, допустим конкретно с IO на хаскеле проще чем в ооп-языке Икс, и что это доказывает ? По мне так только то, что создатель конкретного ООП языка плохо продумал этот момент

Тут вы можете сказать, что это в подавляющем большинстве ООП - языков так. Да, я соглашусь. Но замечу, что если за столько лет никто не сделал лучше, то далеко не это основная проблема и ошибки в ПО в 99% случаев не про "у нас наша же функция делает какую-то неведомую фигню, а должна просто возвращать foo-bar-buzz". Иными словами IO монада решает мало значимую проблему. Именно IO монада, т к другие монады вполне используются в не функциональных языках. Хорошо, вы можете утверждать, что если у вас функция не возвращает IO то с 99% вероятностью она и не делает IO. Но анализ кода это не отменяет, т к один процент остается. Ну допустим он будет проще, чем для ООП-программы. Но субъективно я в этом сомневаюсь, по причине того, что такую "полезную" вещь уже давно бы внедрили повсеместно, как например анонимные функции или монаду MayBe. Про "простой" код хаскеля я уже в другом комментарии писал...

 почему, опять же?

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

Ровно таким же адекватным, если человек начнет заявлять что "я всегда уверен, что делает чужой код "

А если человек начнёт заявлять «ООП даёт гарантии инкапсуляции»? Можете дать прямой ответ хотя бы с третьей попытки?

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

Я уже прямо ответил вам, что чужие библиотеки тоже проверяются компилятором, транзитивно.

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

Для статической линковки это было бы полбеды. Но что делать с динамическим кодом ?

Единственный проект в моей практике, где нужна была динамическая линковка и прочие dll — один мой личный хобби-проект (причём один из нескольких десятков).

HFT — всё в статике, никаких dll.
Мировое финансово-новостное агенство — статика, никаких dll.
Блокчейн ­— статика, никаких dll.

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

Прежде чем мне сюда писать, попробуйте ответить на вопрос, почему за 30+ лет подход хаскеля с IO монадой не получил распрастранения не только в классических императивных языках, но и в подавляющем большинстве тех, которые считаются функциональными.

Новые функциональные языки чистые. Идрис чистый. Агда чистая. Lean чистый.

Я об этом и говорю - мир в себе.

Ну да, тот же блокчейн — это ведь образец мира в себе, ага.

Вам не надо, другим может быть надо

Это совсем не то же самое утверждение, что «если внешняя библиотека должна жить в IO при safe-опциях сборки, то язык игрушечный».

Я сказал ровно то, что без анализа кода хоть на хаскеле, хоть на ооп, нельзя гарантировать как он работает.

И вы ошиблись.

(а может там unsafe внутри ? Есть в хаскеле unsafe ? А может там библиотека подключена как не IO, а она на самом деле IO...)

В safe haskell, на который я уже сослался дважды, не может быть никакого unsafe. Компилятор это тоже проверяет.

Но замечу, что если за столько лет никто не сделал лучше, то далеко не это основная проблема и ошибки в ПО в 99% случаев не про "у нас наша же функция делает какую-то неведомую фигню, а должна просто возвращать foo-bar-buzz". Иными словами IO монада решает мало значимую проблему.

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

В моей практике большинство нетривиальных багов в императивных языках — из-за темпоральной связности между разными кусками кода. «Чтобы метод Foo::Bar() делал что нужно. надо обязательно сначала вызвать Foo::Baz(), а Foo::Quux() не вызывать». На хаскеле добиться такой костыльности почему-то сложнее.

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

Покажете примеры монад ST, STM, и любого из многочисленных парсеров на комбинаторах? Особенно что они там вполне используются.

Хорошо, вы можете утверждать, что если у вас функция не возвращает IO то с 99% вероятностью она и не делает IO. Но анализ кода это не отменяет, т к один процент остается.

В safe haskell — со 100% вероятностью.

В прочем коде, если вы работаете не со злоумышленниками, достаточно того, что в коде начнёт торчать какое-нибудь unsafePerformIO, вы спросите, зачем оно нужно, и уберёте его.

Но субъективно я в этом сомневаюсь, по причине того, что такую "полезную" вещь уже давно бы внедрили повсеместно, как например анонимные функции или монаду MayBe.

Сделать maybe сильно проще, чем сделать IO (и чистые функции вообще), потому что maybe не требует изменений самой семантики языка.

Не может быть никакой 100% гарантии.

Это следствие какой-то математической теоремы? Или всё же того, что вы в очередной раз проигнорируете отсылки к инструментам, которые дают такие гарантии?

по поводу:

Я видел, как ООП-программисты заявляли, что ООП обещает «инкапсуляцию» (про что, собственно, и был мой исходный пример, а вы подменяете тезис)

и поскольку дальше вы написали:

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

возможно вам будет интересно следующее:

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

Но гарантированная инкапсуляция все же возможна! Если вы вызываете функции классов из ДЛЛ только через шаренные интерфейсы, не зная типа класса у которого вы вызываете эти функции. То есть у вас нет доступа не то чтобы к данным (состоянию) этого объекта класса, вы не знаете есть ли там класс (или какая-то конструкция из классов, например) с какими то защищенными полями и методами! И вы даже не знаете каким образом в ДЛЛ-ке создаются эти классы! Такая абсолютная инкапсуляция достигается не заклинаниями, а совокупностью нескольких технических решений, совершенно, кстати обоснованных фактически с математической доскональностью. Для примера можно попробовать сделать какую-нибудь DirectX DLL-ку, то есть не вызывать интерфейсы, а именно сделать реализацию интерфейсов, возможно вы взглянете на мир другими глазами.

Только такая абсолютная инкапсуляция позволяет обновлять код из ДЛЛ не как не затрагивая и не требуя перекомпиляции кода который пользуется известных функций объектов неизвестного типа из ДЛЛ-ки. Это действительно очень закрытое мастерство.

ну в хаскелле тоже можно отрефакторить так, что оно не будет работать %)

начиная с самого простого foo _ = undefined

благо, полнота по Тьюрингу разрешает писать на хаскелле программы, не достигающие останова (путём выжирания памяти, времени, или просто крешащиеся явным способом).

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

Но вот скажите, кто-нибудь использует идрис в продакшене? Или прототипирует на идрисе, а потом перетаскивает кусочки в хаскелл? Мне просто интересно...

Если у меня в хаскеле написано foo :: Int → String, то я знаю (при некоторых дополнительных не важных для принципа вводных), что эта функция не общается с внешним миром, не отправляет запрос к БД, не читает и не пишет в глобальные переменные, и не отправляет мой ~/.bitcoin/wallet на сервер злоумышленника

Полистал по диагонали некоторые мануалы по Haskell, насколько я понял, для того, чтобы функция "отправляла ~/.bitcoin/wallet на сервер злоумышленника" необходимо написать что-то типа такого: bar :: IO () -> Int соответственно, если написать foo(bar()), то foo таки отправит неизвестно что неизвестно куда?

Нет. Нужно, чтобы bar возвращала IO-действие, а не принимала его.

Более того, от одного взгляда на тип bar понятно, что своим аргументом она пользоваться не может (кроме как через unsafePerformIO и подобные, но safe haskell это проверяет и запрещает). Значит, bar эквивалентна простому значению Int.

Нужно, чтобы bar возвращала IO-действие, а не принимала его.

Да, это я понял, чтобы в функции делать IO оно должно быть явно прописано в объявлении. Не смог понять как это правильно записать, синтаксис вида baz :: Int -> Int -> Int -> Int это прям отдельное спасибо создателям, такой вывих мозга и пальцев надо ещё умудрится придумать.
В общем, напишем bar так, чтобы она выполняла IO и возвращала Int, насколько я понял это вполне возможно, после чего делаем foo(bar()).

Да, это я понял, чтобы в функции делать IO оно должно быть явно прописано в объявлении. Не смог понять как это правильно записать

Потому что на самом деле функция не «делает IO». Функция возвращает описание IO-действия, которое при своей интерпретации рантайм-системой хаскеля вернёт значение такого-то типа (или закончится экзепшоном, но не будем вскрывать эту тему). На практике «возвращает IO-действие с результатом типа Int» иногда сокращают до «делает IO и возвращает Int», но это абьюз терминологии, который, видимо, в данном случае путает.

Поэтому для того, что вы имеете в виду, нужно, чтобы у bar был тип SomeInput → IO Int, и поэтому это:

В общем, напишем bar так, чтобы она выполняла IO и возвращала Int, насколько я понял это вполне возможно

на самом деле невозможно. bar будет возвращать IO Int.

Но для того, чтобы выполнить это IO, которое вернула bar, нужно, чтобы выполняющий код тоже жил в IO(другой, более любимый лично мной вариант абьюза терминологии — «функция живёт в монаде M» означает, что она возвращает что-то, завёрнуте в M). foo в IO не живёт и поэтому выполнить то IO-действие, описание которого ей передала bar, не может. Он может его только проигнорировать.

Скрытый текст

Технически никто никогда не выполняет IO, кроме рантайм-системы хаскеля, которая просто берёт ваш main и его интерпретирует. Принимающие IO и возвращающие IO функции, вроде гипотетической twice :: IO a → IO (a, a) с понятной по имени и типу семантикой, правильнее считать берущими одно описание набора действий с IO-эффектами и возвращающими какое-то новое описание на базе того, что они приняли, но это не так важно для дискуссии.

синтаксис вида baz :: Int -> Int -> Int -> Int это прям отдельное спасибо создателям, такой вывих мозга и пальцев надо ещё умудрится придумать.

С каррированием и частичным применением хорошо работает, а так как семантики «сделали IO, вернули просто Int» нет, то, думаю, ваше возражение к синтаксису снимется.

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

Как уже говорил, прочитал некоторое количество статей на хабре, типа этой, в которой обещались рассказать что такое ФП, так ничего и не понял, так что путает пока что очень многое.

на самом деле невозможно. bar будет возвращать IO Int
Технически никто никогда не выполняет IO, кроме рантайм-системы хаскеля, которая просто берёт ваш main и его интерпретирует

Странно, но ладно.

bar :: IO Int

foo :: Int -> String

main = do
    test <- bar()
    foo(test)

Теперь вроде должно быть корректно.
Т.е., выражаясь максимально примитивно, чтобы "отправить ~/.bitcoin/wallet на сервер злоумышленника" в описании функции должно присутствовать IO?

Да. Функции, которые не возвращают IO - чистые, то есть просто преобразовывают данные. Буквально определение чистоты - что вызов функции можно эквивалентно заменить сразу на возвращаемое значение.

Да. Функции, которые не возвращают IO - чистые, то есть просто преобразовывают данные.

Ок, понятно.
Тогда, если в какой-нибудь условный С++ добавить модификатор к функции [[gnu::pure]] std::string foo(int) который будет недопускать ввода-вывода (не суть важно, как именно, просто представим, что может) и вызовов из данной функции других функций без такого модификатора (как сейчас это делается для того же const), то какие ещё различия между ФП и императивными формами foo(bar(arg)) останутся?

который будет недопускать ввода-вывода (не суть важно, как именно, просто представим, что может)

Тогда не стоит его называть [[gnu::pure]] - этот атрибут не про гарантии от компилятора для программиста*, а наоборот - программист обещает компилятору, что функция чистая:

In short, you shouldn't be saying it's pure or const if it's not pure or const
...
if the user is marking trivial functions const or pure when they clearly aren't, they deserve what they get :).
...
attributes are meant for when you are sure you know more than the compiler can tell - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=18487

Нормальные люди переименовали бы его в [[gnu::assume_pure]]...

____

* что-то такое было в D: https://dlang.org/spec/function.html#pure-functions

Тогда не стоит его называть [[gnu::pure]]

Пусть будет __pure__ в данном обсуждении это не принципиально.

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

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

Ну вот теперь хоть что-то стало понятно хотя бы на примитивном уровне.
Спасибо всем, кто принимал участие в объяснении.

Действительно стало что-то понятно.

По моему, под типом функции имеется ввиду тип контекста в котором выполняется функция (может выполняться) причем существует некий нулевой (пустой) контекст - контекст чистых функций, а есть например IO-контекст. То есть когда у функции есть контекст - и она с ним может работать функция не является чистой.

Под типом функции имеется в виду то, что написано (или что выводит компилятор) после :: в её объявлении, не более. Типы «контекстов» там тоже указываются.

 То есть когда у функции есть контекст - и она с ним может работать функция не является чистой.

Эти "контексты" носят название монад. Монада - тип данных с двумя операциями: первая - завернуть произвольную функцию в монаду, так что бы она принимала не сами данные, а монаду. Вторая операция - достать данные из монады. Самый знакомый думаю уже всем программистам монадический тип - это Optional<SomeType> в java или Nullable<SomeType> в C#. Но т к в этих языках туго с поддержкой монад, оно там не полноценное - нет стандартного механизма как из обычныой функции InputType -> Optional<OutputType> сделать автоматически такую, котрая принимает Optional<InputType> вместо просто InputType.

(Вернее механизм то есть, но так никто не пишет. Возможно из-за громоздкости. Пример монады для C# есть в викиедии https://ru.wikipedia.org/wiki/Монада_(программирование )

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

Если бы вы мне еще объяснили зачем и почему так сложно надо писать:

public class MonadApp {
	public static void main(String[] args) {
		Maybe<Integer> x = new Maybe<>(5);
		Monad<Integer> y = x
				.bind(v -> new Maybe<>(v + 1))
				.bind(v -> new Maybe<>(v * 2));
		System.out.println( ((Maybe<Integer>)y).getVal() );
	}
}

я же правильно понимаю мы здесь просто вычисляем (5+1)*2 ?

В чем смысл применения монад здесь, что они гарантируют, от чего защищают? Когда/при каких условиях (понятно что это примитивная демонстрация) будут работать эти гарантии эта защита?

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

В чем смысл применения монад здесь, что они гарантируют, от чего защищают?

Здесь — ни в чём.

Но если у вас код чуть сложнее, с функциями

parseInt : String → Maybe Int
divide : Int → Int → Maybe Int
checkedOverflowAdd : Int → Int → Maybe Int

то код

doStuff s1 s2 s3 = do
  n1 ← parseInt s1
  n2 ← parseInt s2
  sum ← checkedOverflowAdd n1 n2
  n3 ← parseInt s3
  divide sum n3

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

doStuff s1 s2 s3 = do
  n1 ← parseInt s1
  n2 ← parseInt s2
  sum ← checkedOverflowAdd n1 n2
  n3 ← parseInt s3
  divide sum n3

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

И вы так и не ответили на вопросы:

В чем смысл применения монад этой записи здесь, что она гарантируют, от чего защищают? Когда/при каких условиях (понятно что это примитивная демонстрация) будут работать эти гарантии эта защита?

Можете написать код на С/С++ который делает то же самое? У меня есть подозрения что если то же самое написать на С/С++ то будет очевидно что смысла в этом нет! Просто потому что на С/С++ смысл написанного становится очевидным, с помощью С/С++ нельзя дурить людям головы занимаясь простым переименованием и выдавая это за продвинутую математику.

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

Я надеялся, что имён и типов вам будет достаточно.

Здесь парсится три строки в числа (что может завершиться неудачей), числа из первых двух строк складываются с проверкой на переполнение (что тоже может завершиться неудачей), и потом делятся на третье (что тоже может завершиться неудачей, потому что на ноль делить нельзя). Не совсем в описанном порядке, но попробуйте почитать код, а не только придумать, как написать ответ максимально не по существу.

В чем смысл применения монад этой записи здесь

Сокращают количество кода, который нужно написать для проверок успеха предыдущих шагов. Здесь этого кода вообще нет, собсна.

что она гарантируют, от чего защищают?

Гарантирует тотальность и защищает от того, что проверки забыли.

Когда/при каких условиях (понятно что это примитивная демонстрация) будут работать эти гарантии эта защита?

Эээ, всегда?

Можете написать код на С/С++ который делает то же самое?

std::optional<int> parseInt(std::string_view);
std::optional<int> checkedAdd(int, int);
std::optional<int> divide(int, int);

std::optional<int> doStuff(std::string_view s1, std::string_view s2, std::string_view s3)
{
  const auto mn1 = parseInt(s1);
  if (!mn1) {
    return {};
  }
  const auto n1 = *mn1;

  const auto mn2 = parseInt(s2);
  if (!mn2) {
    return {};
  }
  const auto n2 = *mn2;

  const auto msum = checkedAdd(n1, n2);
  if (!msum) {
    return {};
  }
  const auto sum = *msum;

  const auto mn3 = parseInt(s3);
  if (!mn3) {
    return {};
  }
  const auto n3 = *mn3;

  return divide(sum, n3);
}

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

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

Есть ещё вариант сделать всё через and_then, но это будет выглядеть ещё хуже из-за областей видимости, и вы тогда, о ужас, будете пользоваться монадами даже в C++!

выдавая это за продвинутую математику.

Здесь нет никакой продвинутой математики.

Но это уродливо,

может это уродливо потому что вы объединили парсинг нескольких строк и вычисления в одной функции? Классики говорят что не надо объединять принципиально разные ответственности (операции) в одной функции. Мы конечно не можем оценить необходимость существования такой уродливой функции в коде, который имеет практический смысл, но что-то мне подсказывает что вероятность такой необходимости стремится к нулю.

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

Я вам привёл пример более-менее осмысленного кода, который показывает, зачем нужны монады (и do-нотация). Развлекаться совмещением этого с заветами классиков (часть из которых не написала ни одной строчки продакшен-кода за последние несколько десятков лет) предлагаю вам самостоятельно.

А, кстати, да

Классики говорят

фундаментальные основы, которыми должен руководствоваться программист

Бро снова соскочил на апелляцию к авторитетам, которую он так активно выискивал в словах других людей. Лол.

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

Слушать классиков в моем исполнении это тоже на любителя, конечно :), без претензий! Я просто согласен с вами что функция уродливая, но я склонен искать причины уродливости в другом - на вкус и цвет все фломастеры разные! Извините, что это выглядело как поучения!

Лол.

мне так младший сын иногда отвечает, я, честно говоря, тоже не очень понимаю, что это значит :) ! Но надеюсь когда-нибудь он вспомнит про мои слова :) !

Классики говорят что не надо объединять принципиально разные ответственности (операции) в одной функции

Вы можете хоть на функции отдельные разбить, хоть в либу вынести, это не спасёт от необходимости руками совершать проверки после parseInt и checkedAdd.

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

а если надо строку с ошибкой (номер плохого параметра или какая проблема с арифметикой) вернуть а не null как этот код можно доработать?

Either. В переводе на плюсовый std::expected

На плюсах у меня для этого есть 15+ вариантов, я знаю что на плюсах нет ограничений. А для Хаскеля, насколько я понял, главное преимущество, что там для разработчика шаг вправо, шаг влево - расстрел, разработчик всегда под надзором компилятора!

Поэтому интересно было бы посмотреть, как это расширение кода именно на Хаскеле выглядит.

Если мне просто нужен динамический массив, я воспользуюсь std::vector, а не ::operator new или ещё каким-нибудь из 15 вариантов получить память и насоздавать в ней объектов.

А разница между Maybe, Either исключительно невелика. К слову об исключительности, докинул ещё и вариант через исключения:

import Text.Read as Txt
import Control.Monad.Catch as Ex

parseInt :: String -> Maybe Int
parseInt = Txt.readMaybe

divide :: Int -> Int -> Maybe Int
divide _ 0 = Nothing
divide a b = Just (div a b)

checkedOverflowAdd :: Int -> Int -> Maybe Int
checkedOverflowAdd a b | a > maxBound - b = Nothing
                       | otherwise = Just (a + b)

doStuff :: String -> String -> String -> Maybe Int
doStuff s1 s2 s3 = do
  n1 <- parseInt s1
  n2 <- parseInt s2
  sum <- checkedOverflowAdd n1 n2
  n3 <- parseInt s3
  divide sum n3


parseInt2 :: String -> Either String Int
parseInt2 = Txt.readEither

divide2 :: Int -> Int -> Either String Int
divide2 _ 0 = Left "division by zero"
divide2 a b = Right (div a b)

checkedOverflowAdd2 :: Int -> Int -> Either String Int
checkedOverflowAdd2 a b | a > maxBound - b = Left "addition would overflow"
                        | otherwise = Right (a + b)

doStuff2 :: String -> String -> String -> Either String Int
doStuff2 s1 s2 s3 = do
  n1 <- parseInt2 s1
  n2 <- parseInt2 s2
  sum <- checkedOverflowAdd2 n1 n2
  n3 <- parseInt2 s3
  divide2 sum n3


doStuff3 :: String -> String -> String ->  Int
doStuff3 s1 s2 s3 = (x + y) `div` z
  where x = read s1
        y = read s2
        z = read s3

main :: IO ()
main = do
  putStrLn $ show $ doStuff "234" "565" "0"
  putStrLn $ show $ doStuff2 "234" "565" "0"
  Ex.catch (putStrLn $ show $ doStuff3 "234" "565" "0") exHdl
  where
    exHdl :: exc -> IO ()
    exHdl ex = putStrLn $ "Exception: " ++ show ex

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

Но как говорит один мой коллега: "Ты же программист, а это тоже программа!" :) .

я тут догадался спросить, перевести (догадайтесь кого :) то же самое с Хаскеля на С#, мне перевели с использованием try-catch но самое интересное мне сказали что

  • В C# нет встроенной монады do, поэтому используем обычный синтаксис

и теперь я понял что там на Джаве было как Monad<Integer> понял в чем смысл! В каком-то смысле все благодаря вам и нашей дискуссии! Спасибо за конструктивную дискуссию!

Не так чтобы мне это сильно было нужно, но оно достаточно или даже очень интересно!

Вторая операция - достать данные из монады.

Нет такой операции.

Если вы программист, то у вас есть две операции — pure : a → m a и >>= : m a → (a → m b) → m b. Первая засовывает чистые данные в монаду, а вторая делает то, что вы описали — берёт монадическое значение и дёргает с ним функцию, которая может возвращать другое монадическое значение.

«Достающая данные из монады» функция вроде extract : m a → a (или → [a], неважно) не существует в общем случае. Чтобы понять, почему, попробуйте написать тотальную функцию extract : Maybe a → a.

А если вы математик, то монада — это эндофунктор T : 𝒜 → 𝒜 с двумя естественными преобразованиями η : 1_𝒜 ⇒ T (которое примерно эквивалентно pure), и μ : T² ⇒ T (которое примерно эквивалентно join : m (m a) → m a, который есть и у программистов, просто менее известен, и через который можно выразить >>=). Ничего про вытаскивание данных (гипотетическое ε : T ⇒ 1_𝒜) тут тоже нет.

Нет такой операции.

Ну раз нет, то результатом второй операции вы как воспользуетесь ?

(Что мне толку от m a, или m b, если по какой то причине мне нужен чистый тип без монады ? То что её нет у математиков, да и фиг с ними! Но в практике она есть. Даже в хаскеле - сопоставление по шаблону как минимум.)

Ну раз нет, то результатом второй операции вы как воспользуетесь ?

Зависит от конкретной монады. Иногда — никак. Например, результатом IO я никак не пользуюсь, я только вызываю IO из main.

Даже в хаскеле - сопоставление по шаблону как минимум.

Это уже перестаёт быть операцией «достань данные из монады». Ну и да, сопоставьте по шаблону в safe haskell значение типа IO Int. ИлиParser a, где newtype Parser a = String → Maybe (String, a) с очевидной монадической семантикой.

Теперь вроде должно быть корректно.

Почти. foo всё ещё не живёт в IO, поэтому вы не можете так писать (ну и bar скобки не нужны). Вы можете написать

main = do
  someInt ← bar
  let fooResult = foo someInt
  -- нужен ещё какой-то IO-экшн,
  -- иначе толку с foo нет,
  -- и её вызов вырежется компилятором
  print fooResult

Но тут снова foo ничего не делает, не читает с диска, не отправляет на сервер, и так далее. Все возможные эффекты спрятаны в bar, которая ровно поэтому и помечена как возвращающая IO что-то

в описании функции должно присутствовать IO?

Функция должна иметь тип формы ... → IO SomeType

В описании функции meh :: IO Int → String формально присутствует IO, но она ничего никуда не отправляет (и, опять же, физически математически не может использовать свой аргумент).

Я на С/С++/С# пишу по большей части. Мне вот знаете что интересно с точки зрения типов: в принципе декларация типа структуры и декларация функции в Си-ххх это список типов, структура это список типов полей, функция это список типов аргументов + тип результата.

В чистом Си целая, (как назвать?), техника есть когда структура принимает указатель на свой тип - такая рекурсия типов получается. В ядре Линукс все на этом построено.

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

Я на хаскеле не писал. Но интересно было бы посмотреть как там компилятор выводит типы функций, я ни разу не видел.

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

Если вам прямо именно вывод типов, то, в зависимости от теоретической подготовки и желания инвестировать время — возьмите любую вводную статью из гугла по Hindley Milner type inference, либо откройте википедию по этой же теме, либо почитайте какую-нибудь современную обзорную статью по bidirectional typing, например.

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

А, ну для этого любого онлайн-репла достаточно. Я за вас погуглил «haskell online repl» и нашёл второй ссылкой https://tryhaskell.org/, чтобы типы заодно выводились.

Но лучше, конечно, поставить что-нибудь локально, например, через ghcup.

Надеюсь я ошибаюсь про мертвую теорию.

Лол.

ибо откройте википедию по этой же теме

я все всегда начинаю читать сначала, даже если мне предлагают начать с середины, а в начале там очень интересно написано:

As a type inference method, Hindley–Milner is able to deduce the types of variables, expressions and functions from programs written in an entirely untyped style.

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

Получается это про язык для НЕ-программистов которые сами не способны определять типы. Очень интересно!

Троллинг тупостью

Вы с этим сталкиваетесь чаще, чем думаете. Практически любой современный компилятор языка со строгой типизацией использует алгоритм Hindley–Milner  или его части. К примеру C# (буквально первое, что попалось):

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);

Тип входного параметра и тип результата в лямбда выражении выводится компилятором. Для этого в нем должно быть заложено, что:

  1. Тип входного параметра нужно взять из переменной, которая биндится к входу этой функции (в данном случае х)

  2. операция "умножить" применима к целым и в этом случае имеет сигнатуру int -> int (т е умножить - на самом деле имеет полиморфный тип T -> T где T - супертип "число"

  3. Тип выходного параметра соответствует типу возвращаемого выражения

И т д и т п. По сути это система правил, которые применяются к типам.

Отличие в том, какую информацию о функции даёт её тип, и, соответственно, что нельзя написать ...

то есть все функции в стиле ФП это просто совершенство по вашему? Или что вы хотите сказать этим вашим "нельзя написать", что это гарантирует?

Только учтите, что если это совершенство, то непонятно здесь:

Соответственно, рефакторить типизированный код куда проще и одно удовольствие.

что вы собрались рефакторить! Рефакторить совершенство как-то не логично.

Вы не путаете тип функции и тип результата, который возвращает функция?

то есть все функции в стиле ФП это просто совершенство по вашему?

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

Или что вы хотите сказать этим вашим "нельзя написать", что это гарантирует?

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

Вы не путаете тип функции и тип результата, который возвращает функция?

Нет, не путаю. Тип результата — это составляющая типа функции. Когда я смотрю на тип функции, то я в том числе смотрю на тип её результата (который, к слову, может зависеть от типов или даже значений её параметров).

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

Да пожалуйста! Только начнем вот с этого:

Про это написан следующий после процитированной вами фразы абзац. ...

Что ж вы постеснялись его скопировать? Вот он:

Если у меня в хаскеле написано foo :: Int → String, то я знаю (при некоторых дополнительных не важных для принципа вводных), что эта функция не общается с внешним миром, не отправляет запрос к БД, не читает и не пишет в глобальные переменные, и не отправляет мой ~/.bitcoin/wallet на сервер злоумышленника. Если у меня в плюсах написано std::string foo(int), то эта функция может делать всё что угодно.

Я вижу здесь утверждение близкое к тому что на плюсах функция может делать не понятно что, а в хаскеле функции делают исключительно то что нужно. И отсюда я делаю вывод что вы считаете что на хаскеле функции сами собой получаются ЛУЧШЕ чем на плюсах.

Да! Я маленько утрировал написав про совершенство, но, по моему, именно что маленько, потому что любая идея об исключительности (даже языка программирования!) в конечном итоге ведет к идее исключительного превосходства, на сколько я знаю.

Кстати ваше заявление в виде "то я знаю" тоже тянет на претензию в превосходстве! Вы объявили себя авторитетом которому НУЖНО верить на слово. Вроде как если уж ВЫ об этом знаете, это не подлежит сомнению.

Что ж вы постеснялись его скопировать?

Потому что мне подобные копирования кажутся пассивной агрессией, а я предпочитаю активную.

Я вижу здесь утверждение близкое к тому что на плюсах функция может делать не понятно что, а в хаскеле функции делают исключительно то что нужно.

Нет, не «исключительно» что нужно. Но у вас сильно больше средств их ограничить.

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

И отсюда я делаю вывод что вы считаете что на хаскеле функции сами собой получаются ЛУЧШЕ чем на плюсах.

«Лучше» — это ещё не «совершенство». Но да, как-то так выходит, что функции действительно получаются «лучше». Язык стимулирует их разбивать на чистую и нечистую части, DI проще делать (в конце концов, DI — это просто монада), и так далее.

Кстати ваше заявление в виде "то я знаю" тоже тянет на претензию в превосходстве! Вы объявили себя авторитетом которому НУЖНО верить на слово. Вроде как если уж ВЫ об этом знаете, это не подлежит сомнению.

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

Вы правда считаете, что «я» в упомянутой вами фразе — это конкретный я-который-IUIUIUIUIUIU, а не я-который-любой-чувак-который-в-данный-момент-читает-код? И вы продолжаете так считать даже после соседней дискуссии, где мы с другим человеком обсуждали, почему любой читающий этот код человек узнает то же самое?

«Лучше» — это ещё не «совершенство». Но да, как-то так выходит, что функции действительно получаются «лучше».

Может дело не в языке, а в том кто его использует? У кого-то лучше получается на плюсах, у кого то на хаскеле, на Джаве, на Тайп-скрипте, ... ?

Я надеюсь это не обидное предположение, тем более вам же наплевать на мои аргументы, вы считаете мои (только мои? или те с которыми невозможно спорить?) аргументы аргументами соломенного пугала :) .

Я привязался не к "любой запятой", а к той запятой на которой, как мне показалось, мне будет проще всего и нагляднее всего продемонстрировать тенденцию, тем более вы тоже повторно сослались на эту запятую, то есть она как бы не совсем любая и для вас...

Может дело не в языке, а в том кто его использует?

Конечно, другой вопрос, что это человеческий фактор.
foo DQ ? и long foo; по сути одно и то же, однако MOV [foo], 0.33 вполне допустимо, а foo = 0.33; уже нет.
"Лучше" ли использование long, чем DQ? Наверное, зависит от того, какие задачи нужно решать, но то, что это уменьшает человеческий фактор для целого ряда задач - факт.

Может дело не в языке, а в том кто его использует? У кого-то лучше получается на плюсах, у кого то на хаскеле, на Джаве, на Тайп-скрипте, ... ?

Мой опыт споров в интернете показывает, что с подобными утверждениями спорить совершенно бессмысленно. На любой мой тезис в духе «на плюсах я профессионально пишу 20 лет, и там и сейчас получается всё хуже, чем получалось на хаскеле с 3-5 годами опыта, и у всех моих плюсатых коллег тоже всё хуже, чем у пишущих на хаскеле, и наблюдаемое мной распределение качества кода сильно не в пользу плюсов» мой собеседник всегда может ответить «skill issue, git gud вы все просто ниасилили» (что даже если arguendo так, то естественный вывод о сравнительном качестве языков от собеседника всё равно почему-то ускользает).

Мне интереснее другое (мне вообще интереснее, как у людей мозги работают, чем конкретные результаты работы этих мозгов — метауровень всегда веселее). Рассматриваете ли вы возможность, что питон/хаскель/жс в практике вас и вашего ближайшего социума медленнее плюсов не потому, что языки объективно тормознутые, а потому, что, ну, дело в том, «кто их использует»?

Собсна, сама дискуссия рядом (где вы ассемблер приплели и на которую сослались в следующем абзаце) показывает, что нет, всерьёз вы такую возможность не рассматриваете. Поэтому мой настоящий вопрос: почему? В чём для вас разница?

вы считаете мои (только мои? или те с которыми невозможно спорить?) аргументы аргументами соломенного пугала :)

Это общепринятый термин для метода спора ведения дискуссии, когда вместо ответа на аргументы собеседника («хаскель оказался быстрее плюсов») вы строите удобный вам аргумент («хаскель оказался быстрее ассемблера, в который он скомпилировался»), вкладываете его в уста собеседника, и потом спорите/высмеиваете/етц именно его, и потому, что выдуманный вами аргумент проще оспорить, или он смешнее, или что-то такое, хотя никакого отношения к исходному он не имеет.

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

вы строите удобный вам аргумент

вы наверно не поверите, но я в плюсах писал целые функции на ассемблере, плюсы позволяют писать на Ассемблере, поэтому я знаю о чем говорю. Библиотеку с этими функциями потом (после 3 месяцев тестирования) купила одна оченьизвестная контора, возможно они до сих пор где то работают.

Очень жаль, что вы выбрали напрочь проигнорировать основной вопрос моего комментария, и так как он составлял 69% моего текста по объёму, то вы точно сделали это осознанно.

вы наверно не поверите, но я в плюсах писал целые функции на ассемблере, плюсы позволяют писать на Ассемблере, поэтому я знаю о чем говорю

«В плюсах можно делать ассемблерные вставки ⇒ сравнение с кодом на плюсах можно подменять на сравнение с ассемблером, в который компилируется код на плюсах или хаскеле». Топовая логика, ничего не скажешь.

Кстати, на хаскеле тоже можно делать ассемблерные вставки и писать на ассемблере целые функции.

Библиотеку с этими функциями потом (после 3 месяцев тестирования) купила одна оченьизвестная контора, возможно они до сих пор где то работают.

Серьёзное достижение для профессионального программиста.

Ваши попытки придать вес вашим словам через подобные никак не связанные с темой обсуждения отсылки вместе с вашей постоянной апелляцией к «а вы авторитет? а вы пытаетесь выдать себя за авторитет? а почему вы так написали, как будто, если очень напрячься, можно подумать, что вы выдаёте себя за авторитета?» складываются в забавную картину.

Рассматриваете ли вы возможность, что питон/хаскель/жс в практике вас и вашего ближайшего социума медленнее плюсов не потому, что языки объективно тормознутые, а потому, что, ну, дело в том, «кто их использует»?

Мне зачастую приходится писать прерывания и вызывать аппаратные функции записью в регистры памяти ввода-вывода. Насколько я знаю в этом питон/хаскель/жс помочь мне никак не могут (и я не скажу что к сожалению! и даже проверять не вижу смысла!). Поэтому мне и в голову не приходит с ними сравнивать С/С++. Вот тут на днях пришлось разбираться с драйвером EMMC карт в ядре Линукса и, соответственно, с протоколом на шину к ним и с целевым стандартом и с контроллером шины в процессоре... и все-таки решил проблему связанную, как оказалось, просто, с отсутствием информации по ограничениям возможностей его конфигурации на нашем конкретном типе процессоров. Как вы считаете, могут мне или кому-то еще, как-то помочь ваши волшебные концепции "самого лучшего" языка чтобы понять как работает драйвер ЕММС карты в ядре линукса? ... Тут вот, кстати, английский надо знать, потому что такие стандарты на русском не существуют. И, я думаю, вы маленько заговорились и согласитесь с тем что любой язык сам по себе ничего не доказывает. У языка главная функция выражать мысли (логику) - одним словом выразительность. Если вы оставите в языке одни глаголы, такой язык, конечно, можно использовать для каких-то математических экспериментов, но не более, мне кажется.

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

Мне кажется вы судите меня по себе и это действительно забавно. Разве я вам запрещал делиться вашими достижениями, которые вас привели к тому пониманию которое вы пропогандируете?! Я с удовольствием почитаю и про ваши практические достижения... , ну или хотя бы про текущие практические задачи к решению которых вы приближаетесь. Ужасно интересно на чем основана ваша убежденность. Вы же понимаете что ссылки на умные теоретические статьи ничего не доказывают с точки зрения практики?

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

Лучшее — враг хорошего, если вы понимаете, о чём я. Если не понимаете, то можно иначе: ради бога, пусть хаскель, идрис, агда и пр. не доказывают того, что вы вообразили, что вам сказали, что они должны доказывать, но хорошо уже и то, что вот это

int double_arg(int x)
{
    int res = 2 * x;
    send_nudes_to_bosses_wife();
    annihilate_all_puppies();
    write_fuck_you_to_stdout();
    do_rm_rf();
    return x;
}

невозможно сделать в функции double_arg :: Int -> Int.

И в обратную сторону: даже если мне нужно вызвать annihilate_all_puppies, то в текущем уютном 20+ летнем плюсовом проекте с цикломатической сложностью, местами, не удивлюсь, уходящей за сотню, я далеко не всегда способен обеспечить корректность такого вызова (т.е., как вы предлагаете мне убеждаться, что этот вызов будет безобидным только на 95 из 100 возможных путей выполнения, которые могут к нему привести). А вот хаскель бы сказал, среди прочего, что "Could not deduce `PuppyAnnihilator a' arising from a use of annihilate_all_puppies" для чего-то там выше по графу вызовов.

«Лучше» — это ещё не «совершенство».

Не все пределы сходятся, да?

Более того, не все пределы должны сходиться, и не для всех пределов их сходимость важна на практике.

Слева bro, справа не bro. Just enough. Если код читается вертикально...

А если горизонтально?

Я придумал новый язык! Там код читается по диагонали, это новое слово в программировании, я обещаю... через несколько лет... ну может через пару десятков лет, увидите, все перейдут на диагональное программирование!

все перейдут на диагональное программирование

К сожалению, вы опоздали. Диагональный функтор Δ = x ↦ (x, x) : 𝒞 → 𝒞 × 𝒞 на самом деле роляет в программировании и вылезает что в языках с субструктурными системами типов (раст и боров чекер или linear haskell, где он соответствует процедуре удваивания ресурсов), что в семантике обычных языков (копирование данных), что в формализациях некоторых конструкций вроде параллельных вычислений.

Попробуйте программирование змейкой.

это когда port это port(семантика Тарского) и является высшей структурой по логике, которая может пользоваться структурами ниже себя или в которые она входит(или которые в нее входят), по этой логике, тогда можно еще такой язык сделать по типу ML, где компилятор решает задачку как сделать так чтоб тип был любым, соотв в своём порядке(при трансляции) мог пользоваться тем что задал пользователь(разработчик) из того что имеется в библиотеке, но всё это просто связанные сущности безтиповые (подкапотом) и выведенные к 1 типу пользовательскому

Если не охота (или просто страшновато) влезать в теорию категорий и прочие теоретические изыски, то на русском языке имеется вполне ничего себе информация, например: https://www.piter.com/collection/seriya-grokaem/product/grokaem-funktsionalnoe-myshlenie

Если же хочется познакомиться с теорией категорий, но сразу же не рехнуться от обилия терминов и понятий, то есть https://dmkpress.com/catalog/estestvennye-nauki/978-5-93700-313-3/

Короче говоря - ищите и обрящете )

Понимание теорката для ФП нужно настолько же, насколько понимание теории вокруг машины Тьюринга, умение доказать существование универсальной МТ и неразрешимость, и всё такое, нужны для вашего среднего императивного программирования.

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

Никто же не объясняет каррирование через декартово замкнутые категории, power objects, экспоненциирование и сопряжение функторов B × — и (—)^B? Не объясняет, конечно. Так и с функторами и монадами это всё не нужно.

Функторы и монады — это не штука из теорката. Функторы и монады — это просто такие интерфейсы вроде «ассоциативный контейнер». Функтор — это штука, которая умеет map с определёнными законами. Если ваш тип умеет map , удовлетворяющий этим законам, то он функтор. Если ваш тип умеет bimap с похожими законами, то он бифунктор (как пара из двух элементов, например). Из бифунктора и подходящего объекта можно сделать функтор. Из двух функторов можно сделать бифунктор. Всякое такое вот.

В подвале этой длинной, несмешной попытки популяризации сложного материала автором ничегошеньки в нем не понимающим вижу две плашки:
задонатьте
avitotech
Браво)

У ФП ограничения :

Скорость (мы ограничены временем),

Память (лента не бесконечная).

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

ФП сразу берет все время необходимое на вычисления, и всю промежуточную память сразу, и не выбросится в середине, оно будет считать вообще всё.

Ну где-то нужно и красиво, а много где нет.

Вот сравните сколько задач на литкоде с динамическим программированием можно решить задачу сразу функционально все состояния и связки состояний, а сколько другим подходом , через создание матрицы dp (или строки-двух) и её обхода . Причем даже эта огромная матрица (иногда 4-5-мерная) будет меньше чем количество состояний, которые нужно посчитать используя ФП

А чем создание матрицы состояний не является функциональным подходом? На хабре в своё время была статья про расстояние Левенштейна на хаскеле, и там достаточно оптимальный код на плюсах работал, скажем, секунду, а достаточно идиоматичный код на хаскеле — 1.3 секунды. У вас (императивный же) питон или js будут работать в разы, если не на порядки дольше.

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

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

И наверно быстрее Ассемблера, на котором все это в конечном итоге выполняется! Да-да-да, охотно верим.

В теории у нас три миллиона долларов, а на практике ... непонятно что.

И наверно быстрее Ассемблера, на котором все это в конечном итоге выполняется! Да-да-да, охотно верим.

Вам стоит номинироваться на медаль «самый краткий strawman argument».

В теории у нас три миллиона долларов, а на практике ... непонятно что.

В тех статьях описывались фантазии? Или что вы пытаетесь сказать?

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

Забавно, что вы даже не видели код, но уже вполне готовы его обсуждать.

Подобные вещи пишутся примерно так же быстро, как на императивных языках. И читаются они тоже примерно так же быстро. Тут каких-то хитрых вещей с доказательствами и не возникает, потому что речь, в конце концов, только о реализации, а не о доказательстве её корректности.

Забавно, что вы даже не видели код, но уже вполне готовы его обсуждать.

Ну почему не видел то ? Я видел код на хаскеле. И он на мой взгляд абсолютно не читаем (разумеется, кроме книжных примеров, да и то не всех) Но раз вы хотите поговорить именно о примере с расстоянием Левенштейна, то можете сюда запостить код, что бы и другие могли прочитать. Соберем мнение не только мое.

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

Что бы далеко не ходить, давайте возьмем первый, самый простой вариант из статьи:

import qualified Data.ByteString as BS
import qualified Data.Vector.Unboxed as V
import Data.List

levenshteinDistance :: BS.ByteString -> BS.ByteString -> Int
levenshteinDistance s1 s2 = foldl' outer (V.generate (n + 1) id) [0 .. m - 1] V.! n
  where
    m = BS.length s1
    n = BS.length s2

    outer v0 i = V.constructN (n + 1) ctr
      where
        s1char = s1 `BS.index` i
        ctr v1 | V.length v1 == 0 = i + 1
        ctr v1 = min (substCost + substCostBase) $ 1 + min delCost insCost
          where
            j = V.length v1
            delCost = v0 V.! j
            insCost = v1 V.! (j - 1)
            substCostBase = v0 V.! (j - 1)
            substCost = if s1char == s2 `BS.index` (j - 1) then 0 else 1

И такой же "наивный", но на Си шарпе:

static int LevenshteinDistance(string text1, int len1, string text2, int len2)
{
    if (len1 == 0)
    {
        return len2;
    }

    if (len2 == 0)
    {
        return len1;
    }
    
    var substitutionCost = 0;
    if(text1[len1 - 1] != text2[len2 - 1])
    {
        substitutionCost = 1;
    }

    var deletion = LevenshteinDistance(text1, len1 - 1, text2, len2) + 1;
    var insertion = LevenshteinDistance(text1, len1, text2, len2 - 1) + 1;
    var substitution = LevenshteinDistance(text1, len1 - 1, text2, len2 - 1) + substitutionCost;

    return Minimum(deletion, insertion, substitution);
}

static int LevenshteinDistance(string word1, string word2) =>
        LevenshteinDistance(word1, word1.Length, word2, word2.Length);

Так вот, даже для столь коротких примеров, я утверждаю, что Си шарп вариант лучше читается. Даже не смотря на то, что здесь логика кода плюс минус одинакова. Мои доводы:

  1. В хаскель-коде чрезмерное использование символов. К примеру, вот это V.! что за конструкция ? Как мне вообще это узнать ? (хорошо сейчас есть ИИ который подскажет. Но все эти подвыперды нужно при чтении кода держать в голове)

  2. "Универсальный синтаксис" - да, хаскель сделали математики, для математиков - прочитал доказательство один раз в жизни, разобрал и забыл про него всё, кроме того что оно верное. А в программировании не так - всё это каждый раз нужно перечитывать и ломать голову. Вот это s1char == s2 'BS.index' (j - 1) что за конструкция? Это эквивалент простого взятия элемента по индексу в нормальных языках ? Зачем ? Чем хуже s2[j-1] ? И я еще должен угадывать порядок вычисления, что s2 'BS.index' (j - 1) должно быть вычислено до == (Да, я понимаю, что в хаскель вообще такое понятие как порядок вычисления слабо применимо, т к в реальности это напоминает свертку, а не императивное вычисление. Так тем хуже - я должен весь процесс держать в уме...)

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

  4. Конструкции типа foldl' outer (V.generate (n + 1) id) [0 .. m - 1] V.! n Насколько я понимаю, тут опять таки "магия символов"... foldl' - не ленивая левая свертка, [0 .. m - 1] - генератор диапазона, по которому свертка пойдет... Не смотря на все эти не нужные знания, я не могу до конца понять и представить как этот код будет работать и в каком порядке пойдут вычисления. Я должен это именно разбирать, как при разборе математического доказательства. Не хочу. Мне не нужно доказательство корректности этого кода. В 99% случаев мне просто нужно понять что он делает и в хаскель это не возможно понять быстро.

  5. Конструкции типа min (substCost + substCostBase) $ 1 + min delCost insCost c долларом... Опять инверсная логика - то что стоит справа будет вычислено и передано в функцию слева. Очень напоминает мне польскую нотацию в языке Форт, который так и умер вместе с этой нотацией... Зачем ? Почему не использовать скобки ? Опять же - идиоматичным считается код с $...

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

И это только в маленьком кусочке кода... (Кстати, без монад и других функциональных вывертов. Разве что foldl', но это ещё относительно просто). Можете сказать, что это субъективные доводы. Я соглашусь. Не мне одному решать, что более читаемо. Однако же, пока что хаскель не в топ-10 по популярности. И я склонен думать, что в том числе и по причине такого дизайна языка, который требует чтения математических доказательств каждый раз...

И такой же "наивный", но на Си шарпе:

Не понял, а табличка с мемоизацией у вас где?

В хаскель-коде чрезмерное использование символов. К примеру, вот это V.! что за конструкция ? Как мне вообще это узнать ?

text1[len-1] — что за конструкция? Как мне вообще это узнать?

Чем хуже s2[j-1] ?

Тем, что встроенного оператора индексирования нет, а делать расширение языка для чего-то подобного никому неинтересно (хотя синтаксического сахара в ghc более чем достаточно, и его добавляют весьма охотно). Видимо, не является проблемой для людей в массе своей.

И я еще должен угадывать порядок вычисления, что s2 'BS.index' (j - 1) должно быть вычислено до ==

А когда вы в C# пишете foo == callFun(bar), вы ничего не угадываете, боженька знания свыше даёт?

Инверсная логика построения программы сверху вниз.

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

Потому что, в конце концов, это логично: в идеале вы сначала описываете, что должно происходить на самом высоком уровне, без деталей. Детали — они дальше и глубже по тексту.

Вы даже по-русски так говорите: «мы создаём массив, потом заполняем его так-то, потом пробегаем по нему функцией, которая, кстати, определяется сяк-то». Вы не говорите «о, вот определение функции, не спрашивайте зачем, а вот ещё одно. а вот мы создаём массив. а вот теперь мы вызываем первую функцию (вернись и перечитай с учётом контекста)».

Тексты, учебники, доказательства теорем, что угодно, выигрывают в читабельности, когда в каждый момент времени вы понимаете, нахрена вы читаете данную конкретную строчку. А так как дара предвиденья у вас нет, то это значит, что эта строчка была упомянута выше (и вы его либо разбираете подробно, либо скипаете, если она вам с учётом контекста выше сейчас неинтересна).

Конкретно в приведенном примере вложенные конструкции where.

А вы возьмите «Реализация с мутабельностью», например — там один вложенный where.

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

Нет, не должны. Единственное, что вы должны знать — что приоритет у вызова функций («у пробела») больше, чем у операторов, поэтому этот код парсится как

(foldl' outer (V.generate (n + 1) id) [0 .. m - 1]) V.! n

То есть, сначала генерируем массив «0, 1, 2, 3, ..., n». Потом проходимся по всем 0, 1, 2, ... m - 1 с функцией-свёрткой outer, начальное значение аккумулятора которой — тот массив (который и выполняет роль мемоизирующей таблички). Потом достаём из результирующей строчки значение n.

Но очень удобно, конечно, написать код без мемоизации, а потом «ой, чё-т с мемоизацией выглядит слооожна».

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

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

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

Моя неироничная гипотеза — потому, что хаскель требует делает хорошо сначала, а это довольно плохо работает с херак-херак-дривен девелопмент.

Вот сравните сколько задач на литкоде с динамическим программированием можно решить задачу сразу функционально все состояния и связки состояний, а сколько другим подходом , через создание матрицы dp (или строки-двух) и её обхода .

Не очень понимаю в чём проблема. Решение задачи на динамическое программирование с использованием матрицы более-менее прямолинейно переводится в решение через рекурсию с мемоизацией. А если у функционального языка ещё и ленивый рантайм, то даже мемоизацию самому писать не нужно (линк).

Школьники, которых с 1-го класса учили программированию на Haskell, оказались неспособны объяснить свою мысль без теории категорий

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

А можно попросить то же, но человеческим языком? Понимаю, что проще дипсик спросить "растолкй мне (понятие)", но этак полхабра можно заменить таким манером (а вторую половину в виде творчества "редакторов" просто выкинуть).

Когда я начал изучать программирование вообще и С++ в частности - у меня через пару лет начались проблемы с выражением мыслей на человеческом языке, чего раньше не бывало :(

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

Разговаривают два 1с-ника:

— Как у тебя дела?

— Ну, если тебе интересно, конец если, ...

Оно, наверное, так и будет. Врачи, кстати, тоже на подобное жалуются.

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

Когда я лезу в хаб "ненормальное программирование (извращения с кодом)", я жду извращений с кодом! А не извращений с человеческим языком и ноль кодирования, просто вылили ведро воды.

Не надо так, пожалуйста?

самое классное сделано у Ocaml( пару раз писал на нём - смотрел ) если о том пошло по фп OCaml тоесть это ML (в вики написано - Языки данного семейства в большинстве своём не являются чистыми функциональными языками )тут и математикой вроде можно докопаться )

тут вроде и порядки, и логика типа, и что есть что вроде, как я понимаю всё это красиво дополняется математикой и логикой и философией

Уникальный материал. Во время введения в ФП автор на практическом примере напоминает и основы теории вероятностей!

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

Браво, маэстро!

Такое ощущение, что автор просто выгрузил свой minmap в статью на Хабр. Какая-то несвязанная мешанина из математических концепций, эволюций кода, HTML/css и Аристотеля с Тьюрингом.

Аналогии про CS1.6 и "морских существ, которые, соединившись, считают себя графом"(что?) путают ещё больше.

В статьях про базовые концепции хотелось бы видеть последовательный рассказ простым языком. Здесь получилось ну очень замудрёно.

Хорошо скомпонованная статья. Я думаю, надо было подчеркнуть, что ФП - как подход, гораздо более надежен из-за того, что он лучше, как бы, "математически подкреплен". В разработке программ всегда стоит острый вопрос - а как доказать, что моя программа работает корректно? Вот, все, что я использую в программе, все это корректно? Многие разработчики об этом даже не задумываются, полагая, что какие-то практки и юнит тестирование это доказывают. Это так, но только от части. В промышленной сфере этого, за частую, достаточно. Доказать, что твой промышленный код, корректно работает математически - это, на мой дилетанский взгляд, неимоверный труд. Но только он, в итоге, гарантирует истину. Это надо хорошо понимать. Потому что все остальное - это допущение.

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

Sign up to leave a comment.