company_banner

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

Original author: Rhea Moutafis
  • Translation
Функциональное программирование (ФП) существует уже лет 60, но до сих пор оно всегда имело достаточно узкую сферу использования. Хотя компании, меняющие мир, вроде Google, полагаются на его ключевые концепции, средний современный программист знает об этом феномене очень мало, если вообще что-то знает.

Но это скоро изменится. В такие языки, как Java и Python, интегрируется всё больше и больше концепций ФП. А более современные языки, вроде Haskell, являются полностью функциональными.



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

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

Суть функционального программирования — это уничтожение побочных эффектов


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

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

def square(x):
    return x*x

Эта функция крайне проста. Она принимает один аргумент, x, который, вероятно, имеет тип int, а, может быть, тип float или double, и выдаёт результат возведения этого x в квадрат.

А вот — ещё одна функция:

global_list = []
def append_to_list(x):
    global_list.append(x)

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

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

append_to_list(1)
append_to_list(2)
global_list

После пары вызовов функции из предыдущего примера в global_list будет уже не пустой список, а список [1,2]. Это позволяет говорить о том, что список, в действительности, является значением, подаваемым на вход функции, хотя это и никак не зафиксировано при объявлении функции. Это может стать проблемой.

Нечестность при объявлении функций


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

Подумайте о том, как бы вы тестировали функцию append_to_list. Недостаточно будет прочесть первую строку её объявления, и выяснить, что её надо тестировать, передавая ей некое значение x. Вместо этого нужно будет читать весь код функции, разбираться в том, что именно там происходит, объявлять переменную global_list, а потом уже тестировать функцию. То, что в нашем простом примере, как кажется, особых сложностей не вызывает, в программах, состоящих из тысяч строк кода, будет выглядеть уже совсем по-другому.

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

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

Мы не особенно многое изменили в этом коде. В результате работы функции в newlist, как раньше в global_list, оказывается [1,2], да и всё остальное выглядит так же, как прежде.

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

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

Функциональное программирование — это написание чистых функций


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

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

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

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

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


▍Функции map и reduce


Циклы — это механизмы, не имеющие отношения к функциональному программированию. Взгляните на следующие Python-циклы:

integers = [1,2,3,4,5,6]
odd_ints = []
squared_odds = []
total = 0
for i in integers:
    if i%2 ==1
        odd_ints.append(i)
for i in odd_ints:
    squared_odds.append(i*i)
for i in squared_odds:
    total += i

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

А теперь — ещё один вариант этого кода:

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)

Это — полностью функциональный код. Он короче. Он быстрее, так как тут не приходится перебирать множество элементов массива. И, если разобраться с функциями filter, map и reduce, окажется, что этот код понять не намного сложнее, чем тот, в котором применяются циклы.

Это не значит, что в любом функциональном коде используются map, reduce и прочие подобные функции. И это не означает, что для того чтобы с подобными функциями разобраться, нужно знать функциональное программирование. Дело лишь в том, что эти функции достаточно часто применяются тогда, когда избавляются от циклов.

▍Лямбда-функции


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

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

▍Статическая типизация


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

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

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

Некоторые языки «функциональнее» других


▍Perl


В Perl реализован такой подход к работе с побочными эффектами, который отличает его от большинства других языков. А именно, в нём имеется «волшебная переменная» $_, которая выводит побочные эффект на уровень одной из основных возможностей языка. У Perl есть свои достоинства, но я не стал бы пытаться заниматься функциональным программированием на этом языке.

▍Java


Желаю вам удачи в деле написания функционального кода на Java. Она вам не помешает. Во-первых, половину объёма кода будет занимать ключевое слово static. Во-вторых, большинство Java-программистов назовут ваш код недоразумением.

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

▍Scala


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

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

▍Python


В Python приветствуется функциональный стиль программирования. Понять это можно, если учесть тот факт, что у каждой функции, по умолчанию, есть, как минимум, один параметр — self. Это, во многом, в духе «Дзена Python»: «Явное лучше, чем неявное».

▍Clojure


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

▍Haskell


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

Итоги


Надо отметить, что сейчас — всё ещё самое начало эры больших данных. Большие данные идут, и не одни, а с другом — с функциональным программированием.

Функциональное программирование, если сравнить его с объектно-ориентированным программированием, всё ещё остаётся нишевым феноменом. Правда, если считать значимым явлением интеграцию принципов ФП в Python и в другие языки, то можно сделать вывод о том, что функциональное программирование набирает популярность.

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

Хотя у объектно-ориентированного кода есть бесчисленное множество достоинств, не стоит сбрасывать со счетов и достоинства функционального кода. Если программист изучит некоторые базовые принципы ФП, то этого, в большинстве случаев, может быть достаточно для повышения его профессионального уровня. Такие знания, кроме того, помогут ему подготовиться к «функциональному будущему».

Как вы относитесь к функциональному программированию?

RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR

Comments 370

    +27

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

      +16

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

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

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

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

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

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

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


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

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

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


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

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

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

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

                    Так мы о современности или сложности?


                    Так-то мне скорее на питоне тяжело софт писать, особенно если он больше нескольких десятков строк — я это вообще не осилил.


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

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

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


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


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

                        +1

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

                          +1

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

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

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


                        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

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

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

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

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

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

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

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


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

                      +3

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

                        +1

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


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

                          +4

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

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

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

                    Вот делать разработчикам на работе нечего, как только думать «а давай-ка я щас перейду от объектно-ориентированного к функциональному программированию». Разработчики видят в scala язык, который удобно позволяет пользоваться например средствами Spark, и работать с большими данными (примерно в том же стиле, как с данными обычными). Ну или работать с Akka, допустим. Или даже просто взять ScalaCheck, и заняться property based тестированием. Разработчики видят в ФП инструмент, позволяющий им более удобно решать какие-то свои практические задачи.
                      +5
                      Реальная суть ФП скорее в изоляции функциональных эффектов. Уничтожением это называть просто неграмотно.

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

                        +1
                        Да и не эффектов в общем тоже. Скажем прямо, в scala и тем более Java — не самые сильные системы типов. Однако же, даже такие системы типов дают очень полезные на практике подсказки. Скажем, явно намекая, что получение max от списка дает нам на самом деле не Integer, а Option, потому что непустота списка не может быть доказана вот в этом вот месте, и max для пустого списка неизвестен. А что тут взамен дают динамически типизированные (наверное точнее будет сказать слабо типизированные?) языки? А ничего обычно.
                          0

                          Но это полезно и не только для ФП.

                            0
                            Скажем прямо, в scala и тем более Java — не самые сильные системы типов


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

                              Я бы не назвал это слабостью. Среди мейнстримных они вполне на уровне. Просто есть реальные примеры, что система типов вполне может быть более мощной.
                                0

                                В скале полноценных зависимых типов нет, в джаве всё ещё печальнее.

                                  +2
                                  Насколько мне известно, лишь немногие немейнстримовые языки могут похвастаться поддержкой зависимых типов. На этом фоне вменять скале как языку своей эпохи это в вину странно. Но да, их не хватает.
                                0
                                >>> max([])
                                Traceback (most recent call last):
                                  File "<stdin>", line 1, in <module>
                                ValueError: max() arg is an empty sequence

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


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

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

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

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

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

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


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

                                  +1

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

                                    +1

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


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


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


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

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

                                        Иметь тайпкласс типа


                                        class MonadLogger m where
                                          log :: String -> m ()

                                        и навешивать на функции констрейнт MonadLogger m. Реализация этого тайпкласса, конечно, может жить хоть в IO, хоть в чём хотите.


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

                                          0
                                          Хм спасибо.

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

                                          Из самого очевидного (я не настоящий Хаскелист, просто действительно не очень понимаю насколько Хаскелл даже для небольших утилит подходит): логирование (чтобы можно было разобраться что случилось) и maybe (чтобы не писать всю логику «не получилось»).
                                            0
                                            А как вообще будет выглядеть 2 монадических поведения?

                                            Например,


                                            myFunction :: (MonadLogger m, MonadError MyErrorType m) => Arg1 -> … -> m Res

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

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

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

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

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

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

                                      Мне все же больше нравится формулировка, что мы их изолируем (в случае хаскеля при помощи IO), чтобы было четкое разделение на чистые функции, и функции с эффектом. И тогда уже вопросы типа этого будут иметь четкий ответ — чистые функции можно переупорядочивать, параллелить и т.п. с целью оптимизации, а вот эффекты в общем случае переупорядочивать нельзя, потому что в базе сначала INSERT, а уж потом UPDATE или DELETE. И это должно быть видно в коде, возможно в сигнатурах и т.п.
                                    +14
                                    В Python приветствуется функциональный стиль программирования. Понять это можно, если учесть тот факт, что у каждой функции, по умолчанию, есть, как минимум, один параметр — self.

                                    Охренел прямо. Подумал, перевод кривой. Ан нет.
                                    You can see this by the fact that every function has, by default, at least one input, self
                                      +11
                                      Вот только не ясно где там селф в функции
                                      def something():
                                          return 4
                                      


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

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

                                              –1

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

                                                0

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

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

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

                                                      –3
                                                      За что и ругают монады
                                                        0

                                                        Монады, собственно, вообще не ругают. Ругают, например, mtl (где живёт конкретный тайпкласс MonadReader), но по совсем другим причинам.

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

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

                                      Я лично использую только для простенькой фильтрации или трансформации небольших объектов когда знаю что их количество ограничено числом N, и это число меня устраивает.
                                        +2
                                        если бы не GC который будет забирать у вас 80% CPU тупо на очистку памяти…

                                        Только практика показывает, что даже весьма мусорящие программы тратят на GC от процента до, ну, навскидку, 10-20 процентов времени работы, если там вот прям совсем много мусора.


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

                                          0

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


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


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


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

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

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


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

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

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


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

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

                                                  0

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

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

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


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

                                                    +2

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

                                                  +1

                                                  Неа, не было.


                                                  Из примеров серверов — ну, например


                                                  1. Штука, которая принимает HTML, парсит его согласно некоторой внутренней логике, и выдаёт распаршенный результат.
                                                  2. Штука, которая принимает человеческий текст, прогоняет огромный набор правил по нему вместе со всяким машинным обучением внутри, выдаёт результат.

                                                  Проблем с GC не было ни разу.

                                                    0
                                                    И сколько запросов в секунду у вас в пике на этот ендпоинт и какова продолжительность этих пиков? У каждого разная нагрузка и поведение клиентов.
                                                      0

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


                                                      Впрочем, сравнивать RPS имеет смысл только для каких-нибудь echo server'ов. Так ведь совсем упускается, что оно при этом внутри делает и сколько мусора генерит непосредственно внутри бизнес-логики.

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

                                                  ПС
                                                  Отдельно хочу заметить, что даже не представляю как в этом случае профилировать программу на Haskell.
                                                    0
                                                    Ну вот у меня было (когда в качестве начального этапа освоения языка решил олимпиадные задачки пописать), что построение обычного бинарного дерева на Haskell работало раз в 50 медленнее, чем на С.

                                                    Там есть некоторые тонкости, но обычно довольно быстро начинаешь понимать, как писать точно не стоит.


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

                                                    В самом простом случае использования stack для сборки можно сделать stack build --profile и потом stack exec --profile -- your-app +RTS -p для запуска с профилированием.


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

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

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

                                                и наоборот.

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

                                                  Только вы это сделать в общем случае не можете в автоматическом режиме, если вас заботят рантайм-характеристики вашей программы (хотя кого в ФП-тусовке они заботят), потому что в одном направлении вы увеличиваете количество работы, а в другом — можете сделать space leak (особенно в ленивом ЯП).

                                                    +1
                                                    Что интересно, в ФП при необходимости, как правило, проще рассчитать требования программы к ресурсам. Но да, ФП — это прежде всего про корректность, а не про производительность. Но про стеко- и хипобезопасность надо думать, да (но в императивном программировании об этом, вроде, надо думать ещё больше).
                                                      +2
                                                      В случае ленивости — не факт что проще.
                                                      +3

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

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

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

                                                      +1

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


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

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

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

                                                            +1
                                                            А автор не пишет про это ничего. В чем и разница между вашим комментом и оригинальным постом.
                                                        +1
                                                        Да, в общем-то, нигде.

                                                        Статья в целом не очень качественная.
                                                        –9

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

                                                          0
                                                          Однако же надо как-то заходить на новый виток.
                                                          +1
                                                          Описание Scala могло бы быть и получше.
                                                          Например, можно было бы упомянуть, что Scala это Java + типизация + ФП + Иммутабельность + Акторы (в т.ч. распределенные между хостами) + Spark + компиляция в JS + Scala Native
                                                            +1
                                                            + Акторы (в т.ч. распределенные между хостами) + Spark

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

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

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

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

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

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

                                                                  +3

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


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

                                                                    +2

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                                                                        +1

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

                                                                                          +2

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


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

                                                                                            +1

                                                                                            Но ведь ST описывается в рамках ФП.


                                                                                            А хешмапы были и чистые (в unordered-containers), и в SThashtables, ЕМНИП).

                                                                                              +1

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


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

                                                                                                0
                                                                                                Что там от этого ФП остается.

                                                                                                Контроль за тем, что императивщина не утечёт за пределы ST, например. Или что вы из двух разных зелёных или ОС-тредов не поменяете одну и ту же ST-переменную без синхронизации.


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

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


                                                                                                В самом деле, нет вообще никакой концептуальной разницы между


                                                                                                struct Unit {}; // void врёт
                                                                                                
                                                                                                Unit myAction()
                                                                                                {
                                                                                                    std::string name;
                                                                                                    std::cin >> name;
                                                                                                    std::cout << "yay " << name << std::endl;
                                                                                                    return Unit {};
                                                                                                }

                                                                                                и


                                                                                                myAction :: IO ()
                                                                                                myAction = do
                                                                                                  name <- readLn
                                                                                                  putStrLn $ "yay " <> name
                                                                                                  0

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

                                                                                                    0

                                                                                                    Конечно, если есть возможность отделить Int от IO Int, то глупо этим не пользоваться. Но вот тот же ST решает точно такую же задачу: показывает, что есть мутабельность, и что если хочется из неё выйти, то надо сделать runST. Линейные типы тут не очень помогают, потому что существуют программы, типизируемые в ST, но нетипизируемые в линейных системах типов (потому что линейные системы типов — это ещё одна статическая аппроксимация рантайм-поведения).

                                                                                                      0
                                                                                                      А как определить, есть возможность отделить или нету? Может проще весь код писать в ST/IO?
                                                                                                        0

                                                                                                        Ну, нужна явная мутабельность — пишете в ST/IO/PrimMonad/etc, не нужна — не пишете. Если функция, которую вам надо вызвать, живёт в ST или IO, то у вас нет выхода, кроме как самому переселяться в ST или IO.

                                                                                                          0
                                                                                                          И как определить, нужна мутабельность или нет, например из примера выше с hashtable?
                                                                                                            0

                                                                                                            Функции из unordered-containers чистые, для тамошней хешмапы не нужна. Фукнции из hashtables живут в ST и IO, там она нужна (и компилятор не даст функцию в IO вычислить в чистом контексте, если не учитывать всякие unsafePerformIO, которые легко выгрепать).

                                                                                                              0
                                                                                                              Ну вот я написал функцию, которая требует ST. Что дальше? Как выяснить, действительно ли там нужно ST или нет? В противном случае можно просто все в ST и IO реализовать
                                                                                                                0

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


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

                                                                                                                  0
                                                                                                                  Ну так можно и в других языках проверять.
                                                                                                                    +2

                                                                                                                    Так — да. Но, опять же, смысл всех этих систем типов не в том, чтобы проверить, что «если функция объявляет, что она делает IO, то она на самом деле делает IO», а, наоборот, проверить, что «если функция объявляет, что она IO не делает, то она на самом деле IO не делает».

                                                                                                                      0
                                                                                                                      Вопрос в том, насколько позволено допустить «проникновение» монад в код? Не получится ли из монад «эффект разбитых окон»?

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

                                                                                                                        Потому что писать с монадами чуть больнее. В ООП если полениться, то можешь сломать архитектуру/абстракцию, в 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
                                                                                                                          0
                                                                                                                          add :: IO Int -> IO Int -> IO Int
                                                                                                                          add x y = do 
                                                                                                                                  a <- x
                                                                                                                                  b <- y
                                                                                                                                  return (a + b)

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

                                                                                                                            0

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

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

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

                                                                                                                      0

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

                                                                                                                        0

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

                                                                                                                        0

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

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

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


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


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


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

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

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


                                                                                                                              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
                                                                                                                                0
                                                                                                                                да, но функция, грубо говоря, будет просто работать с монадическим интерфейсом. Т.е. различным образом соединять монадические комбинаторы, зацикливать вычисления и т.д.
                                                                                                                                Но вот сегодня не делать ничего, а завтра полезть в файл она не сможет.
                                                                                                                                  +1

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

                                                                                                                                                0

                                                                                                                                                Debug.trace ))00)0))))

                                                                                                                                                  0

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


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

                                                                                                                                +1

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

                                                                                                                0
                                                                                                                // void врёт

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


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

                                                                                                                  0

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


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

                                                                                                                    +1

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


                                                                                                                    void foo() {}
                                                                                                                    void bar() { return foo(); }

                                                                                                                    Но написать


                                                                                                                    void foo() {}
                                                                                                                    void bar()
                                                                                                                    {
                                                                                                                        auto res = foo();
                                                                                                                        return res;
                                                                                                                    }

                                                                                                                    не можете.


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

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

                                                                                                                      0

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

                                                                                                                      +1

                                                                                                                      А неважно, что там получается при компиляции. Важно, что с точки зрения всякого там матлога функция, которая ничего не возвращает, либо никогда не завершается (условный std::abort — хороший кандидат, кстати, на то, чтобы на самом деле ничего не возвращать), либо её невозможно вызвать, потому что хотя бы один из её аргументов «не существует» (и поэтому во всяких теориях типов логическое отрицание утверждения T записывается как функция с типом T -> Void).

                                                                                                        +4
                                                                                                        Но сразу после этого тот же самый человек заявляет — завтипы наше всё! И получается, что в одной ситуации он против, а в другой, полностью аналогичной первой — он «за».

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

                                                                                                      +3
                                                                                                      У вас прямо все по этой картинке

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


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

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

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

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

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

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

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

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


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

                                                                                                        +2
                                                                                                        А теперь — ещё один вариант этого кода:
                                                                                                        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)

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

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

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

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

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

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


                                                                                                                  integers = range(1, 10000)

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

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

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


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

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


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

                                                                                                                      0

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

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

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

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

                                                                                                                          Плюс, вы просто всякие hibernat'ы не встречали, существует множество библиотек, позволяющих из импреативной программы делать запросы ничуть не хуже чем SQL, например просто превращать иерархию классов в таблицы базы данных (у них свои проблемы с производительностью, но вот кое какие вещи на чистом SQL писать намного тяжелее).
                                                                                                                            0
                                                                                                                            Не всегда. Конечно, с помощью 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 разной степени навороченности существуют, естественно: иначе бы программистам, которые обучены обычному императивному программирванию (а таких, наверное, большинство) было бы совсем грустно писать свои программы, которым по жизни часто с БД работать приходится.
                                                                                                                              +1
                                                                                                                              средства построения запросов SQL разной степени навороченности существуют, естественно: иначе бы программистам, которые обучены обычному императивному программирванию (а таких, наверное, большинство) было бы совсем грустно писать свои программы, которым по жизни часто с БД работать приходится.

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

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

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

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


                                                                                                                                    0

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

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

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

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

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


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

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




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


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


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

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

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

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

                                                                                                                                Именно так.

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

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

                                                                                                                                  0

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


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

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

                                                                                                                                    0

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


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


                                                                                                                                    getPersonsInner backend

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

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

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


                                                                                                                                      и как чисто по этой аннотации понять, что IsSqlBackend — это какой-то внешний источник и каждый вызов getPersonsInner backend может вернуть разные результаты (а стало быть может быть мемоизован лишь с оговорками)?

                                                                                                                                      А это не так. Вызов getPersonsInner backend всегда возвращает один и тот же набор действий. Непосредственно юзеров возвращает уже лишь некоторый интерпретатор, который берёт то, что вернул этот вызов, и, собственно, интерпретирует эти действия, залезая в базу и что-то там делая. Или залезая в мок. Или ничего не делая.

                                                                                                                                        0

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


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

                                                                                                                                          0
                                                                                                                                          > И да, эта функция о своих намерениях говорит достаточно точно
                                                                                                                                          Как теперь заставить программиста четко выражать свои намерения, а не ставить везде IO?
                                                                                                                                            +3

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

                                                                                                                                              –1
                                                                                                                                              Ну тоесть никак. Чем тогда это лучше, чем в любом другом языке согласиться в имя функции приписывать суффикс pure?
                                                                                                                                                0

                                                                                                                                                Тем, что компилятор проверит, что вы из pure-функции не вызвали не-pure-код.

                                                                                                                                                  0
                                                                                                                                                  А наоборот проверить можно? Чем это защитит от программиста, который везде будет пропихивать монады?
                                                                                                                                                    0

                                                                                                                                                    Никак наоборот не проверить, вы действительно всё можете писать в IO, никто вам не запретит. Но такой цели и нет.

                                                                                                                                          0

                                                                                                                                          Как выше написали, этот внешний источник возвращает данные разные при интерпретации, но функция 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. Во-вторых появляется возможность "отменять" действия по каким-нибудь причинам, потому что пока вы не начали интерпретировать, действие не запустится.


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


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

                                                                                                                                  0
                                                                                                                                  Помимо Scala есть еще и Kotlin…
                                                                                                                                    0
                                                                                                                                    Увидел тут недавно 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. даже не знаю теперь, фреймворк написать или сразу язык.