Pull to refresh

Comments 370

Забавно, когда Haskell называют "более современным", чем Python или Java, хотя Haskell и Python появились одновременно, а Java — на 5 лет позже них.

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

Можно оспорить факт современности haskell, но дата появления не аргумент.
современный
современный: относящийся к настоящему времени, соответствующий уровню, требованиям настоящего времени.

>Haskell называют «более современным», чем Python или Java
То есть, Haskell более соответствует уровню и требованиям настоящего времени? Мне кажется что доказать это в такой постановке будет еще сложнее.

А что не так? Рыбу сайта магазина я за пару часов сделал, сваггер есть, постгрес драйвер есть, веб-фреймворк вообще один из лучших (всяко лучше спринга, asp.net mvc core, express и прочих). Всё для типовой разработки имеется.

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

>постгрес драйвер есть
Ну, мне как-то нужно было написать быстро-быстро скрипт, который бы что-то доставал из базы MS SQL. Взял первое что подвернулось, python. Взял первый драйвер для MS SQL. Быстро-быстро выяснил, что varchar длиннее 100 символов обрезаются до 100, плюнул, написал на груви. Ну то есть, меня python в этом случае не устраивал, по моим меркам API для работы с СУБД не работал. Но формально — драйвер MS SQL тоже есть. То есть, в практике есть много других критериев, которым продукт может не удовлетворять.
Про реальную разработку это скорее Scala. Вот где уж библиотеки на все что движется и не движется. Летает плавает и вообще. За счет Java конечно. Хотя в целом свои либы есть у Scala и они мне очень нравятся. http4s, fs2, doobie, tapir прям рекомендую. Они прекрасны по моему. Ну и ScalaTest, ScalaCheck, ScalaMock — либы для тестирования тоже по моему конфетки. В целом можно писать настоящий тырпрайс код что собственно и делают 100+ Scala программистов в Тинькоф.

Уверяю, что Persistent не занимается молчаливой модификацией данных, это вообще не в стиле ФП языков.


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

>Уверяю, что Persistent не занимается молчаливой модификацией данных,
Да я и не сомневался. Я немножечко про другое — чем «тырпрайз разработка» в общем отличается от академических проектов, или проектов для себя? Тем что завтра может заявиться к вам условный безопасник (или заказчик), и сказать: «Делайте, что хотите, но чтобы через полчаса в лесу было светло, сухо и медведь!». Ну то есть, потребности тырпрайза широки и многообразны, вероятно сильно шире, чем у типового проекта ради фана. И вопрос пригодности тут стоит немного в другой плоскости. В том числе — в плоскости наличия на рынке выбора хороших разработчиков.

Я занимаюсь типичной тырпрайз разработкой, но у нас никакой безопасник или заказчик не придет и не скажет. Наверное, просто потому что можно работать вне гос/банковского сектора в продуктовой компании. И конечно же мы делаем проект не ради фана, а классический B2B продукт.


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

>Наверное, просто потому что можно работать вне гос/банковского сектора в продуктовой компании.
Можно конечно — бывают области разработки, где все попроще и погибче. Но и банковский сектор — это таки жирный кусок разработки. И если не безопасник, то регулятор к вам все равно может зайти.

Банковский сектор да, но кроме него полно разработки. Все эти abbyy/jetbrains/… Это из того где мои друзья/коллеги работают, из известного. И сотни более мелких.

UFO just landed and posted this here
>Так мы о современности или сложности?
Мы вот о чем: habr.com/ru/company/ruvds/blog/515684/?reply_to=21987504#comment_21986124 предлагается критерий современности как пригодность для удовлетворения сегодняшних потребностей. Я сразу сказал, что доказать такое (как пожалуй и опровергнуть) будет довольно сложно. В том числе потому, что потребности разные. Как вообще померять это? Понятно что популярность и сложность тут играют роль — но ни на том, ни на другом все не заканчивается.
Вопрос же был про «Haskell более современный чем Python или Java», а не просто «Haskell современный».

Ну в хаскелле есть алгебраические типы данных, причем даже в их расширенной версии GADT, тайпклассы, семейства типов, типы высших порядков, rank-N типы (они конечно редко когда нужны, но иногда все же полезны), и так далее


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


Так что да, хаскель соверменнее.

Тут "современнее" можно говорить если только в роадмэпах других языков есть это. А то так можно сказать, что Хаскель безбожно устарел, ему современные ООП языки лет 10+ догонять )

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

А tutorial / пример кода такой вещи есть? У меня Хаскелл как-то никогда за пределы академии (и уж тем более в веб разработку) не выходил

Ну база у меня выглядит так:


connStr :: ConnectionString
connStr = "host=localhost dbname=operdenstorage user=pguser password=mycoolpass port=5432"

getPersonsInner :: (MonadIO m) => SqlPersist m [User]
getPersonsInner = do
  people <- select $
                from $ \person -> do
                return person
  pure $ fmap entityVal people

runInDb :: IsSqlBackend backend => ReaderT backend (NoLoggingT (ResourceT IO)) a -> IO a
runInDb f = runStderrLoggingT $ withPostgresqlPool connStr 10 $ \pool -> liftIO $ runSqlPersistMPool f pool

getPersons :: IO [User]
getPersons = runInDb getPersonsInner

HTTP хендлеры выглядят как большинство в других языках:


isaac :: User
isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)

albert :: User
albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)

users :: Handler [User]
users = liftIO getPersons

Регистрация в местном фреймворке тоже очевидная:


type UserAPI = "users" :> Get '[JSON] [User]
           :<|> "albert" :> Get '[JSON] User
           :<|> "isaac" :> Get '[JSON] User

userAPI :: Proxy API
userAPI = Proxy

type API = SwaggerSchemaUI "swagger-ui" "swagger.json"
    :<|> UserAPI

server :: Server API
server = swaggerSchemaUIServer swaggerDoc :<|> users :<|> pure albert :<|> pure isaac

app :: Application
app = serve userAPI server

swaggerDoc :: Swagger
swaggerDoc = toSwagger (Proxy :: Proxy UserAPI)
    & info.title       .~ "Operden API"
    & info.version     .~ "1.0.0"
    & info.description ?~ "This is an API that perform some operen actions"

main :: IO ()
main = run 8081 app

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

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

Правда, половину вашего кода все равно не понимаю, но теперь хотя бы понятно, что гуглить =)

Если что, "гуглить" лучше на Hoogle, он поддерживает поиск по именам (в том числе операторным) и по тИповым сигнатурам.

То есть, Haskell более соответствует уровню и требованиям настоящего времени?
Этого мы не утверждали, мы лишь заметили, что современность определяется не так, что бы можно было опровергнуть ее таким аргументом
Haskell и Python появились одновременно, а Java — на 5 лет позже

Так а я не говорил, что утверждали. Я просто заметил, что в вашей формулировке современности доказать ее тоже будет не так просто.

Он появился раньше физически, но по факту это академический язык был на старте, а Java/Python — нет. А то что академия на 15-20 лет опережает мейнстрим вроде все и так знают.


Так что фактически утверждение верно.

"А то что академия на 15-20 лет опережает мейнстрим вроде все и так знают" — ну, это уж как повезёт. Из малораспространённых функциональных языков в мейнстрим действительно перетаскивают кое-что, но вот из Пролога, например (который я когда-то даже знал, но уже все забыл, ибо не пригождалось) — нет, он уникальный в своём роде. Ну, точнее, не то, чтобы прямо уникальный, есть ещё Mercury, например, но все это не мейнстрим.

Ну пролог используется, но не для языков общего назначения, а для, например, тайпчекеров. Потому что логику x :- subtype y очень удобно в таком вот виде записывать.


А языком общего назначения емнип пролог если и позиционировался, то недолго. Его удел вроде всегда были всякие экспертные системы и только.

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

>Суть функционального программирования — это уничтожение побочных эффектов
Агащаз. То есть печатать, обновлять данные в базах, и прочее и прочее наша функциональная программа не будет? Реальная суть ФП скорее в изоляции функциональных эффектов. Уничтожением это называть просто неграмотно.

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

Вот делать разработчикам на работе нечего, как только думать «а давай-ка я щас перейду от объектно-ориентированного к функциональному программированию». Разработчики видят в scala язык, который удобно позволяет пользоваться например средствами Spark, и работать с большими данными (примерно в том же стиле, как с данными обычными). Ну или работать с Akka, допустим. Или даже просто взять ScalaCheck, и заняться property based тестированием. Разработчики видят в ФП инструмент, позволяющий им более удобно решать какие-то свои практические задачи.
UFO just landed and posted this here
Да и не эффектов в общем тоже. Скажем прямо, в scala и тем более Java — не самые сильные системы типов. Однако же, даже такие системы типов дают очень полезные на практике подсказки. Скажем, явно намекая, что получение max от списка дает нам на самом деле не Integer, а Option, потому что непустота списка не может быть доказана вот в этом вот месте, и max для пустого списка неизвестен. А что тут взамен дают динамически типизированные (наверное точнее будет сказать слабо типизированные?) языки? А ничего обычно.
UFO just landed and posted this here
Скажем прямо, в scala и тем более Java — не самые сильные системы типов


В чем их слабость и в сравнении с чем?
В сравнении с тем же хаскелем. И с некоторыми не мейнстримными языками.

Я бы не назвал это слабостью. Среди мейнстримных они вполне на уровне. Просто есть реальные примеры, что система типов вполне может быть более мощной.
UFO just landed and posted this here
Насколько мне известно, лишь немногие немейнстримовые языки могут похвастаться поддержкой зависимых типов. На этом фоне вменять скале как языку своей эпохи это в вину странно. Но да, их не хватает.
>>> max([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: max() arg is an empty sequence

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


Вероятно, с точки зрения сильно типизированных языков, такие приёмы выглядят костылями :)

Ну как бы вы не знаете заранее, будет исключение или нет. А сигнатуры у функции вообще нет, потому что типизация такая. Ну да, в каком-то смысле костыль, тем более что вернуть что-то вместо max вполне можно было бы — Option же в питоне реализуется, пусть и без гарантий на этапе компиляции.
Суть функционального программирования — это уничтожение побочных эффектов
Агащаз. То есть печатать, обновлять данные в базах, и прочее и прочее наша функциональная программа не будет? Реальная суть ФП скорее в изоляции функциональных эффектов. Уничтожением это называть просто неграмотно.

Суть в устранении побочных эффектов, а не любых. Достигается это выносом эффекта в результирующий тип функции, после чего он перестает быть побочным. Так что писать в БД без побочных эффектов — да, это то что делает ФП.

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

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

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


А так да, это эквивалентные формулировки.

Ничуть. Это именно изоляция. Сам побочный эффект никуда не пропадает. И не перестаёт таковым быть. Сохранение значения в БД, строго говоря, не является побочным эффектом. Это примерно то же, что присвоить значение переменной, просто она не принадлежит вашей программе, а существует вовне (грубо). И вот за это обращение вовне вы платите определённую цену: целевая сущность может отсутствовать, может иметь иной тип, может неожиданно пропасть… и вы никак не контролируете это на стороне вызывающего кода. Неопределённость взлетает до небес, но поведение программы не может быть недетерминированным. Приходится либо умножать сущности и вместо простого сохранения/получения информации получать хитрую структуру данных с кодом ошибки и анализировать его, либо бросать/ловить исключения, либо заворачивать всё в монаду и притворяться, что она и есть наша реальность (потому что как только вы попытаетесь её покинуть, побочные эффекты "сыграют" и разнесут всю чистоту на кирпичики).

Побочный эффект — это эффект, который не видно в результате функции. Запись в базу или вывод в консоль — не побочный эффект если тип функции которая это делает IO a. Ну просто потому что проблема побочных эффектов — отсутствие ссылочной прозрачности, которой очевидно не существует для значений типа IO.


Существует ли оно вовне, в программе или ещё где совершенно неважно. Я уже писал, ФП — это про ссылочную прозрачность, а побочные эффекты их рушат. Поэтому про то как сделать прозрачные эффекты и говорят так много. Free/Freer/MTL/eff/fused-effects/polysemy/younameit…


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


Только оказывается, что это ложная дихотомия, и вывод в консоль или запись в БД точно так же может быть чистой и без побочных эффектов.

Позвольте один насущный вопрос: а как сделать, логирование в функциональном ЯП (прогидывать IO(...) в сигнатуру всех ф-ий — я бы не назвал нормальным решением задачи)?
UFO just landed and posted this here
Хм спасибо.

А как вообще будет выглядеть 2 монадических поведения?

Из самого очевидного (я не настоящий Хаскелист, просто действительно не очень понимаю насколько Хаскелл даже для небольших утилит подходит): логирование (чтобы можно было разобраться что случилось) и maybe (чтобы не писать всю логику «не получилось»).
UFO just landed and posted this here
Ничуть. Это именно изоляция. Сам побочный эффект никуда не пропадает. И не перестаёт таковым быть. Сохранение значения в БД, строго говоря, не является побочным эффектом. Это примерно то же, что присвоить значение переменной, просто она не принадлежит вашей программе, а существует вовне (грубо).

Очень интересно, но не понял. А зачем тогда вообще нужны чистые функции? Либо я могу распараллелить foreach на ядра/хосты/кластеры (как если бы всё оставалось чисто), либо после первого же SQL UPDATE сказка кончится и «непобочные эффекты» превратят консистентную до того БД в тыкву?

Или чистые функции нужны для чего-то иного?

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

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

Мне все же больше нравится формулировка, что мы их изолируем (в случае хаскеля при помощи IO), чтобы было четкое разделение на чистые функции, и функции с эффектом. И тогда уже вопросы типа этого будут иметь четкий ответ — чистые функции можно переупорядочивать, параллелить и т.п. с целью оптимизации, а вот эффекты в общем случае переупорядочивать нельзя, потому что в базе сначала INSERT, а уж потом UPDATE или DELETE. И это должно быть видно в коде, возможно в сигнатурах и т.п.
UFO just landed and posted this here
Вот только не ясно где там селф в функции
def something():
    return 4


Впрочем там не только это неверно написано.
Про то что мол если заменить явные циклы на неявные ( через map reduce ) они почему-то должны выполнится быстрее ( магия не иначе ), про то что если модифицировать входной параметр а не глобальную переменную функция станет уже без побочных эффектов ( по идее модификация входных параметров тоже побочный эффект ).
селф не в любой функции, а в функциях-методах классов, для доступа к текущему объекту.
UFO just landed and posted this here

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

Явное лучше неявного. Разве не это заявляется главным плюсом ФП? :)

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

ФПшность как раз в том, что функция принимает ТОЛЬКО аргументы и возвращает результат, при этом ничего не знает о «внешнем мире» и не может на него никак влиять. С такой логикой появляется смысл в «явное лучше неявного». Иначе могут возникнуть вопросы — если функция неявно имеет доступ к чему-то, то не имеет ли она также неявно доступ к ещё чему-то?

bar : (MonadReader Foo m) => Int -> m Int — вот такой же эмбиент контекст как и у оопшного объекта, в ФПшности от этого никак не потеряли.

UFO just landed and posted this here
Конечно создание всегда новых объектов избавляет от неприятных багов в многопоточном программировании. Или даже в JS где один поток, можно намудрить так что объект будет изменен кем-то другим по ссылке, это как бы здорово… если бы не GC который будет забирать у вас 80% CPU тупо на очистку памяти…

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

Я лично использую только для простенькой фильтрации или трансформации небольших объектов когда знаю что их количество ограничено числом N, и это число меня устраивает.
UFO just landed and posted this here

У вас никогда не было задержек в 15-30 секунд когда gc останавливает все что бы очистить память? Задача задаче рознь. Одно дело в воркере что то делать, с этим еще можно жить. А вот в веб сервере, когда это напрочь убивает весь перформанс, уже другое.


Самое простое например у нас, это работа с календарем. 365 дней в году.


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


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

У вас никогда не было задержек в 15-30 секунд когда gc останавливает все что бы очистить память? Задача задаче рознь. Одно дело в воркере что то делать, с этим еще можно жить. А вот в веб сервере, когда это напрочь убивает весь перформанс, уже другое.

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


А у большинства людей с кучей <= 4 гигов проблем вообще никаких.

Ну у вас это жаба… у нас nodejs. Возможных настроек минимум. На пиковых нагрузках GC заметно тормозит когда создается очень много short-lived объектов. Можно конечно расти вертикально пока не упрешься в потолок или бюджет… и все же, просто игнорировать GC и другие накладные расходы в статьях про ФП это как-то не правильно.

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


Можно конечно расти вертикально пока не упрешься в потолок или бюджет… и все же, просто игнорировать GC и другие накладные расходы в статьях про ФП это как-то не правильно.

ФП к эффективности ГЦ слабо относится, одно — язык, а другое — рантайм. Если же сравнивать у существующих языков, то у Haskell/Scala достаточно эффективный ГЦ, гигабайты мусора в секунду по крайней мере они собирают без каких-либо задержек со стороны приложения.

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

Хм. Насколько я помню, вы сейчас общаетесь именно с автором той статьи :)

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


Нашел кстати пост, комменты выше есть если хочется восстановить контекст: https://t.me/rustlang_ru/243901

Пул объектов пробовали использовать?

UFO just landed and posted this here
И сколько запросов в секунду у вас в пике на этот ендпоинт и какова продолжительность этих пиков? У каждого разная нагрузка и поведение клиентов.
UFO just landed and posted this here
Ну вот у меня было (когда в качестве начального этапа освоения языка решил олимпиадные задачки пописать), что построение обычного бинарного дерева на Haskell работало раз в 50 медленнее, чем на С.
Ну и я по времени не укладывался.

ПС
Отдельно хочу заметить, что даже не представляю как в этом случае профилировать программу на Haskell.
UFO just landed and posted this here
Специально зашёл с десктопа и грепнул слово «композиция». Пардон, но вот эта изоляция (а не уничтожение) побочных эффектов в ФП не самоцель, а нужна для облегчения композиции индивидуальных функций и целых кусков кода.
Что бы мы могли в любой момент заменить
value = f(arg)
array = [ value, value ]

на
array = [ f(arg), f(arg) ]

и наоборот.

При этом это только пример, а реально это используется для того, что бы безболезненно развивать существующие системы и, как минимум, адекватно тестировать написанный код.
UFO just landed and posted this here
Что интересно, в ФП при необходимости, как правило, проще рассчитать требования программы к ресурсам. Но да, ФП — это прежде всего про корректность, а не про производительность. Но про стеко- и хипобезопасность надо думать, да (но в императивном программировании об этом, вроде, надо думать ещё больше).
В случае ленивости — не факт что проще.

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

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

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

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


Но написано через одно место, да.

>Потому что итераторы фьюзятся
Это особенности (правильной) реализации. Если тупо взять какую попало реализацию где-нибудь в js, то от замены цикла на map/reduce/filter как раз наоборот производительность может пострадать, и очень сильно.

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

А автор не пишет про это ничего. В чем и разница между вашим комментом и оригинальным постом.

Сия статья опоздала года на 3. Функциональные языки опят вышли из моды

UFO just landed and posted this here
Описание Scala могло бы быть и получше.
Например, можно было бы упомянуть, что Scala это Java + типизация + ФП + Иммутабельность + Акторы (в т.ч. распределенные между хостами) + Spark + компиляция в JS + Scala Native
+ Акторы (в т.ч. распределенные между хостами) + Spark

Это имеет к Scala такое же отношение как Spring к Java, т.е. прикладное и весьма опосредованное

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

Ну как сказать. У нас был обратный пример. Взяли программиста, который наваял программу для контроллера на C++. Не, парень молодец, быстро врубился, написал правильную программу, используя ООП, но… процессорной мощности на нее не хватило, причем радикально. Пришлось ему переписывать без объектов, на чистом C.

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

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

"неизменяемыми переменными" — оксюморон, отличное начало простого объяснения концепции.

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


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

Если принять определение переменной как "именованная область памяти", то нкмзменяемая переменная — это именованная нкмзменяемая область памяти. Вроде норм.

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

Слово "переменная" означает, что она может принимать разные значения. А "неизменяемая" означает, что после принятия значения оно посередине вычислений поменяться уже не может.


Вспомните математику. Рассмотрим функцию f(x) = 2x+1. Можно вычислить f(2), а можно вычислить f(3). В одном случае икс будет равно двум, а во втором — трём, поэтому икс — переменная. Но неизменяемая, изменяемых переменных в математике нету.

Ну я вроде примерно так и написал.
Квалификатор const в С\С++ вам о чём-нибудь говорит?
Как вы его переведёте?
Думаю, что направление развития языков совпадает с направлением развития компьютерных железок.
Вот в процессорах мы уперлись в максимальную тактовую частоту 4ггц. Усовершенствуем в них конвеер, кеш, системную шину, но в целом частота все та же. Зато пределов многоядерности еще не видно.
А с другой стороны память — ее объемы и скорость доступа неуклонно растут и пока предела тоже не видно.
Отсюда выгоднее для всей индустрии писать слабосвязанные куски кода, пусть даже ценой большего расхода памяти — потому что они лучше параллелятся.
Опять всё тот же набор штампов. И все штампы с доказательствами вида: а у нас получается короче. Но «короче» получается только потому, что вместо полной реализации алгоритма «знатоки» показывают нам вызов функций, реализацию которых никогда не объясняют (ссылаясь на «это же все знают», ага).

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

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

Поэтому фанаты ФП — это фанаты стиля. Я понимаю, скажем, математиков — они привыкли к математическому виду своих рассуждений, и тут хаскель с его очень похожим подходом, ну и математиков «попёрло». А все остальные — либо не понимают и просто следуют за авторитетом математики, либо… Ну в общем в обоих случаях — не понимают.

Даже больше — агрессивное отношение фанатов ФП ко всем «не фанатам» губительно для ФП. Но фанатам этого не понять.
фанаты ФП — это фанаты стиля.

Ну не совсем, элементы ФП (неизменяемые структуры, функции без побочных эффектов) очень помогают для многопоточного программирования (например, в Java). Поэтому, как минимум, элементы ФП в ООП/императивном языке — полезны.
элементы ФП в ООП/императивном языке — полезны.

Обсуждалось. Это не «элементы ФП». Это давно известные способы написания процедурных программ.

Если есть желание, вы можете писать без побочных эффектов, если нет желания — можете с побочными. Это называется «свобода». В ФП же свободы нет. Только чистые функции. То же самое касается всего остального. Вплоть до передачи функции в качестве параметра — это тоже ФП заимствовал из императива.

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

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

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


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

Свобода ведёт к хаосу, хаос ведёт к багам

О да, достаточно продекларировать некий набор слов, и доказательство крутости ФП в кармане!

Свобода расширяет возможности. Несвобода сужает. Вроде бы очевидно, но нет, адепты «несвободы» увидели хаос…

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

Далее сравниваем. ФП запрещает нам целый ряд конструкций. Здесь уместно вспомнить JavaScript, который точно так же запрещает нам целый ряд конструкций. И на этом фоне есть императивные языки, поддерживающие то, что не хочет JS и то, что не хочет ФП. То есть возможностей в императиве больше, чем в ФП и в JS. Но да, я понимаю, признавать своё поражение неприятно…

Хотя… ещё раз ладно. Объясню ещё нагляднее (а то-ж не все понимают). Вы что-то там про зависимые типы говорили. Было? Так вот, это тоже есть расширение возможностей. Хочет кто-то их использовать — использует. Не хочет — пишет на подможестве языка а-ля чистый хаскель. Теперь сравниваем с императивом — там тоже если кто-то хочет — пользует чистые функции и всё прочее, а если не хочет — не пользует. И вот приходит фанат ФП и заявляет — свобода ведёт к хаосу! Это таким образом он запрещает нам (сторонникам императива) свободу в выборе способа написания наших программ. Но сразу после этого тот же самый человек заявляет — завтипы наше всё! И получается, что в одной ситуации он против, а в другой, полностью аналогичной первой — он «за».

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

vedenin1980
Вы почти всё правильно поняли. Различие в спорах — только стилистическое. Но ваша картинка, к сожалению, относится именно к фанатам ФП. Точнее — к худшей и небольшой их части, которая владеет рядом умов на этой площадке.

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

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

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

то и ФП — это такое же расширение возможностей по ограничению себя типами побочных эффектов.

Вы опять не поняли сказанного. Уже настораживает.

Объясняю. В ФП нет других вариантов, кроме того, что требует ФП. А в императиве есть. В ФП один подход, а в императиве минимум два. Внимание, детский вопрос — где больше? Один или два? Вы выше заявили, что один больше двух. Поэтому срочно просыпайтесь и не пытайтесь отвечать как всегда — первой пришедшей в голову мыслью. Она у вас всегда ошибочная. Доказано хотя бы данной перепиской.

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

Удар ниже пояса почти :) Ведь вроде как машина Тьюринга и Лямбда-исчисление эквивалентны. То есть "будить" вас нужно, залезая в конкретные детали реализации и "разбудить" без большой практике в ФП не получится ))

Хэшмапу смогли реализовать только когда в хаскель линейные типы затащили. Или какую-нибудь инплейс быструю сортировку.


По сути, все что в ST это такой алгоритм.

UFO just landed and posted this here

Что там от этого ФП остается. По сути эмбед императивщины в монаду же.


А то так остается заключить, что кроме ФП ничего и не существует, просто весь код в императивных языках неявно заключен IO a, который не пишется по тем же причинам, почему никто не пишет T | bottom для каждой нетотальной функции.

UFO just landed and posted this here

Ну лично мне ближе идея к тому, что ФП — это когда код не использует лишних возможностей. То есть функция возведения в квадрат в стд не имеет вид Int -> IO Int.

UFO just landed and posted this here
А как определить, есть возможность отделить или нету? Может проще весь код писать в ST/IO?
UFO just landed and posted this here
И как определить, нужна мутабельность или нет, например из примера выше с hashtable?
UFO just landed and posted this here
Ну вот я написал функцию, которая требует ST. Что дальше? Как выяснить, действительно ли там нужно ST или нет? В противном случае можно просто все в ST и IO реализовать
UFO just landed and posted this here
Ну так можно и в других языках проверять.
UFO just landed and posted this here
Вопрос в том, насколько позволено допустить «проникновение» монад в код? Не получится ли из монад «эффект разбитых окон»?

«А почему этому мальчику можно добавлять монады в код, а мне нельзя?»

Потому что писать с монадами чуть больнее. В ООП если полениться, то можешь сломать архитектуру/абстракцию, в Haskell нужно быть активным придурком. Чтобы сломать, прикладывать усилия нужно.


add :: IO Int -> IO Int -> IO Int
add x y = do 
        a <- x
        b <- y
        return (a + b)

add2 :: Int -> Int -> Int
add2 x y = x + y
add :: IO Int -> IO Int -> IO Int
add x y = do 
        a <- x
        b <- y
        return (a + b)

Столько лишнего кода вместо add = liftA2 (+)

Это ж пример. Для более сложной функции аналог (+) придётся всё равно самому писать — а в таком случае уже непонятно зачем нужна add.

Например, если вы написали функцию, которая отбивает строку слева пробелами, а там нужно IO, то что-то не так.

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

Что за формулировка вопроса! Утрируя можно перефразировать:
— IO не нужно при простой работе со строками.
— Я хочу работу со строками и файлами.
— Для работы с файловой системой нужно IO.
— Хочу файловую систему без IO монады. Я так понимаю, что в рамках типичных функциональных языков эта задача нерешаема?

Можно перефразировать так: Получается в популярных (для ФП) языках нельзя прозрачно для потребителя добавить в функцию нефункциональные сайд-эффекты? Примерно как в JS нельзя прозрачно для потребителя отрефакторить функцию так, чтобы она начала использовать await?

Насколько я знаю, предполагается такой подход для кеширования: делаете свою монаду, которая позволяет не все действия из IO, а только чтение-запись в конкретный кэш; используете её в функциях, которые связаны с кэшем; профит. Да, если до этого был вызов такой же функции, но без кэша, то его надо будет поправить. Однако зато получаете гарантии, что ни в какие другие файлы/сеть/etc эти функции не лазят — только в кэш.

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

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


Я кажется рассказывал историю, как у меня в сишарпе в одной библиотеке безобидная функция для перевода времени из одного часового пояса лезла в БД через статический глобальный коннекшн потому что там хранились информации по таймзонам и оффсетам. А в сигнатуре ничего, безобидный DateTime -> DateTime.


То что добавление кэша ломающее изменение — хорошо и правильно.


Более правильным решением написать функцию относительно любого эффекта который умеет то что нужно. foo :: (Monad m) => m (). И дальше если вам эффект не нужен, то вы просто передаёте пустой эффект Id, а если нужен, то передаете что нужно: кэш или еще там что-то.

foo :: (Monad m) => m ()
Только(если я правильно понимаю о чём речь), в такой функции никаким конкретным эффектом воспользоваться не получится.

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


whileM' : (Monad m, Monad f, Alternative f) => (a -> Bool) -> m a -> m (f a)
whileM' p f = go
    where go = do
            x <- f
            if p x
                then do
                        xs <- go
                        pure (pure x <|> xs)
                else pure empty

whileM : Monad m => (a -> Bool) -> m a -> m (List a)
whileM = whileM'

Который работает для любой монады. Можно проверить для m = Maybe, m = IO, m = Async, ...


Пример использования:


readToBlank : IO (List String)
readToBlank = whileM (/= "") getLine
да, но функция, грубо говоря, будет просто работать с монадическим интерфейсом. Т.е. различным образом соединять монадические комбинаторы, зацикливать вычисления и т.д.
Но вот сегодня не делать ничего, а завтра полезть в файл она не сможет.

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

> Я кажется рассказывал историю, как у меня в сишарпе в одной библиотеке безобидная функция для перевода времени из одного часового пояса лезла в БД через статический глобальный коннекшн

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

Мне не нужен запрет на глобальные переменные, более обще запретить эффекты. Я знаю, что функция вида
Int -> Int


  1. не мутирует стейт
  2. не делает сетевых запросов
  3. не ходит в базу
  4. не пишетт логов
  5. не кэшируется
  6. ...

И я хочу видеть эти опции из сигнатуры.

> И я хочу видеть эти опции из сигнатуры.

Допишем к функции суффикс Pure?

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

Я не верю в соглашения, только в компилятор


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

Вызвать сишный printf?

> Вызвать сишный printf?

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

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

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

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

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

Вот видите. В питоне не мешают и они это делают. В яве мешают и они это делают. А что в хаскеле мешает писать все в ио?

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

Не уверен что им получится воспользоваться с
{# SAFE #}


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

UFO just landed and posted this here
// void врёт

А можно чуть подробнее? Почему врёт? Кому врёт? При компиляции C-кода void функции с return или неявным return будет однозначная ассемблерная конструкция RET без какого-либо возвращаемого значения в стэке, регистре, или где там по очередной конвенции о вызове. (бывают исключения в этих конвенциях). То есть реально ничего не возвращает.


На предыдущем комменте про это удержался, а теперь нет )

Насколько я понимаю, потому что в стандарте написано, что void — ненаселённый тип, а на самом деле это что-то вроде unit.


Хотя я тоже не понимаю что именно делает struct Unit в этом коде.

UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here
У вас прямо все по этой картинке

Картинка...
image


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

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

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

newlist = []
def append_to_list2(x, some_list): some_list.append(x)
append_to_list2(1,newlist)

Я может чего-то не понимаю, но разве тут append_to_list2 — чистая функция?

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

То есть, получается автор статьи сам не понимает, что такое функциональное программирование и чистые функции?
Странно, минусы поставили, но на вопрос чистая ли это функция и функциональное программирование не ответили (это не риторический вопрос, мне действительно интересен ответ).

Если натянуть сову на глобус и считать что append_to_list это функция в монаде то получится чистая.


Но вообще я бы её такой не назвал.

А теперь — ещё один вариант этого кода:
from functools import reduce
integers = [1,2,3,4,5,6]
odd_ints = filter(lambda n: n % 2 == 1, integers)
squared_odds = map(lambda n: n * n, odd_ints)
total = reduce(lambda acc, n: acc + n, squared_odds)

Это — полностью функциональный код. Он короче.

integers = [1,2,3,4,5,6]
total = sum(i*i for i in integers if i%2)

Так еще короче. Чем в данном случае такой код хуже?
сама по себе парадигма замены цикла списком — глупость и нишевое решение. Например что делать, если количество итераций — расчетное? Я уж не говорю про то, что объем кода становится пропорционален количеству итераций цикла. Посмотрю, как программист будет набивать список из сотни-другой элементов. А еще можно ожидать глупых багов из-за пропусков в списке или дубликатов. Как их выгребать?
Это всего-лишь пример. В реальной жизни список вероятно не будет константой. Даже если нужен список целых от и до, во многих языках есть средства генерации таких последовательностей, причем без сохранения куда-либо. В Scala, для примера, это будет просто 1 to 6.map...filter...etc

>если количество итераций — расчетное
Это логичный вопрос, но такой цикл не заменяют map-ом.
>Так еще короче. Чем в данном случае такой код хуже?
Ну он возможно будет несколько хуже расширяться (хотя честно говоря, и оригинальный авторский код в этом смысле так себе). map и filter это средства композиции решения из частей, а эта композиция имеет свойство меняться, в том числе при изменении постановки задачи.

То есть, если у вас есть набор функций, f1, f2, f3, вы можете скомпоновать из них обработку как integers.map(f1).map(f2).map(f3), а можете построить композицию функций f123, и даже доказать, что integers.map(f123) будет эквивалентно. А в вашем варианте эти части где? i*i, i%2? Для однострочника норм, а в перспективе может оказаться не очень.

comprehension это и есть короткая запись для filter/map, но если добавить чуть больше шагов (например группировка после фильтрации но до отображения) или любые другие нетривиальные шаги то всё развалится.

«Понравился» раздел Map и Reduce
что делать, если количество итераций цикла не 6, а 1000 или 10000? И мне вот интересно, как выгребать ошибки в составлении списка «integers».
что делать, если количество итераций цикла не 6, а 1000 или 10000? И мне вот интересно, как выгребать ошибки в составлении списка «integers».

например вот так:


integers = range(1, 10000)

Примеры [1,2,3,4,5,6] составляют для наглядности, это не всегда скопированный из прода код.

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

Ну вот так например (RxPy):


count = events.pipe(ops.count())
count = events.pipe(ops.reduce(lambda acc, event: acc + 1, 0))
Это не значит, что Java — плохой язык. Но он не создан для решения тех задач, для решения которых отлично подходит функциональное программирование. Например — для управления базами данных или для разработки приложений из сферы машинного обучения.

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


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

Функция вставки записи в таблицу, примет на вход таблицу и запись и вернёт новую таблицу :) Или даже примет базу и вернёт новую базу

Про Java и БД.
Что-то мне подсказывает, что автор статьи записал в функциональные языки ещё и SQL.
Участовать в споре функциональный ли язык SQL или нет, я не собираюсь, но, вообще-то, нечто общее у SQL с ФП есть — и уж, по-любому, в SQL реализована никак не императивная парадигма программирования.
А то, что SQL в качестве языка доступа к данным в БД (DML) сильно лучше императивных языков — это, думаю, ощутил на своей шкуре всякий достаточно опытный программист, которому приходилось в качестве DML где-нибудь в 1-й половине 90-х помучаться с чем-нибудь из серии Clipper/FoxPro/Paradox для написания запросов на получение данных из нескольких таблиц, да ещё и с фильтрами по значениям полей — тем, что в SQL делается в одну строчку.
А то, что SQL в качестве языка доступа к данным в БД (DML) сильно лучше императивных языков — это, думаю, ощутил на своей шкуре всякий достаточно опытный программист

Всегда? Ок, простая разминка для ума, у вас есть таблица с значениями id, parent_id описывающие дерево, силами чистого SQL найдите самую длинную ветку от корня.

То что не все так просто с SQL говорит тот факт, что почти любая база реализует какой-нибудь PL-SQL или Transact-SQL, которые уже явно не чисто функциональные.

Плюс, вы просто всякие hibernat'ы не встречали, существует множество библиотек, позволяющих из импреативной программы делать запросы ничуть не хуже чем SQL, например просто превращать иерархию классов в таблицы базы данных (у них свои проблемы с производительностью, но вот кое какие вещи на чистом SQL писать намного тяжелее).
Не всегда. Конечно, с помощью SQL нельзя сделать всё. Но очень многое можно сделать куда проще, чем расписывая вручную выполнение запроса циклом(ами) в императивном языке.
За стандартный SQL не скажу (стандартов не читал давно), но вот в Transact-SQL для MS SQL Server рекурсивное получение данных из таблицы с id и parent_id, хранящей дерево (или даже целый лес), в виде набора записей, содержащих и вычисленную длину от корня дерева, вполне возможно и в чисто декларативном стиле — через использование Common Table Expresions (CTE) и UNION ALL. Как-то, типа (синтаксическую проверку не делал) так:
WITH Nodes(parent_id, id, node_level) AS (
  SELECT parent_id, id, 0 as node_level FROM tree_table WHERE parent_id IS NULL
  UNION ALL
    SELECT t.parent_id, t.id, node_level+1 FROM tree_table t
    INNER JOIN Nodes n ON t.parent_id = n.id
)
SELECT * FROM Nodes

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

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

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

На самом деле, SQL, на мой взгляд, далеко не самый удобный и простой способ работы с БД, SQL больше похож на ассемблер или javascript — нативный, быстрый (по сравнению с обертками над ним), но многословный и тяжелый для написани и использования. Отладка и написание огромных простынь SQL запросов — боль, а попытка запихнуть вcю бизнес логику в какой-нибудь PL-SQL — создает монстров.
В результате вы сами себе противоречите — с одной стороны, что любой программист должен считать SQL самым лучшим языком для работы с БД, с другой внезапно большинство программистов внезапно вместо SQL используют совсем другие средства в качестве обертки над SQL

Противоречия нет — есть нюанс: не только лишь все программисты умеют в SQL.
Конечно, сферический программист в вакууме, в совершенстве знающий все языки, чаще (значительно чаще IMHO) будет использовать SQL для доступа к данным в базе. Но вот реальный программист, которого удалось нанять на проект за разумные(на самом деле — не совсем, потому что «всем нужен программист») деньги, от SQL с немалой вероятностью окажется далек — чисто потому, что SQL не похож на его основной рабочий язык. И вот потому применение всяких средств уклонения от написания кода на SQL — от ORM до конструкторов запросов (которые в GIU мышкой) оказывается более чем оправданным.
Но у этой опраданности есть и другая, тёмная сторона. В качестве примера её расскажу историю. Я как-то (в довольно давние уже времена, впрочем) был привлечен к выяснению вопроса, почему так тормозит свежеразработанный местными веб-программистами сайт. Недолгое колдунство с SQL Profiler однажды вечером, когда число запросов на сайт было далеко от пика, но общее торможение оставалось, позволило найти дивные SQL-запросы, выполнявшиеся по 15 секунд — причем это были не отчеты и не поиск по БД, а обычная генерация страниц квазистатического контента. Тем не менее, планировщику СУБД от этих запросов конкретно плохело — и не зря: просмотр текста запроса — крайне сложного и запутанного — ввел меня в глубокую задумчивость, как такое можно вообще написать? Вопрос этот (с примерами) я резонно переадресовал команде веб-программистов — на что получил честный ответ типа «А мы сами не знаем, оно автоматически создается» (потом они, правда, зная, где именно творится безобразие, все же что-то с ним сделали). Короче, думаю, мораль понятна.
SQL, повторяю, на мой взгляд, сложен для привыкших к императивному программированию (коих среди программистов большинство) своей непривычностью — и уж никоим образом не многословностью и необходимостью учитывать множество мелочей, как ассемблер. Мне, например, не приходилось писать действительно огромные простыни именно на SQL (а не на каком-нибудь расширении его для создания хранимых процедур): всё самое сложное, что было нужно, укладывалось в несколько (всяко меньше десятка) связанных друг с другом подзапросов. Правда нередко сразу было не очевидно, как их писать, и приходилось писать код не с начала, а буквально с середины. Но в итоге код запроса получался довольно компактным.
А вот в защиту бизнес-логики на хранимых процедурах у меня слов мало найдется. По-моему это — пережиток той уже давней эпохи, когда приложения были чисто клиент-серверные, с «толстым клиентом» (иными словами — «тощим» сервером, который был по сути СУБД), а удобных технологий вынесения бизнес-логики на сервер («трехзвенные приложения») ещё не существовало. Вот тогда, с теми ограничениями, бизнес-логика на PL-SQL и ему подобных была как-то оправдана. Сейчас же оправдание такой архитектуры для новых проектов я найти не могу.
И вот потому применение всяких средств уклонения от написания кода на SQL — от ORM до… оказывается более чем оправданным.

Справедливости ради:


  • ORM — не средство уклонения от написания SQL, одно другое не исключает, самые быстрые ORM у меня были как раз с вручную написанными SQL запросами. ORM — лишь средство представить содержимое реляционной БД, строк в табличках в виде графа объектов и наоборот. Понятно, что без SQL запросов там не обойтись обычно, вопрос лишь в том генерируются SQL код программой из где-то задекларированного в коде/конфиге/аннотациях маппинге или пишется вручную из маппинга в голове программиста.
  • различные QueryBuilder'ы часто являются 1:1 маппингом формально императивного, но псевдодекларативного кода на SQL:
    $users = (new QueryBuilder($connection))->from('users', 'u')->where('u.isActive')->select()->execute->all();
    $users = $connection->execute('SELECT * FROM users AS u WHERE u.isActive = 1')->execute()->all();

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


SQL всяко лучше любого кастомного дсл. Но как я уже замечал, проблема в маппинге этого SQL на типы. В ОРМ весь билдер тащит типчики, а при написании запросов надо не забыть, что на что маппися и как.

ORM — не средство уклонения от написания SQL, одно другое не исключает,

Справедливо. Но ORM может использоваться как средство написания запросов на SQL. Но есть и другие средства. В частности, упомянутый выше 15-секундный запрос был написан отнюдь не ORM, потому что испольуемая там технология была VB+ASP(не .NET ещё).
различные QueryBuilder'ы часто являются 1:1 маппингом формально императивного, но псевдодекларативного кода на SQL

Этот код не «псевдо-» — он декларативный по сути, просто он записан не на SQL. Кстати, с непривычки такой код писать и читать труднее (сужу, естественно, по себе), чем привычные с юности вложенные циклы.

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


main :: IO ()
main = putStrLn "Hello World"

Тем не менее этот main — чистая функция.




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


В си у вас функция которая ничего не делает выглядит как void foo() и функция которая печатает в консоль тоже выглядит как void bar(). В хаскелле же первая будет foo :: () а вторая bar :: IO (). И эта разница принципиальна.


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

Если на пальцах, то чистота означает не то что функция не делате ничего полезного, а то что её резульат действий всегда есть в сигнатуре.

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

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

Именно так.

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

А из сигнатуры будет видно — пойдёт функция в базу или в консоль?

Ну например из моего пет проекта:


getPersonsInner :: (IsSqlBackend backend) => SqlPersist backend [User]
getPersonsInner = do
  people <- select $
            from $ \person -> do
            pure person
  pure $ fmap entityVal people

Собственно SqlPersist backend [User] показывает, что операция будет ходить в базу и вернет список юзеров.

хм, и эта функция тоже считается чистой?


и как чисто по этой аннотации понять, что IsSqlBackend — это какой-то внешний источник и каждый вызов


getPersonsInner backend

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

UFO just landed and posted this here

Хм, действительно, интересный подход!


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

> И да, эта функция о своих намерениях говорит достаточно точно
Как теперь заставить программиста четко выражать свои намерения, а не ставить везде IO?
UFO just landed and posted this here
Ну тоесть никак. Чем тогда это лучше, чем в любом другом языке согласиться в имя функции приписывать суффикс pure?
UFO just landed and posted this here
А наоборот проверить можно? Чем это защитит от программиста, который везде будет пропихивать монады?
UFO just landed and posted this here

Как выше написали, этот внешний источник возвращает данные разные при интерпретации, но функция getPersonsInner возвращает не результат запроса, а описатель этого запроса.


Вот я в качестве интереса набрасывал мою реализацию IO на расте:


https://gist.github.com/Pzixel/3fc17be254f6c6bcaf88711e12bddd2c


Обратите внимание, что get_line или read_line чистый по-определению, например можно сделать:


get_line().flat_map(|x| 
  get_line().flat_map(|y| 
    write_line(concat!(x,y))))

эквивалентно:


let line = get_line();
line.flat_map(|x| 
  line.flat_map(|y| 
    write_line(concat!(x,y))))

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


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


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

Увидел тут недавно F# в стиле
 Foo<TError, TResult>(bla bla, ba bla); //syntax is not true

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

IWorker{
class WorkerResult;
class WorkerError: WorkerResult, Error
WorkerResult Work(WorkerInput Input) => if(Input is null) return new WorkerError("Guy, who programmed this, is {(int)Input} meaning")
}

То есть абсолютно типизированная обработка и входов и выходов функции — рай ООП.

p.s. даже не знаю теперь, фреймворк написать или сразу язык.

Ну это обычное использование Either для ошибок, весь раст на этом построен (там типчик Result называется).


В ООП языках неудобно потому что там или нет достаточно мощного свитча для этого, или он есть, но писать постоянно обработку кейса когда наш WorkerResult это и не WorkerOk и не WorkerError, а что-то другое.

Either и Result это частные случаи sum types. В ООП есть похожие по смыслу Chain of Responsibility и Strategy. Но для большинства случаев встроенных в язык конструкций (if-else и try-catch) хватает выше крыши.

Either и Result — это одно и то же. То что это частный случай sum types — очевидно, как например "синглтон" или "фабрика" это частный случай классов.


А вот как chain of responsibility это расширяет — непонятно. Как и со стратегией — стратегия предполагает открытое множество, собственно, "стратегий", а АДТ — нет.


Если же искать что-то похожее в ООП, то самое близкое это визитор.

Уточнение: визитор тут тоже ни при чём.

Ну визитор это эффективно способ эмулировать АДТ всё же. Каждый VisitFoo это обработка одной ветки матча, все вместе они дают обработку АДТ целиком.

А с точки зрения ФП монады (не IO) считаются чистыми функциями или нет? И насколько вообще стоит предпочитать обычную чистую функцию монаде?
если говорить о haskell, то с точки зрения языка — всё чистое, даже IO. Собственно, IO и нужна как-раз для того что-бы «чистым» образом описать «грязный» ввод-вывод.

Монады можно применять в том числе чтобы делать эффекты чистыми. Само ИО тоже чистое, если взять определение. Оно и было придумано, чтобы "упаковывать" всякую грязь в чистый язык.


Соответственно чистая функция монаде не противопоставляется, поэтому второй вопрос не очень корректный. Если же вопрос, когда стоит писать в монаде а когда нет — аналогичен "когда стоит выносить функционал в интерфейс/базовый класс, а когда — нет". Если так удобнее, то стоит, если нет — то нет.

> когда стоит выносить функционал в интерфейс/базовый класс, а когда — нет

Отличие в том, что в интерфейсах самих по себе ничего плогохо нет. В той же Java, например, интерфейс — это основопологающая концепция. Как правило, нет ничего плохого чтобы создать очередной интерфейс. Да, всегда можно упороться и начать городить «фабрики фабрик», но как правило человека от этого останавливает обычная лень. А что останавливает человека от оборачивания всего и вся в IO? И насколько всеже IO антипаттерн? Может мы неправильно понимаем ФП, и нужно вообще все писать в IO?
А что останавливает человека от оборачивания всего и вся в IO?

Ровно та же "лень", про которую вы пишете, ну и вообще здравый смысл
Вон выше пример писали — функция add в монаде IO, и чистая:


add :: IO Int -> IO Int -> IO Int
add x y = do 
        a <- x
        b <- y
        return (a + b)

add2 :: Int -> Int -> Int
add2 x y = x + y
Для такой простой функции да. Но простые функции можно вычитать и глазами, увидев и так, что никаких побочных эффектов там нет. А вот про функции от 3 строк разница уже не сильно большая.

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

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

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

Если алгоритм требует императивного стиля, но не требует доступа во внешний мир — то для него подойдёт монада ST. В отличии от IO, она может находиться внутри чистой функции, runST в помощь.

Тоесть монадой ST можно пользоваться в любом удобном случае?
UFO just landed and posted this here
UFO just landed and posted this here
А что останавливает человека от оборачивания всего и вся в IO? И насколько всеже IO антипаттерн? Может мы неправильно понимаем ФП, и нужно вообще все писать в IO?

А что останавливает человека от того, чтобы все функции писать как асинхронные, а где асинхронности по факту не надо просто возвращать Task.FromResult()/Promise.resolve/...? Здравый смысл, наверное.

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

Меня интересует, какие препятствия ставит haskell от неправильного применения монад, в том числе IO. Насколько сложнее писать код в монаде IO, чем не в монаде IO?

Примерно настолько же, насколько легко писать асинхронные функции с async/await :dunno:


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

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

Потомучто здравый смысл — а что считать здравым смыслом? Давайте в любом другом языке будем приписывать суффикс pure к чистым функциям. Это будет считаться здравым смыслом?

> Примерно настолько же, насколько легко писать асинхронные функции с async/await

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

Потомучто здравый смысл — а что считать здравым смыслом? Давайте в любом другом языке будем приписывать суффикс pure к чистым функциям. Это будет считаться здравым смыслом?

С точки зрения лени IO — лищние буковки, которые можно не писать.


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

асинк это один из видов монад. Его особенности распространяются на их все, включая ИО. Если где-то внизу коллстека появился эффект, то за редким исключением он будет протекать до самого верха.

> С точки зрения лени IO — лищние буковки, которые можно не писать.

А если эти лишние буковки упрощают написание остального кода? В результате лишних буковок становится много меньше

Поймите, я не хочу писать весь код в IO просто ради хулиганства. Я хочу его писать там просто потому, что мне например проще мыслить в императивном стиле, а не функциональном. Мне проще сходить в сеть когда мне захочется, а не когда мне это позволят. Записать в лог чтото в произвольном месте кода. Что меня остановит?

> асинк это один из видов монад
Мне кажется, обсуждение ушло в другое русло
Мне проще сходить в сеть когда мне захочется, а не когда мне это позволят. Записать в лог чтото в произвольном месте кода. Что меня остановит?

Ничего не остановит от написания всех функций в IO — ровно так же, как от написания всего кода проекта в одном файле или даже одной функции, например. Вы же в условной джаве указываете тип функции не object f(object a, object b), а int f(string a, int b), хотя в принципе можно везде с object'ами работать.

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

А вот написать весь код в одном файле — удобно. Поэтому с этим борются всякими административными методами.
это как? Премии лишают за слишком большие файлы?
Не знаю. Как обычно в компаниях борются с несоответствием корпоративным правилам форматирования кода?
показывают хороший код и плохой. Когда показывают плохой код — бьют током. Так у программиста вырабатывается условный рефлекс, и он больше не пишет плохой код.
А вот написать весь код в одном файле — удобно. Поэтому с этим борются всякими административными методами.

Нет, не удобно и не административными методами с этим борются, по крайней мере преимущественно. Иначе бы все личные pet projects на гитхабе состояли из одного файла — ведь никакой "администрации" там не руководит.

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

Тем не менее и на гитхабе попадаются проекты из одного файла
Тем не менее и на гитхабе попадаются проекты из одного файла

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

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

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

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

"Самодисциплина" не проверяется компилятором: достаточно один раз с недосыпа забыть где-нибудь во внутрнней функции вставить какой-нибудь запрос к сети, и это пойдёт через весь код который её вызывает.


Но есть ли прям какаято киллер-фича, без которой никуда?

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

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

И в каком другом языке компилятор не разрешит из функции
sqr :: Int -> Int вызвать printf?

Поймите, я не хочу писать весь код в IO просто ради хулиганства. Я хочу его писать там просто потому, что мне например проще мыслить в императивном стиле, а не функциональном. Мне проще сходить в сеть когда мне захочется, а не когда мне это позволят. Записать в лог чтото в произвольном месте кода. Что меня остановит?

ИО не позволяет писать в императивном стиле. State позволяет ST позволяет, а ИО — нет.

Я не очень силен в хаскеле, так что пусть так

Так если вы просто хотите внутри функции писать код "в императивном стиле" с изменением локальных переменных, то можно его писать в монаде ST и вызывать runST как тут уже писали. Итоговая функция будет безо всяких монад в сигнатуре: действительно, пользователю не нужно знать как она реализована, если внешне-видимой разницы от этого нет.

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

То есть должен ли я насиловать себя, и стараться писать любой код «чисто» (то бишь без монад в таком понимании), или это позволительно расслабиться и сделать как проще?

Вы видимо исходите из того, что "проще" писать в императивном стиле — хотя множество примеров показывает, что часто это не так.

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

Вы точно читаете предыдущие сообщения? Буквально 10 минут назад я писал про монаду ST и чистые функции. В других цепочках тут тоже про неё упоминали в контексте написания алгоритмов, которые более естественно ложатся на мутабельные переменные.

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

UFO just landed and posted this here
Монада это пара функций над значением некоторого типа, которые должны удовлетворять нескольким общим правилам. Можно про нее думать как про интерфейс. Напрямую сайд-эффекты ни кто не запрещает. Но при их наличии удовлетворить определенным свойствам (скажем такому, что композиция должна быть ассоциативной) скорее всего будет невозможно — сайд эффекты будут выполняться не в том порядке, что сильно сужает область их полезного применения.
newlist = []
def append_to_list2(x, some_list):
    some_list.append(x)
append_to_list2(1,newlist)
append_to_list2(2,newlist)
newlist

Мы избавились от побочных эффектов. И это очень хорошо.

А что разве в данном случае append_to_list2 — не создает побочных эффектов? Она же меняет внешний newlist, который потом выводится?
UFO just landed and posted this here

Тут вопрос не в ФП, а в гц. На каком-нибудь сишарпе наверное лоу-латенси не особо попишешь.

UFO just landed and posted this here

Товарищ из майкрософта который actix писал (на который azure iot перевели) как раз жаловался, что код на шарпах аллоцирует как не в себя даже на IO bound задачах, на затюненном приложении. И уверяю, что если майкрософт не умеет готовить C#, то никто не умеет.

UFO just landed and posted this here

Это он жаловался до появления Pipelines или после?

ну Actix писался в 2017-2018 годах, полагаю сравнение было примерно в этом районе.

Pipelines тоже в 2017-2018 годах делались...

Ну я не думаю что там что-то поменялось. Аллокации на каждый чих (если пользоваться структурами, то о наследовании можно забыть), при том что все стандартные типы типа строк и массивов — исключительно хиповые. Span на стакаллок память работает хорошо только на бумаге, все задачи всегда выполняются на тредпуле, который нафиг для ИО не нужен, и так далее… Это всё кишки CLR и BCL, я не думаю что Pipelines или что-то ещё с этим может что-то сделать.

Но способа выполнять таски не на тредпуле а на current thread например я не знаю. В BCL зашито что оно все в тредпуле, все эти ISyncronizationContext и все остальное прям вопит об этом.


Пул это хорошо, но не всегда удобно и/или возможно.

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

TaskScheduler.FromCurrentSynchronizationContext() (в ContinueWith, в конструкторе TaskFactory) — не оно?
Правда, для этого упомянутый SynchronizationContext должен быть. Впрочем, там где это очень важно (в GUI к примеру, где все обращение к GUI должно быть в выделенном для этого потоке) MS уже изначально об этом позаботилась, а для желающих странного есть возможность реализовать SynchronizationContext самостоятельно.
UFO just landed and posted this here

"этот криворукий идиот" это команда разработчиков из 20 человек в майкрософте. Но конечно, в интернете каждый на 300 IQ умнее любого из них, понимаю.

UFO just landed and posted this here
UFO just landed and posted this here
Попишешь но это будет такой код что поддерживать его да и вообще второй раз в жизни видеть желания у тебя не будет. Хотя и на Rust это все с лайфтаймами делается и с вставками unsafe местами. Няша, лоу латенси на самом деле не то говно с которым хочется иметь дело на постоянной основе. Очень на много компромиссов приходиться идти.
UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here

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

А ООП без классов это просто процедурное программирование. Значит ФП это просто процедурное программирование.
Вывод: то что действительно важно — это классы(если грубо округлить).

Пишу на сишарпе с иерархиями, наследованием и всем остальным в ФП стиле (за исключением монад, но тут уж понятно). ЧЯДНТ?

А классы у вас там есть? Ну это тогда просто ООП получается(если грубо округлить)

P.S. а вывод «ФП это просто процедурное программирование» вас не смущает? Это как сказать чёрное это белое…

Нет, не получается просто ООП. Даже дядюшка Боб уже писал, что ООП и ФП непротиворечат друг другу. И сказать "Да эт опросто ООП" — это свернуть весь спектр до одной точки.

Вся Scala это ООП + ФП. Да и в хаскеле какая нибудь монада State это такой типичный объект. В Эрланге акторы это вообще эталонное ООП. ФП против Мутабельности. ФП против императивности. ФП вместе с ООП идет в комплекте в большинстве ЯП. Тобишь для ФП главное это иммутабельность и изоляция эффектов за счет вынос их в объекты отложенного выполнения рантаймом.
монада State это такой типичный объект
что делает State объектом(с точки зрения ООП)?
Ниже дедфуд и выше психаст уже ответили. Мне просто интересно, почему по вашему монада State это не объект?
Ниже дедфуд и выше психаст уже ответили

Что-то не вижу ответа на мой вопрос — ни ниже, ни выше.
Мне просто интересно, почему по вашему монада State это не объект?
состояние торчит наружу, объекты имеют внутреннее состояние. Разные объекты могут взаимодействовать друг с другом путём отправки сообщений, обрабатывать сообщения. В Java, например, этот механизм редуцирован до (subtype)полиморфных методов. State же подобных механизмов не имеет.
И да, это не значит что ООП на hs нельзя изобразить, вполне себе можно. Только, как и везде(в т.ч. Java), это очень редко нужно.

А какой-нибудь data-bean с геттером-сеттером и без других методов – это уже не объект?

Геттеры и сеттеры выступают в роли сообщений, которые принимает data-bean, так он становится подобным объекту.
Но объект это только формально, по факту data-bean не управляет своим состоянием, а, значит, объектом не является. Это просто структура, мимикрирующая под объект.
Что-то не вижу ответа на мой вопрос — ни ниже, ни выше.

Глаза протрите.
состояние торчит наружу

То есть по вашему ООП про приватное состояние и получается что в каком нибудь Python ООП нет? Шире мыслить надо. Objects are poor man's closures ООП оно про то что результат вычисления зависит не только от входных параметров его метода но и от каких-то еще данных и эти данные вполне могут быть публичными. Какой нибудь контроллер который возвращает какое нибудь значение которое ему возвращает какой-то репозиторий которым он обладает это таки типичный объект потому что он возвращает значения на основе данных которые ему отдает его репозиторий (который тоже может обладать стейтом или в просто виде массива или в виде БД) а не только тех данных которые ему передали в качестве параметров.Только вот обычно сам объект идет неким параметром в каждом методе. this или self. Ничего не напоминает? А ну да например >>= первым параметром принимает саму монаду т. е. this aka self. Для любой монады результат do x< — monad return x зависит от того какое значение было до этого в монаде (например если было None то None и вернется) поэтому вообще любая монада это объект. Не несите фигни. А то как в той поговорке, научили грамоте осла раньше он просто глупости говорил а теперь он говорит глупости грамотно. Еще раз для таких как вы повторяю — ООП оно про то что функция или метод связанный с объектом возвращает значение или вызывает эффект на основе текущего состояния этого объект и абсолютно не важно публичное ли это состояние или нет.
Глаза протрите.
Не надо хамить
То есть по вашему ООП про приватное состояние и получается что в каком нибудь Python ООП нет? Шире мыслить надо.
Это был один из критериев, вы путаете инкапсуляцию и сокрытие. Далее был описан пример data-bean, в котором есть сокрытие, но нет инкапсуляции.
ООП оно про то что результат вычисления зависит не только от входных параметров его метода но и от каких-то еще данных и эти данные вполне могут быть публичными
Опять же — Вы путаете ООП и процедурное программирование, ООП совсем не про это.
ООП про объекты, их поведение и взаимодействие(сообщения), про инкапсуляцию внутреннего состояния.
Только вот обычно сам объект идет неким параметром в каждом методе. this или self. Ничего не напоминает? А ну да например >>= первым параметром принимает саму монаду т. е. this aka self. Для любой монады результат do x< — monad return x зависит от того какое значение было до этого в монаде (например если было None то None и вернется) поэтому вообще любая монада это объект.
Да чего мелочиться то? f x = x зависит от того какое значение было до этого в x (например если было None то None и вернется) поэтому вообще всё что угодно это объект. А если назвать x 'this', то и комар носа не подточит. f this = this. Матерь божья, где-то я это уже видел:
public A f(A this) {
    return this;
}
...
a.f();
Объект это структура данных и набор функций ака методов для работы с ней. Связанных с ней. Поэтому List и Maybe это объекты.
Те же каррированные функции это объекты и какой нибудь
def add(a)(b)
val c = add(1)
val r = c(2)
будет равен по сути своей вызову у инстанса объекта Int метода add вот так
var r = 1.add(2)
Вообще да в скале 1 реально инстанс объекта и у 1 реально есть метод + и по сути 1 + 2 это в Scala синтаксический сахар над вызовом метода объекта Int. Такие дела.
Объект это структура данных и набор функций ака методов для работы с ней. Связанных с ней. Поэтому List и Maybe это объекты.
А вас не смущает, что для любой структуры данных есть функции, которые с ней работают? Если всё ООП, то нет никакого ООП, т.к. нет смысла отдельно упоминать о том, что есть везде и по умолчанию.
Смысл ООПроектирования чтобы выделить все функции которые работаю именно с этой структурой в единый объект. Сгруппировать их в единую абстракцию и дальше уже на основе нее строить более сложные конструкции. Ток для этого надо проектировать уметь, смотреть шире и иметь возможность к абстрактному мышлению. А вы тут зациклились на определении объекта как Актора.
На самом деле это нужно только в сложных прикладных приложениях с огромным количеством разных структур данных. Вроде игр. Только такие вещи на ФП языках обычно не пишут.
А какое у Вас определение объекта?

Ну вообще Алан Кей, который придумал понятие ООП, объекты как акторы и определял.


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

Все-все-все? А если потом новых функций придумали?

Ну вообще Алан Кей, который придумал понятие ООП, объекты как акторы и определял
В том-то и дело, что он задумал их (пусть не самый первый, а на плечах предшественников) как акторы, обменивающиеся сообщениями. Но в мейнстриме (C++, Java) победила (пока?) другая версия ООП, которая служит в основном переиспользованию кода — наследование и полиморфизм. А у этих победителей и с инкапсуляцией все плохо, и с хрупкостью кода, и с расширяемостью. Но народ пока терпит…
Вообще да, все что угодно это объект а тайпклассы это способ работы с категориями объектов. Работа со всеми объектами которые можно сравнивать (aka имеют метод eq) например. Тайпклассы это как раз таки ООП часть ФП.
UFO just landed and posted this here
Тайпклассы просто механизм, который способен выступить аналогом диспатчера сообщений(виртуальных методов) в ООП языках. Т.е. они позволяют добиться аналогичного эффекта, но это не делает всё, что использует тайпклассы, объектно ориентированным. Даже на ООП языках можно писать не объектно ориентированно(возьмите ту же анемичную модель).

Анемичная модель (класс со свойствами и только тупыми геттерами/сеттерами) — хороший пример одновременно ООП(грамирования) без ООП(роектирования).

ЛОЛ, с анемичной моделью это тоже ООП. Просто логика из Классов которые являются структурами данных перенесена в классы отвечающие за эту самую логику. Тобишь Сервисы. Вообще ООП оно про взаимодействие объектов друг с другом так или иначе и в том же Хаскель в прикладном каком нибудь коде так же будут объекты Order, User, Item и взаимодействие их с друг другом через связанные с ними функции.
Вообще ООП оно про взаимодействие объектов друг с другом так или иначе и в том же Хаскель в прикладном каком нибудь коде так же будут объекты Order, User, Item и взаимодействие их с друг другом через связанные с ними функции
То, что всё объект и везде ООП, Вы уже объяснили. Пойдём от обратного — возможно ли писать не объектно ориентированный код? Можно пример такого кода?
Объект это экземпляр какого-то типа и связанные с ним методы использующие данные этого конкретного экземпляра. Это больше логическая единица моделирующая что-то на текущем уровне абстракции. ООП оно больше про проектирование чем про сам код. В том же Swift у вас могут быть методы объекта отдельно от него. Да и в Rust тоже. Оно больше про то как вы о своем коде думаете.
Например
val x = 1
val notOopX = mul(add(x,2),3)
val oopX = x.add(2).mul(3)

Разница тут в вызвать у объекта X метод или вызвать функцию add с передачей ей x в качестве параметра. То бишь оба вариант могу быть ООП если для вас важнее какие у меня есть X и что он может сделать чем какие у меня есть функции и что они могут сделать.
Ну и тайклассы это прям натуральная ООП фишка потому что они этим самым связыванием и группировкой экземпляров неких типов с некими функциями (методами) занимаются. В Хаскеле синтаксис ML но в Scala натурально как методы вызываются функции тайпклассов вроде
list.map(x=>x+1)
Особенно какие нибудь тайпкласс Movable и Damageble сразу становится понятно что они про какой-то объект который умеет двигаться и может получать урон.
Насколько я помню, в scala вообще нет тайпклассов. Они «эмулируются» с помощью implicit'ов.
Объект это экземпляр какого-то типа и связанные с ним методы использующие данные этого конкретного экземпляра.

int x = 1; int y = 2; int z = x + y;

x, y, z — это объекты? Код(java) объектно-ориентирован?(вопрос в том, что даже в терминологии Java ничего из объявленного — не объект).
А для того же кода на haskell — x, y, z это объекты? Код объектно ориентирован?
x = 1 :: Int
y = 2 :: Int
z = x + y

На мой взгляд Ваше определение термина «объект» имеет место в целом в computer science, но никакого отношения к термину «объект» объектно-ориентированного программирования не имеет(во всяком случае это разные вещи).
Само определение объекта ООП таким образом, просто обесценивает ООП, ведь по сути нельзя написать не объектно-ориентированный код — в чём тогда ценность термина?
Еще раз — зависит от того как вы о них думает. Объект это логическая единица. ООП он про проектирование. Вы слишком узко мыслите со своим представлением об Объектах из Java. В паре строчек кода не увидеть занимался ли человек ООП. Надо глобально смотреть как у него построена система. Вокруг объектов или просто разрозненные структуры данных и фукнции. Класс в Java это модуль и там ООП по сути сводится к умению разделять правильно код на модули и это умение одинаково полезно в любом ЯП и одинаково редко встречается везде.
Это уже уход в философию, с призывами о расширении сознания. Я говорю об инженерном подходе, и с моей точки зрения Ваше определение термина объекта ООП не имеет практической ценности(как и отношения к ООП).
Что за фигню вы несете? Вы либо выделяете в своей системе объекты (User, Item, Money) и вокруг них стоите свою систему либо нет. Вот и весь смыл. Вам просто надо перестать смотреть на объекты как на классы из Java ( я вообще нигде не говорил классы а говорил объекты почему вы пытаетесь общую концепцию объекта сузить до класса из Java?) и понять что это более общая абстракция которая по сути значит инстансы неких типов данных. И да, любую структуру данных можно представить как объект. И обратное тоже верно. Можно любую структуру данных рассматривать как просто данные для вашей функции. 1 это и объект тип Int и одновременно не объект. Все зависит от того на основе какого предположения вы будете проектировать и писать свой код.
Допустим у вас ЯП без генериков и кодо генерации. Вам нужно сделать метода добавления и умножения для Int и для Float.
В ООП стиле — вы сделаете модуль где будут методы Add и Mul для Int Потом сделает отдельный модуль где будут методы Add и Mull для Float
Без ООП стиля — вы сделаете модуль где будут все Методы Add и модуль где будут все методы Mul. Потому что вам важнее что за функции у вас есть и что они делают. В ООП наоборот для вас важнее что за структуры данных у вас есть и что с ними можно сделать. Теперь разницу поняли?
Вам просто надо перестать смотреть на объекты как на классы из Java
Я уже объяснял, и не раз, что такое объект ООП на мой взгляд. Когда я говорю о Java я специально помечаю что речь об объекте в терминологии Java, а не об объекте ООП.
более общая абстракция которая по сути значит инстансы неких типов данных
Нет не значит, по крайнем мере в ООП. Объект это не просто струтура данных, объект имеет собственное поведение, которое зависит от состояния, инкапсулируемого объектом.
да, любую структуру данных можно представить как объект. И обратное тоже верно. Можно любую структуру данных рассматривать как просто данные для вашей функции. 1 это и объект тип Int и одновременно не объект. Все зависит от того на основе какого предположения вы будете проектировать и писать свой код.
если речь том, что одну и ту же сущность можно представить в виде объекта, или не объекта — я с этим согласен. Беда в том, что по вашему определению это в любом случае будет объект. И никакой ценности представление в том или инном виде не имеет.
Теперь разницу поняли?
Конечно, сенсей. Не понял только, что общего State a имеет с ООП. По моему самое логичное объяснение «Если из всех инструментов у тебя есть только молоток, то в каждой проблеме ты увидишь гвоздь». Т.е. Вы просто не воспринимаете других подходов, ведь не знаете ничего кроме ООП.
Я просто много подходов знаю и умею. Как и очень много языков программирования знаю и умею поэтому вижу больше и взгляды у меня более широкие. ООП рука об руку идет с TyDD и с Модульностью и без них сложные прикладные программы хорошими сложно сделать на любом ЯП. Просто в одних Языках это все делается через IRepository, IMovable, IUnitOfWork, User в других через User, Monad, Functor, Movable и т. д. Вся суть ООП про то — какую роль в нашей программе играет инстанс вот этого вот типа. Если вы проектируете и пишете код на основе этого то ООП если нет то не ООП.
Я просто много подходов знаю и умею
круто, наверное, много уметь. Видимо, настолько расширенные взгляды позволяют Вам утверждать что
ООП рука об руку идет с TyDD
Хотя казалось бы — какая связь? Ведь ООП это вообще не про статическую типизацию. И вполне нормально себя чувствует и в динамически типизированных языках.
Но куда нам, простым смертным. Видимо, остаётся поверить на слово.
Ну TyDD про типы. ООП про экземпляры этих типов. Таки да, ООП лучше со статической типизацией живет. Ну и каждый Объект описывается неким типом. Если вы используете ООП то автоматически начинаете работать с типами. Другое дело что без статической типизации их проверить при компиляции нельзя и TyDD не работает но ваша программа продолжает базироваться на свойствах каких-то типов (классов например).
Да если Вы любой практически современный язык используете, Вы уже с типами так или иначе работаете. В js есть типы — значит js идёт рука об руку с TyDD, так? Правда проверить статически не получится, и TyDD не работает, но программа то базируется на свойствах каких-то типов… где логика?
ООП вообще никакого отношения к TyDD не имеет.
ООП прямое отношение имеет к типам. Просто с динамикой их не проверить во время компиляции.
Кажется я начал понимать логику.
ООП прямое отношение имеет к типам
А в названии TyDD как раз есть слово «тип». А всё что можно назвать «объектом» — это ООП.
Так уфологи, видимо, были основоположниками ООП, ведь они исследовали НЛО. Вот, нашли применение этим объектам в программировании.
Следующим шагом предлагаю провозгласить любую программу не только объектно ориентированной, но и функциональной(ФП), ведь в любой программе, определённо есть функции.

TyDD без проверки компилятором — всё равно что TeDD со случайным запуском тестов.

В js есть типы — значит js идёт рука об руку с TyDD, так?

Но ведь в js нет типов.

Типы есть, нет статической проверки типов. Можно использовать оператор typeof, с помощью него можно узнать тип значения.
UFO just landed and posted this here

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

UFO just landed and posted this here

В прототипном ООП (JS-стайл) никаких особо типов, кроме object, нет (и то, говорят, не тип, а рантайм-тег), и тем не менее как-то работает и полиморфирует.

Так для справки Мутабельность противоречит Иммутабельности. Декларативность противоречит Процедурности. ООП код не противоречит ФП поэтому его сложно как-то особо отличить. Вы этого не понимаете поэтому думаете что есть какие-то четкие различия а на самом деле код может быть и ФП и ООП одновременно.
Я нигде не говорил что ФП мешает писать объектно-ориентированно. Не мешает, но и не помогает — это просто разные плоскости. На том же hs, как я уже говорил, вполне можно изобразить ООП(как и на С, для примера можно взять Gtk и его обёртку на hs).
Мысль вам на размышление. Класс Java или C# который содержит методы которые не используют данные самого этого класса тоже не являться объектом. Просто неймспейс ака модуль для набора функций/процедур
//Это не объект хоть и класс C#
class Calculator
{
int Add(int a, int b) => a + b
} 

//Вот это объект
class Service
{
public IRepository Repo{get;set;}
public int Count() => Repository.Count()
}
Согласен(правда у меня есть собственное мнение насчёт сервисов и анемично модели, но это как-нибудь в другой раз), но на мой взгляд объект отличает как раз инкапсуляция и собственное поведение(в данном случае — делегирование сообщения другому объекту). А на Ваш взгляд экземпляр этого класса — объект с точки зрения ООП(не java)?
class User {
    String name;
    String something;
}

Зависит от того как вы строите свой код и разбиваете его на модули. Вокруг этой структуры данных или нет. В том же Swift или Rust у него могут быть методы. Да в Scala у него могут появится методы от тайпклассов. Это взглянув на пару строчек нельзя сказать. Это можно только полностью код посмотрев и оценив как идет логическое разбиение на модули понять. Грубо говоря в той же Java можно сказать что Методы это процедуры которые первым параметром принимают сам объект как this. Анемичная модель вполне себе позволяет писать ООП код. Вообще в том же C# можно через методы расширения к анемичной модели прикрепить методы.
Да в Scala у него могут появится методы от тайпклассов
implicit class, не type class
Грубо говоря в той же Java можно сказать что Методы это процедуры которые первым параметром принимают сам объект как this
зато у методов в Java есть исключительное право на доступ к приватным полям. Да и вообще — методы в Java это лишь отражение системы сообщений прежних языков, в которых развивалось ООП. И, конечно, метод в Java это не то же самое что обычная процедура.(да, я не сомневаюсь что Вы знаете про виртуальность, но по моему это очень важное отличие, которое позволяет изменить поведение объекта).
Инкапсуляция не значит сокрытие. Массив объектов User является объектом который инкапсулирует в себе несколько объектов User. Для ООП сокрытие в принципе не нужно хотя с ним удобнее.
Ну и да. class в частности да и объект в общем это какие-то типы поэтому ООП намертво связанно в TyDD (Type Driven Development) Да и в целом суть одна и та же. Если в ООП вы концентрируетесь на том какие объекты у вас есть и что с ними можно сделать то в TyDD вы концентрируетесь на том какие типы у вас есть и что с ними можно сделать. Ну и Процедурное Программирование оно про то какие функции/процедуры у вас есть и что они могут сделать.
Только вот ООП вполне практикуется и в динамических языках, а TyDD это про статическую типизацию.
Эээ, так в динамических ЯП объекты с неким типом идут. в том же JS есть тип number. Так и class User в нем это тип. Динамическая типизация это значит что типы проверяются во время выполнения а не во время компиляции. Динамическая это не значит что типов нет…
Динамическая типизация это значит что типы проверяются во время выполнения а не во время компиляции.
Да, а TyDD это про статическую проверку.
Ну да. Просто ООП тоже про типы и их экземпляры. Про их свойства. Про их поведение. Точнее экземпляров этих типов.

В JS number — не объект. А полноценные объекты могут создаваться без явного объявления class или функции-конструктора. Тупо на литералах и динамических "объявлениях" методов. У всех объектов сложной системы будет один тип object, если исключить встроенные типы вроде Date или Number (не путать с number)

КМК, всё-таки идеологически мешает.
ООП — оно об инкапсуляции стейта в объектах, в том числе об изменении этого стейта в ответ на внешние раздражители. Без мутабельности методы объектов — просто пачка частично-применённых функций.

Возможно. Тем не менее, реализовать ООП в ФП языках(например hs) можно(как пример та же обёртка для гтк). Не факт, конечно, что это будет удобно использовать.
К тому же всегда будет проблема с несовместимостью разных объектных систем(за неимением встроенной в язык). Оно и понятно — язык то не объектно ориентированный.
Что за фигню вы опять несете? В Хаскеле лист это объект у которого есть стейт — коллекция данных. Который инкапсулирует в себя (внезапно инкапсуялиция она про собирать вместе, включать в себя а сокрытие про то что с наружи не видно. Одно без другого живет) работы с коллекцией данных внезапно. Как какой нибудь HttpClient работы с HTTP инкапсулирует.
Речь о том, что при появлении (мутабильного)состояния, придётся менять сигнатуру функции отправки сообщения.
Лист в хаскеле всё-таки больше управляющая конструкция, чем коллекция данных.
инкапсуялиция она про собирать вместе, включать в себя а сокрытие про то что с наружи не видно
Инкапсуляция это когда внутреннее устройство компонента отделено от его интерфейса. (Компонент выступает в роли черного ящика).
Так List в хаскеле ничего не инкапсулирует, вся его структура доступна любому желающему.
Заучили баз ворды и теперь повторяете их как попугай. Тратить на вас время бессмысленно. Все равно не поймете.
Заучили баз ворды и теперь повторяете их как попугай
«TyDD», «Type class», какие ещё были «баз ворды»?
Тратить на вас время бессмысленно
Правильно, время лучше не тратить, а использовать. Например, для улучшения собственных навыков и знаний.
Все равно не поймете.
Согласен, мне действительно не понять почему hs List это ООП объект, и с чего вдруг ООП «намертво связанно в TyDD». Видимо, тут нужен Lead 80го уровня =)
Я вам пытался объяснить. До вас все равно не доходит. Как об стенку горох. Смысла учить таких ограниченных людей как вы не вижу.
Ну тут уже кидали ссылку на статью объясняющую что любая частично примененная функция ака каррированная, это по сути объект с одни методом. Ну а про то что ООП оно про стейт это вы в самое яблочко. Вся суть ООП это работа со стейтом и везде где надо работать со стейтом начинается в той или иной форме ООП. Да вон фул ФП либа doobie возвращает объект Conection котоырой хранит в себе стейт тех операций с БД что надо сделать. Дальше у него вызываются методы ака он передается функциям в качестве основного параметра и т.д. Да тот же List в Хаскеле это объект такой типичный. Просто вот что стейт обязательно мутабельный этот прям ЛОЛ. Даже в ООП Java тот те паттерн стейт реализуют через возврашение мового стейта. Тобишь вместо того чтобы мутировать текущий просто создают новый с новым стейтом.
UFO just landed and posted this here
Можно — не значит нужно.

Какие же дегенераты… Что, не нравится фраза? В чем, бл, проблема?

Гнилье тут, а не народ..

Минусите больше, дегенераты, вам ведь только это надо

Немного есть опыт работы с C++ и Haskell, хотя оба языка не являются идеальными ООП и ФП в вакууме. Если сравнивать более менее сформировавшиеся проекты, в которые до сих пор вносятся изменения, то их состояние можно описать как неустойчивое и устойчивое равновесие.


Все особенности ООП подхода с разными шаблонами, постоянными спорами "ты не так понимаешь принцип единственной ответственности" делает состояние согласованной и однородной кодовой базы весьма красивым, но со временем оно склонно разваливаться. Костыли писать весьма легко. Делать зависимость локального логгера от подключения к БД вам никто не помешает. Конечно можно сказать "Нормально делай — нормально будет", но формального определения нормального в ООП нет, есть только несколько общепринятых трактовок, внутри которых также остается пространство для спекуляций. Чтобы проект придерживался архитектуры приходится прикладывать усилия.


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


Плюсами я пользуюсь в большей степени, уж как-то пришлось разобраться с особенностями ООП, но любовь к ФП у меня точно не из-за того, что мне лень разбираться с классами.

но со временем оно склонно разваливаться. Костыли писать весьма легко.

Интересует эта часть. Что именно и по какой причине разваливается?

Тщательно выстроенные красивые, согласованные иерархии и композиции со временем разваливаются, потому что костыли, их нарушающие, писать легко.

Почему разработчики влюбляются в функциональное программирование?

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

В разработке основная борьба идет со сложностью. Хаскели копают уже больше 30 лет. Согласитесь, это достаточный срок для того, чтобы индустрия могла развернуться к ним лицом, давай все эти милые ФП трюки, хотя бы 20% преимуществ на практике (в терминах сроков, бюджетов или сопровождения). Пусть даже не вся, я согласен принять как аргумент в пользу, если доминирование ФП будет заметным хотя бы в каких-то определенных нишах.
UFO just landed and posted this here

На чём пишут компиляторы C++, Java, C#, Fortran, Rust, Go?

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

UFO just landed and posted this here
Было бы интересно посмотреть как уживаются фп и деревья… ну АСТ я имею ввиду.
UFO just landed and posted this here
Вот это вот переименование и замена переменных, с точки зрения фп и чистых функций, я правильно понимаю что дерево должно копироваться полностью от функции к функции? И копируется ли?

На языках со сборщиком мусора подобные структуры данных копируются при необходимости модификации и не копируются (передаются по неизменяемой ссылке) когда модификация не требуется.


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

UFO just landed and posted this here

Короткий ответ — да, должно копироваться, и нет, по факту не копируется. См. персистентные структуры данных.


За длинным есть классическая книжка на эту тему, что-то вроде банды четырех для ООП или Кнута для сишников:


image

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

И сразу с места в карьер мимо. Функциональное программирование — оно про функции как объекты первого порядка, поэтому оно и называется функциональным. Здесь и далее в статье вы описываете Haskell. По вашему описанию, вся из себя функциональная Scheme функциональщиной не является, потому что про побочные эффекты там вообще ничего нет, и доступно мутирование состояния через set!.


Понять это можно, если учесть тот факт, что у каждой функции, по умолчанию, есть, как минимум, один параметр — self

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

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

Именно, не является. Как и любой лисп, в общем-то.

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

"грязное" функциональное — это оксюморон.

Понять это можно, если учесть тот факт, что у каждой функции, по умолчанию, есть, как минимум, один параметр — self.

Вы уверены в этом? )