Функциональное программирование (ФП) существует уже лет 60, но до сих пор оно всегда имело достаточно узкую сферу использования. Хотя компании, меняющие мир, вроде Google, полагаются на его ключевые концепции, средний современный программист знает об этом феномене очень мало, если вообще что-то знает.
Но это скоро изменится. В такие языки, как Java и Python, интегрируется всё больше и больше концепций ФП. А более современные языки, вроде Haskell, являются полностью функциональными.

Если описать функциональное программирование простыми словами, то это — создание функций для работы с неизменяемыми переменными. В противоположность этому, объектно-ориентированное программирование — это когда используется сравнительно постоянный набор функций, а программист, в основном, занят модификацией существующих переменных и созданием новых.
ФП, по своей природе, подходит для решения актуальных задач, вроде задач анализа данных и машинного обучения. Это не означает, что нужно попрощаться с объектно-ориентированным программированием и полностью перейти на функциональное. Современному программисту просто полезно знать основные принципы ФП, что даст ему возможность применить эти принципы там, где они могут сослужить ему хорошую службу.
Для того чтобы понять принципы функционального программирования, сначала надо разобраться в том, что такое «функция». Может, это покажется скучным, но, в итоге, это позволит увидеть то, что на первый взгляд незаметно. Поэтому давайте поговорим о функциях.
Функция, говоря упрощённо, это сущность, которая преобразует некие входные данные, передаваемые ей, в выходные данные, которые она возвращает в место вызова. Правда, на самом деле всё далеко не всегда выглядит так просто. Взгляните на следующую функцию, написанную на Python:
Эта функция крайне проста. Она принимает один аргумент,
А вот — ещё одна функция:
На первый взгляд кажется, что она принимает
Функция не сможет нормально работать в том случае, если заранее не будет объявлена переменная
После пары вызовов функции из предыдущего примера в
Эти неявные входные или выходные значения имеют официальное наименование: побочные эффекты. Тут мы используем очень простые примеры, но в более сложных программах побочные эффекты способны приводить к возникновению реальных сложностей.
Подумайте о том, как бы вы тестировали функцию
К счастью, вышеозначенную проблему легко исправить. Нужно просто быть честным при указании того, что именно должно поступать на вход функции. Следующий вариант нашей функции выглядит уже куда лучше предыдущего:
Мы не особенно многое изменили в этом коде. В результате работы функции в
Но мы, однако, внесли в этот код одно существенное изменение. Мы избавились от побочных эффектов. И это очень хорошо.
А именно, теперь, прочтя первую строку объявления функции, мы точно знаем о том, с какими входными данными она работает. В результате, если программа не будет вести себя так, как ожидается, можно легко протестировать каждую имеющуюся в ней функцию и найти ту, которая работает неправильно. Чистые функции легче поддерживать.
Функция, при объявлении которой чётко указано то, что она принимает, и то, что она возвращает — это функция без побочных эффектов. Функция без побочных эффектов — это чистая функция.
Вот очень простое определение функционального программирования. Это — написание программ, состоящих только из чистых функций. Чистые функции никогда не модифицирую переданные им данные, они лишь создают новые и возвращают их. (Отмечу, что я немного смошенничал в предыдущем примере. Он написан в духе функционального программирования, но в нём функция модифицирует глобальную переменную-список. Но здесь мы лишь разбираем базовые принципы ФП, поэтому я и поступил именно так. Если хотите, здесь вы можете найти более строгие примеры чистых функций.)
Далее, работая с чистыми функциями, можно ожидать того, что они, получая на вход одни и те же данные, всегда будут формировать одни и те же выходные данные. А функции, которые чистыми не являются, могут зависеть от каких-то глобальных переменных. В результате они, получая одно и то же на вход, могут выдавать разные результаты, зависящие от значения глобальных переменных. Этот факт способен значительно усложнить отладку и поддержку кода.
Есть простое правило, которое позволяет обнаруживать побочные эффекты. Так как при объявлении чистых функций должно быть чётко определено то, что они получают на вход и возвращают, функции, которые ничего не принимают и не возвращают, чистыми не будут. Если вы решите внедрить в свой проект методы функционального программирования, то первым делом вы, вероятно, решите проверить объявления своих функций.
Циклы — это механизмы, не имеющие отношения к функциональному программированию. Взгляните на следующие Python-циклы:
С помощью этого кода мы решаем простые задачи, но получился он довольно длинным. И он, кроме того, не является функциональным, так как тут производится модификация глобальных переменных.
А теперь — ещё один вариант этого кода:
Это — полностью функциональный код. Он короче. Он быстрее, так как тут не приходится перебирать множество элементов массива. И, если разобраться с функциями
Это не значит, что в любом функциональном коде используются
Когда говорят об истории функционального программирования, часто начинают с рассказа об изобретении лямбда-функций. Но, хотя лямбда-функции — это, без сомнения, краеугольный камень функционального программирования, они не являются главной причиной возникновения ФП.
Лямбда-функции — это инструменты, которые можно использовать для того чтобы писать программы в функциональном стиле. Но эти функции можно использовать и в объектно-ориентированном программировании.
Вышеприведённый пример не типизирован статически. Но он, тем не менее, представляет собой образец функционального кода.
Даже хотя статическая типизация добавляет дополнительный слой безопасности в код, она не является обязательным условием создания функционального кода. Она, правда, может быть приятным дополнением к функциональному стилю программирования.
Надо отметить, что на некоторых языках программировать в функциональном стиле легче, чем на других.
В Perl реализован такой подход к работе с побочными эффектами, который отличает его от большинства других языков. А именно, в нём имеется «волшебная переменная»
Желаю вам удачи в деле написания функционального кода на Java. Она вам не помешает. Во-первых, половину объёма кода будет занимать ключевое слово
Это не значит, что Java — плохой язык. Но он не создан для решения тех задач, для решения которых отлично подходит функциональное программирование. Например — для управления базами данных или для разработки приложений из сферы машинного обучения.
Scala — интересный язык. Его цель — унификация функционального и объектно-ориентированного программирования. Если вам это кажется странным, то знайте, что вы не одиноки. Ведь функциональное программирование нацелено на полное устранение побочных эффектов. А объектно-ориентированное программирование направлено на ограничение побочных эффектов рамками объектов.
Учитывая это, можно сказать, что многие разработчики видят в Scala язык, который поможет им перейти от объектно-ориентированного к функциональному программированию. Использование Scala может упростить для них, в будущем, переход на полностью функциональный стиль программирования.
В Python приветствуется функциональный стиль программирования. Понять это можно, если учесть тот факт, что у каждой функции, по умолчанию, есть, как минимум, один параметр —
Clojure, по словам создателя языка, является функциональным примерно на 80%. Все значения, по умолчанию, неизменяемы. А ведь именно это и нужно для написания функционального кода. Правда, обойти это можно, используя изменяемые контейнеры, в которые помещают неизменяемые значения. А если извлечь значение из контейнера — оно снова становится неизменяемым.
Это — один из немногих полностью функциональных и статически типизированных языков. Хотя при его использовании в процессе разработки и может показаться, что на реализацию функциональных механизмов уходит слишком много времени, подобные усилия многократно окупятся во время отладки кода. Этот язык выучить не так просто, как другие, но его изучение — это, безусловно, стоящее вложение времени.
Надо отметить, что сейчас — всё ещё самое начало эры больших данных. Большие данные идут, и не одни, а с другом — с функциональным программированием.
Функциональное программирование, если сравнить его с объектно-ориентированным программированием, всё ещё остаётся нишевым феноменом. Правда, если считать значимым явлением интеграцию принципов ФП в Python и в другие языки, то можно сделать вывод о том, что функциональное программирование набирает популярность.
И в этом есть смысл, так как функциональное программирование хорошо показывает себя в работе с базами данных, в параллельном программировании, в сфере машинного обучения. А в последнее десятилетие всё это находится на подъёме.
Хотя у объектно-ориентированного кода есть бесчисленное множество достоинств, не стоит сбрасывать со счетов и достоинства функционального кода. Если программист изучит некоторые базовые принципы ФП, то этого, в большинстве случаев, может быть достаточно для повышения его профессионального уровня. Такие знания, кроме того, помогут ему подготовиться к «функциональному будущему».
Как вы относитесь к функциональному программированию?

Но это скоро изменится. В такие языки, как 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 и в другие языки, то можно сделать вывод о том, что функциональное программирование набирает популярность.
И в этом есть смысл, так как функциональное программирование хорошо показывает себя в работе с базами данных, в параллельном программировании, в сфере машинного обучения. А в последнее десятилетие всё это находится на подъёме.
Хотя у объектно-ориентированного кода есть бесчисленное множество достоинств, не стоит сбрасывать со счетов и достоинства функционального кода. Если программист изучит некоторые базовые принципы ФП, то этого, в большинстве случаев, может быть достаточно для повышения его профессионального уровня. Такие знания, кроме того, помогут ему подготовиться к «функциональному будущему».
Как вы относитесь к функциональному программированию?

KanuTaH
Забавно, когда Haskell называют "более современным", чем Python или Java, хотя Haskell и Python появились одновременно, а Java — на 5 лет позже них.
Lodin
Тоже об этом подумал. Тот момент, когда автору ну очень хочется написать статью, а разбираться в матчасти лень.
edogs
Можно оспорить факт современности haskell, но дата появления не аргумент.
sshikov
>Haskell называют «более современным», чем Python или Java
То есть, Haskell более соответствует уровню и требованиям настоящего времени? Мне кажется что доказать это в такой постановке будет еще сложнее.
PsyHaSTe
А что не так? Рыбу сайта магазина я за пару часов сделал, сваггер есть, постгрес драйвер есть, веб-фреймворк вообще один из лучших (всяко лучше спринга, asp.net mvc core, express и прочих). Всё для типовой разработки имеется.
sshikov
Осталось это расширить на всю разработку. Яж ничего против хаскеля не имею, я скорее за, но факт что популярности питона и Java он и близко не достигает. Возможно потому что он таки сложнее?
>постгрес драйвер есть
Ну, мне как-то нужно было написать быстро-быстро скрипт, который бы что-то доставал из базы MS SQL. Взял первое что подвернулось, python. Взял первый драйвер для MS SQL. Быстро-быстро выяснил, что varchar длиннее 100 символов обрезаются до 100, плюнул, написал на груви. Ну то есть, меня python в этом случае не устраивал, по моим меркам API для работы с СУБД не работал. Но формально — драйвер MS SQL тоже есть. То есть, в практике есть много других критериев, которым продукт может не удовлетворять.
VanquisherWinbringer
Про реальную разработку это скорее Scala. Вот где уж библиотеки на все что движется и не движется. Летает плавает и вообще. За счет Java конечно. Хотя в целом свои либы есть у Scala и они мне очень нравятся. http4s, fs2, doobie, tapir прям рекомендую. Они прекрасны по моему. Ну и ScalaTest, ScalaCheck, ScalaMock — либы для тестирования тоже по моему конфетки. В целом можно писать настоящий тырпрайс код что собственно и делают 100+ Scala программистов в Тинькоф.
PsyHaSTe
Уверяю, что Persistent не занимается молчаливой модификацией данных, это вообще не в стиле ФП языков.
Но для тырпрайз разработки в скале конечно выбор побольше, там в крайнем случае можно сделать фоллбек на джава экосистему, в которой как известно есть фактически всё.
sshikov
>Уверяю, что Persistent не занимается молчаливой модификацией данных,
Да я и не сомневался. Я немножечко про другое — чем «тырпрайз разработка» в общем отличается от академических проектов, или проектов для себя? Тем что завтра может заявиться к вам условный безопасник (или заказчик), и сказать: «Делайте, что хотите, но чтобы через полчаса в лесу было светло, сухо и медведь!». Ну то есть, потребности тырпрайза широки и многообразны, вероятно сильно шире, чем у типового проекта ради фана. И вопрос пригодности тут стоит немного в другой плоскости. В том числе — в плоскости наличия на рынке выбора хороших разработчиков.
PsyHaSTe
Я занимаюсь типичной тырпрайз разработкой, но у нас никакой безопасник или заказчик не придет и не скажет. Наверное, просто потому что можно работать вне гос/банковского сектора в продуктовой компании. И конечно же мы делаем проект не ради фана, а классический B2B продукт.
Никто не предлагает условный хаскелль как серебрянную пулю, но в некоторых достаточно простых допущениях он хорош. Для бодишопа где у начальника подчиненные — ничего не могущие бездари, а он у них — идиот, такой подход наверное не подойдет, ибо действительно определенная нишевость присутствует. Но компании в которых делается более взвешенное решение получается более конкурентноспособный продукт. Один из моих коллег кстати на следующей неделе выходит на фулл хаскель проект десятым разработчиком.
sshikov
>Наверное, просто потому что можно работать вне гос/банковского сектора в продуктовой компании.
Можно конечно — бывают области разработки, где все попроще и погибче. Но и банковский сектор — это таки жирный кусок разработки. И если не безопасник, то регулятор к вам все равно может зайти.
PsyHaSTe
Банковский сектор да, но кроме него полно разработки. Все эти abbyy/jetbrains/… Это из того где мои друзья/коллеги работают, из известного. И сотни более мелких.
0xd34df00d
Так мы о современности или сложности?
Так-то мне скорее на питоне тяжело софт писать, особенно если он больше нескольких десятков строк — я это вообще не осилил.
Да и вообще, что такое «вся разработка»? С тензорфлоу я скорее буду общаться на том же питоне (или вообще на плюсах), писать же на питоне компилятор или что-то подобное — зачем себя так мучать? Нет ни одного языка, который можно расширить на эту самую всю разработку.
sshikov
>Так мы о современности или сложности?
Мы вот о чем: habr.com/ru/company/ruvds/blog/515684/?reply_to=21987504#comment_21986124 предлагается критерий современности как пригодность для удовлетворения сегодняшних потребностей. Я сразу сказал, что доказать такое (как пожалуй и опровергнуть) будет довольно сложно. В том числе потому, что потребности разные. Как вообще померять это? Понятно что популярность и сложность тут играют роль — но ни на том, ни на другом все не заканчивается.
anonymous
Вопрос же был про «Haskell более современный чем Python или Java», а не просто «Haskell современный».
PsyHaSTe
Ну в хаскелле есть алгебраические типы данных, причем даже в их расширенной версии GADT, тайпклассы, семейства типов, типы высших порядков, rank-N типы (они конечно редко когда нужны, но иногда все же полезны), и так далее
Как по мне, это всё вещи которые будут ещё лет 10+ пролезать в мейнстрим. В расте вон пока только АДТ и тайпклассы завезли, типы высших порядков будут в ограниченном виде в конце этого года, остальное пока даже не планируется.
Так что да, хаскель соверменнее.
VolCh
Тут "современнее" можно говорить если только в роадмэпах других языков есть это. А то так можно сказать, что Хаскель безбожно устарел, ему современные ООП языки лет 10+ догонять )
PsyHaSTe
Ну формально это наверное так, но по ощущениям — нет. По крайней мере в хаскелле у меня не было ощущения что я чего-то не могу сделать, а в шарпе постоянно натыкаюсь на несовершенство языка.
k7paul
А tutorial / пример кода такой вещи есть? У меня Хаскелл как-то никогда за пределы академии (и уж тем более в веб разработку) не выходил
PsyHaSTe
Ну база у меня выглядит так:
HTTP хендлеры выглядят как большинство в других языках:
Регистрация в местном фреймворке тоже очевидная:
В хаскелле любят кастомные операторы для всяких таких вещей, сперва выглядит диковато, но на самом деле доволньо удобно. В частности оператор
<|>
это оператор альтернативы, то есть позволяет из отдельных хендлеров собирать приложение которое обрабатывает все эти роуты.caffeinum
Это отличный комментарий, мне очень не хватало примеров реального кода, когда начинал разбираться в ФП. А то там всегда переход от «вот мы научились класть кошек в монады, а теперь сделайте сами веб-приложение».
Правда, половину вашего кода все равно не понимаю, но теперь хотя бы понятно, что гуглить =)
AnthonyMikh
Если что, "гуглить" лучше на Hoogle, он поддерживает поиск по именам (в том числе операторным) и по тИповым сигнатурам.
edogs
sshikov
Так а я не говорил, что утверждали. Я просто заметил, что в вашей формулировке современности доказать ее тоже будет не так просто.
PsyHaSTe
Он появился раньше физически, но по факту это академический язык был на старте, а Java/Python — нет. А то что академия на 15-20 лет опережает мейнстрим вроде все и так знают.
Так что фактически утверждение верно.
KanuTaH
"А то что академия на 15-20 лет опережает мейнстрим вроде все и так знают" — ну, это уж как повезёт. Из малораспространённых функциональных языков в мейнстрим действительно перетаскивают кое-что, но вот из Пролога, например (который я когда-то даже знал, но уже все забыл, ибо не пригождалось) — нет, он уникальный в своём роде. Ну, точнее, не то, чтобы прямо уникальный, есть ещё Mercury, например, но все это не мейнстрим.
PsyHaSTe
Ну пролог используется, но не для языков общего назначения, а для, например, тайпчекеров. Потому что логику x :- subtype y очень удобно в таком вот виде записывать.
А языком общего назначения емнип пролог если и позиционировался, то недолго. Его удел вроде всегда были всякие экспертные системы и только.
KanuTaH
Я к тому, что не все концепции из академических языков вообще попадают в мейнстрим. Бывает и так, что спустя не то что 15-20 лет, а и все 50 эти языки (и концепции) продолжают применяться очень нишево и в гомеопатических дозах. Поэтому я как-то не уверен, есть ли смысл давать академическим языкам заведомую фору в плане "современности".
sshikov
>Суть функционального программирования — это уничтожение побочных эффектов
Агащаз. То есть печатать, обновлять данные в базах, и прочее и прочее наша функциональная программа не будет? Реальная суть ФП скорее в изоляции функциональных эффектов. Уничтожением это называть просто неграмотно.
Вот делать разработчикам на работе нечего, как только думать «а давай-ка я щас перейду от объектно-ориентированного к функциональному программированию». Разработчики видят в scala язык, который удобно позволяет пользоваться например средствами Spark, и работать с большими данными (примерно в том же стиле, как с данными обычными). Ну или работать с Akka, допустим. Или даже просто взять ScalaCheck, и заняться property based тестированием. Разработчики видят в ФП инструмент, позволяющий им более удобно решать какие-то свои практические задачи.
0xd34df00d
Именно. И если это учесть, то оказывается, что статическая типизация для ФП жизненно важна, как раз для контроля эффектов.
sshikov
Да и не эффектов в общем тоже. Скажем прямо, в scala и тем более Java — не самые сильные системы типов. Однако же, даже такие системы типов дают очень полезные на практике подсказки. Скажем, явно намекая, что получение max от списка дает нам на самом деле не Integer, а Option, потому что непустота списка не может быть доказана вот в этом вот месте, и max для пустого списка неизвестен. А что тут взамен дают динамически типизированные (наверное точнее будет сказать слабо типизированные?) языки? А ничего обычно.
0xd34df00d
Но это полезно и не только для ФП.
dimoff66
В чем их слабость и в сравнении с чем?
sshikov
В сравнении с тем же хаскелем. И с некоторыми не мейнстримными языками.
Я бы не назвал это слабостью. Среди мейнстримных они вполне на уровне. Просто есть реальные примеры, что система типов вполне может быть более мощной.
0xd34df00d
В скале полноценных зависимых типов нет, в джаве всё ещё печальнее.
AcckiyGerman
Пайтон дает взамен исключение, а исключения в пайтоне часто используются, как аналог Option (например функция-итератор возвращает или следующий элемент, или исключение, если элементы кончились).
Вероятно, с точки зрения сильно типизированных языков, такие приёмы выглядят костылями :)
sshikov
Ну как бы вы не знаете заранее, будет исключение или нет. А сигнатуры у функции вообще нет, потому что типизация такая. Ну да, в каком-то смысле костыль, тем более что вернуть что-то вместо max вполне можно было бы — Option же в питоне реализуется, пусть и без гарантий на этапе компиляции.
PsyHaSTe
Суть в устранении побочных эффектов, а не любых. Достигается это выносом эффекта в результирующий тип функции, после чего он перестает быть побочным. Так что писать в БД без побочных эффектов — да, это то что делает ФП.
sshikov
Дело в том, что в обычном, классическом (ну или не функциональном) языке типа скажем C побочные эффекты — это и есть все эффекты. А если вдуматься в вашу формулировку, что мы эффекты делаем явными и выносим в тип — то между «сделать явными» и тем что я понимаю под «изолировать» уже и не будет особой разницы.
В общем, против вашей формулировки я не возражаю, она мне кажется просто взглядом с другой стороны, а вот авторская без пояснений — все равно фиговая.
PsyHaSTe
Практически уверен что он и имел это в виду, поэтому использовал слово "побочный".
А так да, это эквивалентные формулировки.
orthanner
Ничуть. Это именно изоляция. Сам побочный эффект никуда не пропадает. И не перестаёт таковым быть. Сохранение значения в БД, строго говоря, не является побочным эффектом. Это примерно то же, что присвоить значение переменной, просто она не принадлежит вашей программе, а существует вовне (грубо). И вот за это обращение вовне вы платите определённую цену: целевая сущность может отсутствовать, может иметь иной тип, может неожиданно пропасть… и вы никак не контролируете это на стороне вызывающего кода. Неопределённость взлетает до небес, но поведение программы не может быть недетерминированным. Приходится либо умножать сущности и вместо простого сохранения/получения информации получать хитрую структуру данных с кодом ошибки и анализировать его, либо бросать/ловить исключения, либо заворачивать всё в монаду и притворяться, что она и есть наша реальность (потому что как только вы попытаетесь её покинуть, побочные эффекты "сыграют" и разнесут всю чистоту на кирпичики).
PsyHaSTe
Побочный эффект — это эффект, который не видно в результате функции. Запись в базу или вывод в консоль — не побочный эффект если тип функции которая это делает
IO a
. Ну просто потому что проблема побочных эффектов — отсутствие ссылочной прозрачности, которой очевидно не существует для значений типа IO.Существует ли оно вовне, в программе или ещё где совершенно неважно. Я уже писал, ФП — это про ссылочную прозрачность, а побочные эффекты их рушат. Поэтому про то как сделать прозрачные эффекты и говорят так много. Free/Freer/MTL/eff/fused-effects/polysemy/younameit…
В общем, посмотрите определеие, что такое побочный эффект. У людей заблуждение, что любое вычисление которое требует взаимодействие с внешним миром нельзя сделать чистым, поэтому ФП программы ничего полезного сделать не могут, ведь даже если она что-то посчитает, то ответ вывести не сможет!
Только оказывается, что это ложная дихотомия, и вывод в консоль или запись в БД точно так же может быть чистой и без побочных эффектов.
Paulus
Очень интересно, но не понял. А зачем тогда вообще нужны чистые функции? Либо я могу распараллелить foreach на ядра/хосты/кластеры (как если бы всё оставалось чисто), либо после первого же SQL UPDATE сказка кончится и «непобочные эффекты» превратят консистентную до того БД в тыкву?
Или чистые функции нужны для чего-то иного?
anonymous
На самом деле в некоторых случаях можно говорить именно об отсутствии побочных эффектах. Если взять хаскелль и попытаться, скажем, написать программу, которая берёт число из командной строки, удваивает и печатает обратно в консоль, мы обнаружим, что никаких побочных эффектов в коде не будет. Финальным результатом выполнения будет программа, которая читает и пишет в стандартный ввод-вывод, и вычисляется эта программа чисто.
sshikov
Не, ну сам-то эффект никуда не делся, верно? Так что тут налицо скорее отсутствие четкого согласованного понимания, что именно считать побочным эффектом, почему их наличие плохо, и что с ними сделать, чтобы было хорошо.
Мне все же больше нравится формулировка, что мы их изолируем (в случае хаскеля при помощи IO), чтобы было четкое разделение на чистые функции, и функции с эффектом. И тогда уже вопросы типа этого будут иметь четкий ответ — чистые функции можно переупорядочивать, параллелить и т.п. с целью оптимизации, а вот эффекты в общем случае переупорядочивать нельзя, потому что в базе сначала INSERT, а уж потом UPDATE или DELETE. И это должно быть видно в коде, возможно в сигнатурах и т.п.
random1st
Охренел прямо. Подумал, перевод кривой. Ан нет.
bogolt
Вот только не ясно где там селф в функции
Впрочем там не только это неверно написано.
Про то что мол если заменить явные циклы на неявные ( через map reduce ) они почему-то должны выполнится быстрее ( магия не иначе ), про то что если модифицировать входной параметр а не глобальную переменную функция станет уже без побочных эффектов ( по идее модификация входных параметров тоже побочный эффект ).
Lure_of_Chaos
селф не в любой функции, а в функциях-методах классов, для доступа к текущему объекту.
random1st
но не в методах классов и не в статикметодах, а в методах экземпляров классов)
PsyHaSTe
Всё ещё непонятно как добавление self, как и любого другого количества скрытых аргументов, показывает что приветствуется функциональный стиль.
VolCh
Явное лучше неявного. Разве не это заявляется главным плюсом ФП? :)
PsyHaSTe
Ну особой неявности тут нет, все понимают в ООП что в
foo.Bar()
функция имеет доступ кfoo
. Но неявные (или явные) параметры ничего не говорят о ФПшности, потому ФПшность она про результат функции, а не то что она принимает.Lure_of_Chaos
ФПшность как раз в том, что функция принимает ТОЛЬКО аргументы и возвращает результат, при этом ничего не знает о «внешнем мире» и не может на него никак влиять. С такой логикой появляется смысл в «явное лучше неявного». Иначе могут возникнуть вопросы — если функция неявно имеет доступ к чему-то, то не имеет ли она также неявно доступ к ещё чему-то?
PsyHaSTe
bar : (MonadReader Foo m) => Int -> m Int
— вот такой же эмбиент контекст как и у оопшного объекта, в ФПшности от этого никак не потеряли.Lure_of_Chaos
За что и ругают монады
AlexSpaizNet
Конечно создание всегда новых объектов избавляет от неприятных багов в многопоточном программировании. Или даже в JS где один поток, можно намудрить так что объект будет изменен кем-то другим по ссылке, это как бы здорово… если бы не GC который будет забирать у вас 80% CPU тупо на очистку памяти…
А в остальном, сколько не пытаются преподнести фп как спасение от всех бед, на практике, как я вижу, используется в очень узких кругах… и в очень специфических задачах.
Я лично использую только для простенькой фильтрации или трансформации небольших объектов когда знаю что их количество ограничено числом N, и это число меня устраивает.
0xd34df00d
Только практика показывает, что даже весьма мусорящие программы тратят на GC от процента до, ну, навскидку, 10-20 процентов времени работы, если там вот прям совсем много мусора.
Конечно, при этом всегда можно написать так, что всё будет сильно хуже.
AlexSpaizNet
У вас никогда не было задержек в 15-30 секунд когда gc останавливает все что бы очистить память? Задача задаче рознь. Одно дело в воркере что то делать, с этим еще можно жить. А вот в веб сервере, когда это напрочь убивает весь перформанс, уже другое.
Самое простое например у нас, это работа с календарем. 365 дней в году.
Разная метадата на каждый день, потом накидываем всякие бизнес рулы на каждый день в отдельности, вот уже память и скачет.
Нет, конечно по возможности делаем прекалькулейшен и храним где-то, но по большому счету очень много операций синхронных, когда ответ нужно дать asap, бывает один такой вот тяжелый ендпоинт не дает спать по ночам, когда алерты приходят =))
PsyHaSTe
Настройте гц на низкие задержки, делов-то. В современных жабовских ГЦ например терабайт мусора (да, там по несколько терабайт памяти ставят в машины) очищает за считанные микросекунды.
А у большинства людей с кучей <= 4 гигов проблем вообще никаких.
AlexSpaizNet
Ну у вас это жаба… у нас nodejs. Возможных настроек минимум. На пиковых нагрузках GC заметно тормозит когда создается очень много short-lived объектов. Можно конечно расти вертикально пока не упрешься в потолок или бюджет… и все же, просто игнорировать GC и другие накладные расходы в статьях про ФП это как-то не правильно.
PsyHaSTe
Ну у меня не жаба, а дотнет, но там тоже проблемы редко бывают. Пару раз только кастомизировал гц опциями, всегда этого хватало.
ФП к эффективности ГЦ слабо относится, одно — язык, а другое — рантайм. Если же сравнивать у существующих языков, то у Haskell/Scala достаточно эффективный ГЦ, гигабайты мусора в секунду по крайней мере они собирают без каких-либо задержек со стороны приложения.
dimka11
На хабре была статья, где сравнивалась производительность функционального и императивного кода на Scala и Rust.
И функциональный код на Scala работает намного медленнее, потому что требует создания и очистки многих объектов.
sshikov
Хм. Насколько я помню, вы сейчас общаетесь именно с автором той статьи :)
PsyHaSTe
У меня есть пример когда человек спрашивал в телеграме почему его программа на хаскелле работает быстрее чем на расте. Оказалось, что он всё завернул в боксы и естественно аллокатор раста помер на этом, а гц пережевал гигабайты мусора без каких-либо проблем.
Нашел кстати пост, комменты выше есть если хочется восстановить контекст: https://t.me/rustlang_ru/243901
wheercool
Пул объектов пробовали использовать?
0xd34df00d
Неа, не было.
Из примеров серверов — ну, например
Проблем с GC не было ни разу.
AlexSpaizNet
И сколько запросов в секунду у вас в пике на этот ендпоинт и какова продолжительность этих пиков? У каждого разная нагрузка и поведение клиентов.
0xd34df00d
Во втором случае — порядка десятка тысяч, продолжительность — минуты или десятки минут. В первом я через ab тестировал, не помню уже, сколько получилось.
Впрочем, сравнивать RPS имеет смысл только для каких-нибудь echo server'ов. Так ведь совсем упускается, что оно при этом внутри делает и сколько мусора генерит непосредственно внутри бизнес-логики.
allter
Специально зашёл с десктопа и грепнул слово «композиция». Пардон, но вот эта изоляция (а не уничтожение) побочных эффектов в ФП не самоцель, а нужна для облегчения композиции индивидуальных функций и целых кусков кода.
Что бы мы могли в любой момент заменить
на
и наоборот.
При этом это только пример, а реально это используется для того, что бы безболезненно развивать существующие системы и, как минимум, адекватно тестировать написанный код.
0xd34df00d
Только вы это сделать в общем случае не можете в автоматическом режиме, если вас заботят рантайм-характеристики вашей программы (хотя кого в ФП-тусовке они заботят), потому что в одном направлении вы увеличиваете количество работы, а в другом — можете сделать space leak (особенно в ленивом ЯП).
allter
Что интересно, в ФП при необходимости, как правило, проще рассчитать требования программы к ресурсам. Но да, ФП — это прежде всего про корректность, а не про производительность. Но про стеко- и хипобезопасность надо думать, да (но в императивном программировании об этом, вроде, надо думать ещё больше).
sshikov
В случае ленивости — не факт что проще.
PsyHaSTe
Чтобы не сделать space leak достаточно аллокации тоже вынести на уровень типчиков, AllocMonad или что-нибудь в таком духе. Но не думаю, что с ней прям будет сильно удобно жить.
Groosha
Объясните, пожалуйста, где здесь ускорение? Чтобы получить массив квадратов чисел, нужно в исходном массиве их все перебрать. Или нет?
PsyHaSTe
Речь о том, что каждый элемент массива посещается один раз. Потому что итераторы фьюзятся, и вместо трех циклов над требя коллекциями получится один цикл которые делает все три действия (фильтр, отображение, свертку).
Но написано через одно место, да.
sshikov
>Потому что итераторы фьюзятся
Это особенности (правильной) реализации. Если тупо взять какую попало реализацию где-нибудь в js, то от замены цикла на map/reduce/filter как раз наоборот производительность может пострадать, и очень сильно.
PsyHaSTe
Это особенность конкретно итераторов, а в жс эти функции реализованы на массивах. Если же взять жсовские итераторы то получите все те же плюсы.
sshikov
А автор не пишет про это ничего. В чем и разница между вашим комментом и оригинальным постом.
iHun
Да, в общем-то, нигде.

Статья в целом не очень качественная.
vitalijs
Сия статья опоздала года на 3. Функциональные языки опят вышли из моды
PerlPower
Однако же надо как-то заходить на новый виток.
Nikita001
Описание Scala могло бы быть и получше.
Например, можно было бы упомянуть, что Scala это Java + типизация + ФП + Иммутабельность + Акторы (в т.ч. распределенные между хостами) + Spark + компиляция в JS + Scala Native
maxzh83
Это имеет к Scala такое же отношение как Spring к Java, т.е. прикладное и весьма опосредованное
plyatov
ФП несомненно полезный стиль программирования, но во встраиваемых системах, с ограниченными ресурсами ОЗУ и частотой ЦПУ приходится им часто жертвовать.
Потому, что передавая кучу входных аргументов функции, потребуется дополнительное время ЦПУ и свободное место в стеке. А их, зачастую нехватает. Вот и приходится выкручиваться используя глобальные переменные или передавая функции один указатель на структуру, как входной аргумент.
Borjomy
Ну как сказать. У нас был обратный пример. Взяли программиста, который наваял программу для контроллера на C++. Не, парень молодец, быстро врубился, написал правильную программу, используя ООП, но… процессорной мощности на нее не хватило, причем радикально. Пришлось ему переписывать без объектов, на чистом C.
PsyHaSTe
Не понял почему это обратный пример. В обоих случаях приводится пример, что абстракции иногда не позволяют писать эффективный код. Но иногда наоборот помогают. Так что тут в каждом случае нужно разбираться отдельно.
AlexSpaizNet
Извиняюсь, а причем тут чистый С к функциональному программированию? Вы точно переписали на фп или все таки это обычный процедурный код?
Borjomy
Этой истории десять лет, как минимум. Там ни о каком ФП в чистом виде речи и не шло.
pavlushk0
"неизменяемыми переменными" — оксюморон, отличное начало простого объяснения концепции.
PsyHaSTe
В англоязычной среде immutable variable видимо тоже дурачки пишут.
Что поделать, смысл слов со временем меняется. "Незименяемая привязка к имени" мб и более корректна, но звучит ужасно. Иначе не было бы в языке слов вроде "сухая вода".
VolCh
Если принять определение переменной как "именованная область памяти", то нкмзменяемая переменная — это именованная нкмзменяемая область памяти. Вроде норм.
sshikov
Ну просто само слово «переменная» как-бы подразумевает, что она может меняться. Хотя я вот не улавливаю, откуда тут могут быть сложности с пониманием — эта самая неизменяемость она вполне типична для математики, где переменные как правило вычисляются один раз, и больше не изменяются. То есть по сути — immutable. Не, ну если человек математику учил много лет назад в школе и не применял — то наверное привыкнуть к этому после императивных языков не просто.
mayorovp
Слово "переменная" означает, что она может принимать разные значения. А "неизменяемая" означает, что после принятия значения оно посередине вычислений поменяться уже не может.
Вспомните математику. Рассмотрим функцию
f(x) = 2x+1
. Можно вычислитьf(2)
, а можно вычислитьf(3)
. В одном случае икс будет равно двум, а во втором — трём, поэтому икс — переменная. Но неизменяемая, изменяемых переменных в математике нету.sshikov
Ну я вроде примерно так и написал.
mixsture
Думаю, что направление развития языков совпадает с направлением развития компьютерных железок.
Вот в процессорах мы уперлись в максимальную тактовую частоту 4ггц. Усовершенствуем в них конвеер, кеш, системную шину, но в целом частота все та же. Зато пределов многоядерности еще не видно.
А с другой стороны память — ее объемы и скорость доступа неуклонно растут и пока предела тоже не видно.
Отсюда выгоднее для всей индустрии писать слабосвязанные куски кода, пусть даже ценой большего расхода памяти — потому что они лучше параллелятся.
commenter
Опять всё тот же набор штампов. И все штампы с доказательствами вида: а у нас получается короче. Но «короче» получается только потому, что вместо полной реализации алгоритма «знатоки» показывают нам вызов функций, реализацию которых никогда не объясняют (ссылаясь на «это же все знают», ага).
И кстати, фанатов ФП (активно засирающих комменты) всего-то 3-4 человека. Хотя да, комментов они наплодили бесконечное множество. И вот такое море флуда нам выставляют как доказательство того, что «разработчики влюбляются в ФП». То есть тупо гонят агрессивную рекламу, максимально массово и полностью безапелляционно. Выхватывают какую-то частность и уводят куда-то в дебри (теория категорий, ага). Но для непосвящённого читателя всё выглядит оживлённой дискуссией. Только нормальным людям никогда в голову не придёт спорить с уводящими в теории категорий про то, что на самом-то деле от циклов никуда не денешься. Поэтому массовый вброс от всего 3-4-х человек кажется «аргументом» в пользу ФП.
Ну а основной аргумент против — никто ещё не сумел с помощью ФП решить ни одной задачи, которую бы до них не решили императивно.
Поэтому фанаты ФП — это фанаты стиля. Я понимаю, скажем, математиков — они привыкли к математическому виду своих рассуждений, и тут хаскель с его очень похожим подходом, ну и математиков «попёрло». А все остальные — либо не понимают и просто следуют за авторитетом математики, либо… Ну в общем в обоих случаях — не понимают.
Даже больше — агрессивное отношение фанатов ФП ко всем «не фанатам» губительно для ФП. Но фанатам этого не понять.
vedenin1980
Ну не совсем, элементы ФП (неизменяемые структуры, функции без побочных эффектов) очень помогают для многопоточного программирования (например, в Java). Поэтому, как минимум, элементы ФП в ООП/императивном языке — полезны.
commenter
Обсуждалось. Это не «элементы ФП». Это давно известные способы написания процедурных программ.
Если есть желание, вы можете писать без побочных эффектов, если нет желания — можете с побочными. Это называется «свобода». В ФП же свободы нет. Только чистые функции. То же самое касается всего остального. Вплоть до передачи функции в качестве параметра — это тоже ФП заимствовал из императива.
Фанаты ФП вещают из башни, с которой сами не видят мир. Они видят лишь своё, видят его удобство, но при этом понятия не имеют, откуда растут корни этого удобства. Потому что бросились сразу от математики зубрить хаскель.
@mayorovp
Вы конечно же считаете себя умным, но суть возражения просто не поняли.
mayorovp
Свобода ведёт к хаосу, хаос ведёт к багам, притом количество багов растёт в лучшем случае как квадрат размера кода, поскольку баги "прячутся" в паразитных связях между частями программы.
Чем сложнее программы, тем важнее наводить в них порядок. Да, это всегда происходит ценой ограничения свободы программиста. Статическая типизация, обобщенный код, LSP, ФП, завтипы — всё это инструменты, разменивающие свободу программиста на уменьшение количества тех самых паразитных связей.
commenter
О да, достаточно продекларировать некий набор слов, и доказательство крутости ФП в кармане!
Свобода расширяет возможности. Несвобода сужает. Вроде бы очевидно, но нет, адепты «несвободы» увидели хаос…
Ладно, давайте более конкретно. В JavaScript нет возможности ограничивать себя типами данных. То есть в JavaScript меньше возможностей. В типизированных языках возможность ограничивать себя типами данных есть. То есть в типизированных языках больше возможностей. К типизированным языкам относятся как множество императивных, так и множество функциональных. То есть фанатам ФП должно быть стыдно за критику в адрес наличия дополнительных возможностей, которые присутствуют и в их любимом подходе к программированию.
Далее сравниваем. ФП запрещает нам целый ряд конструкций. Здесь уместно вспомнить JavaScript, который точно так же запрещает нам целый ряд конструкций. И на этом фоне есть императивные языки, поддерживающие то, что не хочет JS и то, что не хочет ФП. То есть возможностей в императиве больше, чем в ФП и в JS. Но да, я понимаю, признавать своё поражение неприятно…
Хотя… ещё раз ладно. Объясню ещё нагляднее (а то-ж не все понимают). Вы что-то там про зависимые типы говорили. Было? Так вот, это тоже есть расширение возможностей. Хочет кто-то их использовать — использует. Не хочет — пишет на подможестве языка а-ля чистый хаскель. Теперь сравниваем с императивом — там тоже если кто-то хочет — пользует чистые функции и всё прочее, а если не хочет — не пользует. И вот приходит фанат ФП и заявляет — свобода ведёт к хаосу! Это таким образом он запрещает нам (сторонникам императива) свободу в выборе способа написания наших программ. Но сразу после этого тот же самый человек заявляет — завтипы наше всё! И получается, что в одной ситуации он против, а в другой, полностью аналогичной первой — он «за».
Павел (скорее всего вас так зовут), вы пристрастны. Хотя вроде бы и не сторонник душить за альтернативное мнение (то есть сами ищете истину). Но ваша пристрастность душит ваши собственные позывы.
vedenin1980
Вы почти всё правильно поняли. Различие в спорах — только стилистическое. Но ваша картинка, к сожалению, относится именно к фанатам ФП. Точнее — к худшей и небольшой их части, которая владеет рядом умов на этой площадке.
Здесь всего-то несколько активных фанатов, толкающих идею исключительности ФП в массы. Но у них есть то ли клонированные юзеры, то ли группа поддержки из студентов-одногруппников. Гляньте на мою карму — почти все минусы от них за участие в подобных обсуждениях. За каждое обсуждение 10-15 минусов. Но вроде у засранцев более нет клонов (уже не портят карму). И вот эта ничтожная группка и создала образ «крепости на том берегу», прямо как на вашей картинке.
Надеюсь, читающие это обсуждение не повторят ошибок тех фанатов ФП, которые следуют жестокому правилу «давить любые возражения», и всё ведь исключительно ради поддержания ЧСВ этой ничтожной группки зазнавшихся и очень обидчивых «молодых лидеров».
mayorovp
Если у вас статическая типизация — это расширение возможностей по ограничению себя типами данных, то и ФП — это такое же расширение возможностей по ограничению себя типами побочных эффектов.
commenter
Вы опять не поняли сказанного. Уже настораживает.
Объясняю. В ФП нет других вариантов, кроме того, что требует ФП. А в императиве есть. В ФП один подход, а в императиве минимум два. Внимание, детский вопрос — где больше? Один или два? Вы выше заявили, что один больше двух. Поэтому срочно просыпайтесь и не пытайтесь отвечать как всегда — первой пришедшей в голову мыслью. Она у вас всегда ошибочная. Доказано хотя бы данной перепиской.
mayorovp
Я "проснусь" только после того, как увижу код (на любом из известных мне языков), аналог которому ну вот никак нельзя написать в рамках ФП.
VolCh
Удар ниже пояса почти :) Ведь вроде как машина Тьюринга и Лямбда-исчисление эквивалентны. То есть "будить" вас нужно, залезая в конкретные детали реализации и "разбудить" без большой практике в ФП не получится ))
PsyHaSTe
Хэшмапу смогли реализовать только когда в хаскель линейные типы затащили. Или какую-нибудь инплейс быструю сортировку.
По сути, все что в ST это такой алгоритм.
0xd34df00d
Но ведь
ST
описывается в рамках ФП.А хешмапы были и чистые (в
unordered-containers
), и вST
(вhashtables
, ЕМНИП).PsyHaSTe
Что там от этого ФП остается. По сути эмбед императивщины в монаду же.
А то так остается заключить, что кроме ФП ничего и не существует, просто весь код в императивных языках неявно заключен
IO a
, который не пишется по тем же причинам, почему никто не пишетT | bottom
для каждой нетотальной функции.0xd34df00d
Контроль за тем, что императивщина не утечёт за пределы
ST
, например. Или что вы из двух разных зелёных или ОС-тредов не поменяете одну и ту жеST
-переменную без синхронизации.Да, так и есть. По крайней мере, я считаю эту ментальную модель наиболее продуктивной и соответствующей реальности, и давно за неё топлю.
В самом деле, нет вообще никакой концептуальной разницы между
и
PsyHaSTe
Ну лично мне ближе идея к тому, что ФП — это когда код не использует лишних возможностей. То есть функция возведения в квадрат в стд не имеет вид
Int -> IO Int
.0xd34df00d
Конечно, если есть возможность отделить
Int
отIO Int
, то глупо этим не пользоваться. Но вот тот жеST
решает точно такую же задачу: показывает, что есть мутабельность, и что если хочется из неё выйти, то надо сделатьrunST
. Линейные типы тут не очень помогают, потому что существуют программы, типизируемые вST
, но нетипизируемые в линейных системах типов (потому что линейные системы типов — это ещё одна статическая аппроксимация рантайм-поведения).svr_91
А как определить, есть возможность отделить или нету? Может проще весь код писать в ST/IO?
0xd34df00d
Ну, нужна явная мутабельность — пишете в ST/IO/PrimMonad/etc, не нужна — не пишете. Если функция, которую вам надо вызвать, живёт в ST или IO, то у вас нет выхода, кроме как самому переселяться в ST или IO.
svr_91
И как определить, нужна мутабельность или нет, например из примера выше с hashtable?
0xd34df00d
Функции из unordered-containers чистые, для тамошней хешмапы не нужна. Фукнции из hashtables живут в ST и IO, там она нужна (и компилятор не даст функцию в IO вычислить в чистом контексте, если не учитывать всякие
unsafePerformIO
, которые легко выгрепать).svr_91
Ну вот я написал функцию, которая требует ST. Что дальше? Как выяснить, действительно ли там нужно ST или нет? В противном случае можно просто все в ST и IO реализовать
0xd34df00d
В общем случае — никак, соответствующего алгоритма не существует по близким к Тьюрингу причинам, поэтому приходится включать голову.
Например, если вы написали функцию, которая отбивает строку слева пробелами, а там нужно IO, то что-то не так. Или если вы написали функцию, которая находит наикратчайший путь в графе, а она в IO, то что-то снова не так.
svr_91
Ну так можно и в других языках проверять.
0xd34df00d
Так — да. Но, опять же, смысл всех этих систем типов не в том, чтобы проверить, что «если функция объявляет, что она делает IO, то она на самом деле делает IO», а, наоборот, проверить, что «если функция объявляет, что она IO не делает, то она на самом деле IO не делает».
svr_91
Вопрос в том, насколько позволено допустить «проникновение» монад в код? Не получится ли из монад «эффект разбитых окон»?
«А почему этому мальчику можно добавлять монады в код, а мне нельзя?»
telhin
Потому что писать с монадами чуть больнее. В ООП если полениться, то можешь сломать архитектуру/абстракцию, в Haskell нужно быть активным придурком. Чтобы сломать, прикладывать усилия нужно.
AnthonyMikh
Столько лишнего кода вместо
add = liftA2 (+)
mayorovp
Это ж пример. Для более сложной функции аналог
(+)
придётся всё равно самому писать — а в таком случае уже непонятно зачем нужнаadd
.VolCh
А если я решу, что этой функции нужен файловый кэш, чтобы не тратить уйму вычислительных ресурсов на решение этой сложной вычислительной задачи, и хочу его прозрачно для потребителей добавить. Я так понимаю, что в рамках типичных функциональных языков эта задача нерешаема?
telhin
Что за формулировка вопроса! Утрируя можно перефразировать:
— IO не нужно при простой работе со строками.
— Я хочу работу со строками и файлами.
— Для работы с файловой системой нужно IO.
— Хочу файловую систему без IO монады. Я так понимаю, что в рамках типичных функциональных языков эта задача нерешаема?
VolCh
Можно перефразировать так: Получается в популярных (для ФП) языках нельзя прозрачно для потребителя добавить в функцию нефункциональные сайд-эффекты? Примерно как в JS нельзя прозрачно для потребителя отрефакторить функцию так, чтобы она начала использовать await?
chersanya
Насколько я знаю, предполагается такой подход для кеширования: делаете свою монаду, которая позволяет не все действия из IO, а только чтение-запись в конкретный кэш; используете её в функциях, которые связаны с кэшем; профит. Да, если до этого был вызов такой же функции, но без кэша, то его надо будет поправить. Однако зато получаете гарантии, что ни в какие другие файлы/сеть/etc эти функции не лазят — только в кэш.
PsyHaSTe
"Решаема" хаками которые позволяют выполнять ссылочно непрозрачный код, но да, если вам вдруг захотелось файловый кэш использовать, это должно быть видно из сигнатуры. Но я бы не хотел использовать код, написанный с таким подходом.
Я кажется рассказывал историю, как у меня в сишарпе в одной библиотеке безобидная функция для перевода времени из одного часового пояса лезла в БД через статический глобальный коннекшн потому что там хранились информации по таймзонам и оффсетам. А в сигнатуре ничего, безобидный
DateTime -> DateTime
.То что добавление кэша ломающее изменение — хорошо и правильно.
Более правильным решением написать функцию относительно любого эффекта который умеет то что нужно.
foo :: (Monad m) => m ()
. И дальше если вам эффект не нужен, то вы просто передаёте пустой эффект Id, а если нужен, то передаете что нужно: кэш или еще там что-то.St_one
PsyHaSTe
Ну это буквально "я работаю с любой монадой". То есть функция будет пользоваться тем эффектом, который передан аргументом. Например, у меня есть вот такая хелпер-функция в идрисе:
Который работает для любой монады. Можно проверить для m = Maybe, m = IO, m = Async, ...
Пример использования:
St_one
да, но функция, грубо говоря, будет просто работать с монадическим интерфейсом. Т.е. различным образом соединять монадические комбинаторы, зацикливать вычисления и т.д.
Но вот сегодня не делать ничего, а завтра полезть в файл она не сможет.
PsyHaSTe
Да, чем более общая сигнатура, тем меньше возможных реализаций у функции, и тем проще судить о том, что она делает. Параметричность-таки )
svr_91
> Я кажется рассказывал историю, как у меня в сишарпе в одной библиотеке безобидная функция для перевода времени из одного часового пояса лезла в БД через статический глобальный коннекшн
То есть если бы в языке был запрет на глобальные переменные, то это решило бы все проблемы? Или чтото еще нужно?
PsyHaSTe
Мне не нужен запрет на глобальные переменные, более обще запретить эффекты. Я знаю, что функция вида
Int -> Int
И я хочу видеть эти опции из сигнатуры.
svr_91
> И я хочу видеть эти опции из сигнатуры.
Допишем к функции суффикс Pure?
Плюс, если действует запрет на глобальные переменные, то что из вышеперечисленного такая функция сможет сделать?
PsyHaSTe
Я не верю в соглашения, только в компилятор
Вызвать сишный printf?
svr_91
> Вызвать сишный printf?
Хм, я подозреваю, что сишный printf тоже использует глобальные переменные.
С одной стороны можно точно также запретить в языке неявный printf. С другой стороны, иногда конечно всетаки хочется без лишнего геморроя вывести чтото в лог.
chersanya
… или отправить по сети, или считать файл, или многое другое. Запись эффектов явным образом в сигнатуре функции делается как раз для того, чтобы вызывающий код знал если что-то такое может произойти.
PsyHaSTe
А питонистам хочется иногда без лишнего геморроя прицепить к объекту какие-нибудь динамические данные или иногда вместо числа возвращать например строку. А в какой-нибудь джаве гадкие типы им мешают и не дают этого сделать.
svr_91
Вот видите. В питоне не мешают и они это делают. В яве мешают и они это делают. А что в хаскеле мешает писать все в ио?
PsyHaSTe
Точно так же как типы джавы не разрешают просто так прицепить что-то к объекту, типы хаскелля не разрешают просто так "вывести что-нибудь в лог". Я пользуюсь джавой потому что она не разрешает цеплять к объектам мусор, и пользуюсь хаскелем потому что я вижу что функция не делает под шумок какой-нибудь дичи.
AnthonyMikh
Debug.trace
))00)0))))PsyHaSTe
Не уверен что им получится воспользоваться с
{# SAFE #}
А так, если есть такое чудовищное желание стрелять по ногам, то не нужен хаскель с его бойлерплейтным MTL, можно прям на джаве фигачить и все будет летать.
VolCh
А можно чуть подробнее? Почему врёт? Кому врёт? При компиляции C-кода void функции с return или неявным return будет однозначная ассемблерная конструкция RET без какого-либо возвращаемого значения в стэке, регистре, или где там по очередной конвенции о вызове. (бывают исключения в этих конвенциях). То есть реально ничего не возвращает.
На предыдущем комменте про это удержался, а теперь нет )
mayorovp
Насколько я понимаю, потому что в стандарте написано, что void — ненаселённый тип, а на самом деле это что-то вроде unit.
Хотя я тоже не понимаю что именно делает
struct Unit
в этом коде.0xd34df00d
Там чуточку сложнее всё. В стандарте объект типа
void
создать нельзя (то есть, он в этом плане ведёт себя как ненаселённый), но при этом вы можете написатьНо написать
не можете.
Ничего полезного, его оттуда можно выкинуть. Там сначала был чуть более сложный пример, но я подумал, что для иллюстрации этого тезиса он не нужен, и пример упростил, а
void
иUnit
выкинуть забыл. Хотя, с другой стороны, так о войдах думать не нужно и учитывать их в предлагаемом изоморфизме.PsyHaSTe
Ну я лично использую юниты для генерик-кода, где я не хочу делать специальный кейс на случай если возвращаемое значение внезапно юнит.
0xd34df00d
А неважно, что там получается при компиляции. Важно, что с точки зрения всякого там матлога функция, которая ничего не возвращает, либо никогда не завершается (условный
std::abort
— хороший кандидат, кстати, на то, чтобы на самом деле ничего не возвращать), либо её невозможно вызвать, потому что хотя бы один из её аргументов «не существует» (и поэтому во всяких теориях типов логическое отрицание утвержденияT
записывается как функция с типомT -> Void
).0xd34df00d
Потому что завтипы позволяют ещё больше ограничивать некорректные поведения программ (и, среди прочего, допустимые эффекты).
vedenin1980
У вас прямо все по этой картинке
mayorovp
Вы, конечно же, можете писать одни и те же алгоритмы каждый раз сами, но я предпочитаю использовать библиотечные функции. Даже если я не знаю их реализации (но это редко, потому что я любопытный).
vedenin1980
Я может чего-то не понимаю, но разве тут append_to_list2 — чистая функция?
По идее, она была бы чистой, если мы возвращали новый массив с значением 1, тогда ее всегда можно было бы заменить на результат вычисления. А тут в полный рост встает вопрос многопоточности и прочего (можно легко получить ошибки или неверные значения если тот же код выполняется во множестве потоков).
То есть, получается автор статьи сам не понимает, что такое функциональное программирование и чистые функции?
vedenin1980
Странно, минусы поставили, но на вопрос чистая ли это функция и функциональное программирование не ответили (это не риторический вопрос, мне действительно интересен ответ).
PsyHaSTe
Если натянуть сову на глобус и считать что append_to_list это функция в монаде то получится чистая.
Но вообще я бы её такой не назвал.
thealfest
Так еще короче. Чем в данном случае такой код хуже?
Borjomy
сама по себе парадигма замены цикла списком — глупость и нишевое решение. Например что делать, если количество итераций — расчетное? Я уж не говорю про то, что объем кода становится пропорционален количеству итераций цикла. Посмотрю, как программист будет набивать список из сотни-другой элементов. А еще можно ожидать глупых багов из-за пропусков в списке или дубликатов. Как их выгребать?
sshikov
Это всего-лишь пример. В реальной жизни список вероятно не будет константой. Даже если нужен список целых от и до, во многих языках есть средства генерации таких последовательностей, причем без сохранения куда-либо. В Scala, для примера, это будет просто 1 to 6.map...filter...etc
>если количество итераций — расчетное
Это логичный вопрос, но такой цикл не заменяют map-ом.
thealfest
integers не обязан быть списком, он вполне может быть генератором.
sshikov
>Так еще короче. Чем в данном случае такой код хуже?
Ну он возможно будет несколько хуже расширяться (хотя честно говоря, и оригинальный авторский код в этом смысле так себе). map и filter это средства композиции решения из частей, а эта композиция имеет свойство меняться, в том числе при изменении постановки задачи.
То есть, если у вас есть набор функций, f1, f2, f3, вы можете скомпоновать из них обработку как integers.map(f1).map(f2).map(f3), а можете построить композицию функций f123, и даже доказать, что integers.map(f123) будет эквивалентно. А в вашем варианте эти части где? i*i, i%2? Для однострочника норм, а в перспективе может оказаться не очень.
PsyHaSTe
comprehension это и есть короткая запись для filter/map, но если добавить чуть больше шагов (например группировка после фильтрации но до отображения) или любые другие нетривиальные шаги то всё развалится.
Borjomy
«Понравился» раздел Map и Reduce
что делать, если количество итераций цикла не 6, а 1000 или 10000? И мне вот интересно, как выгребать ошибки в составлении списка «integers».
PsyHaSTe
например вот так:
Примеры
[1,2,3,4,5,6]
составляют для наглядности, это не всегда скопированный из прода код.Borjomy
эта «наглядность» только вредит и принижает интеллектуальные способности автора статьи. ИМХО. Сначала речь про максимально эффективный код, а в нем такая конструкция, со списком чисел. И ладно бы сущности описал, так нет — числа.
Просто я ООП в работе мало пользуюсь, больше аналогом ФП. Мне вот такие сентенции в качестве примера не слишком понятны.
maxzh83
Чистые функции, лямбды и т.д. это все прекрасно. Но лично мне хочется примера программы в функциональном, в которой будет реализован банальный счетчик, т.е. тупо по событию что-то считается. И вот тут будет интересно посмотреть как получится добиться иммутабельности и прочих концепций.
mayorovp
Ну вот так например (RxPy):
1nt3g3r
Эм, джава, которая в куче энтерпрайз софта, и которая отлично работает с базами данных, оказывается не для этого создана?
Как мне кажется, для работы с базами данных джава отлично подходит. По логике в статье метод, в котором вставляются данные в БД (insert) уже не будет чистым — ведь он ничего не возвращает. Но в реальной жизни у нас есть изменяемое состояние (та же БД), и пример выше кажется надуманным.
VolCh
Функция вставки записи в таблицу, примет на вход таблицу и запись и вернёт новую таблицу :) Или даже примет базу и вернёт новую базу
mvv-rus
Про Java и БД.
Что-то мне подсказывает, что автор статьи записал в функциональные языки ещё и SQL.
Участовать в споре функциональный ли язык SQL или нет, я не собираюсь, но, вообще-то, нечто общее у SQL с ФП есть — и уж, по-любому, в SQL реализована никак не императивная парадигма программирования.
А то, что SQL в качестве языка доступа к данным в БД (DML) сильно лучше императивных языков — это, думаю, ощутил на своей шкуре всякий достаточно опытный программист, которому приходилось в качестве DML где-нибудь в 1-й половине 90-х помучаться с чем-нибудь из серии Clipper/FoxPro/Paradox для написания запросов на получение данных из нескольких таблиц, да ещё и с фильтрами по значениям полей — тем, что в SQL делается в одну строчку.
vedenin1980
Всегда? Ок, простая разминка для ума, у вас есть таблица с значениями id, parent_id описывающие дерево, силами чистого SQL найдите самую длинную ветку от корня.
То что не все так просто с SQL говорит тот факт, что почти любая база реализует какой-нибудь PL-SQL или Transact-SQL, которые уже явно не чисто функциональные.
Плюс, вы просто всякие hibernat'ы не встречали, существует множество библиотек, позволяющих из импреативной программы делать запросы ничуть не хуже чем SQL, например просто превращать иерархию классов в таблицы базы данных (у них свои проблемы с производительностью, но вот кое какие вещи на чистом SQL писать намного тяжелее).
mvv-rus
Не всегда. Конечно, с помощью SQL нельзя сделать всё. Но очень многое можно сделать куда проще, чем расписывая вручную выполнение запроса циклом(ами) в императивном языке.
За стандартный SQL не скажу (стандартов не читал давно), но вот в Transact-SQL для MS SQL Server рекурсивное получение данных из таблицы с id и parent_id, хранящей дерево (или даже целый лес), в виде набора записей, содержащих и вычисленную длину от корня дерева, вполне возможно и в чисто декларативном стиле — через использование Common Table Expresions (CTE) и UNION ALL. Как-то, типа (синтаксическую проверку не делал) так:
Ну, а в оконечном SELECT (который я в простейшем виде записал) можно уже делать что нужно — например отсортировать записи в порядке убывания node_level и взять верхнюю.
Да, средства построения запросов SQL разной степени навороченности существуют, естественно: иначе бы программистам, которые обучены обычному императивному программирванию (а таких, наверное, большинство) было бы совсем грустно писать свои программы, которым по жизни часто с БД работать приходится.
vedenin1980
В результате вы сами себе противоречите — с одной стороны, что любой программист должен считать SQL самым лучшим языком для работы с БД, с другой внезапно большинство программистов внезапно вместо SQL используют совсем другие средства в качестве обертки над SQL, вместо того чтобы писать простыни SQL запросов в программе.
На самом деле, SQL, на мой взгляд, далеко не самый удобный и простой способ работы с БД, SQL больше похож на ассемблер или javascript — нативный, быстрый (по сравнению с обертками над ним), но многословный и тяжелый для написани и использования. Отладка и написание огромных простынь SQL запросов — боль, а попытка запихнуть вcю бизнес логику в какой-нибудь PL-SQL — создает монстров.
mvv-rus
Противоречия нет — есть нюанс: не только лишь все программисты умеют в SQL.
Конечно, сферический программист в вакууме, в совершенстве знающий все языки, чаще (значительно чаще IMHO) будет использовать SQL для доступа к данным в базе. Но вот реальный программист, которого удалось нанять на проект за разумные(на самом деле — не совсем, потому что «всем нужен программист») деньги, от SQL с немалой вероятностью окажется далек — чисто потому, что SQL не похож на его основной рабочий язык. И вот потому применение всяких средств уклонения от написания кода на SQL — от ORM до конструкторов запросов (которые в GIU мышкой) оказывается более чем оправданным.
Но у этой опраданности есть и другая, тёмная сторона. В качестве примера её расскажу историю. Я как-то (в довольно давние уже времена, впрочем) был привлечен к выяснению вопроса, почему так тормозит свежеразработанный местными веб-программистами сайт. Недолгое колдунство с SQL Profiler однажды вечером, когда число запросов на сайт было далеко от пика, но общее торможение оставалось, позволило найти дивные SQL-запросы, выполнявшиеся по 15 секунд — причем это были не отчеты и не поиск по БД, а обычная генерация страниц квазистатического контента. Тем не менее, планировщику СУБД от этих запросов конкретно плохело — и не зря: просмотр текста запроса — крайне сложного и запутанного — ввел меня в глубокую задумчивость, как такое можно вообще написать? Вопрос этот (с примерами) я резонно переадресовал команде веб-программистов — на что получил честный ответ типа «А мы сами не знаем, оно автоматически создается» (потом они, правда, зная, где именно творится безобразие, все же что-то с ним сделали). Короче, думаю, мораль понятна.
SQL, повторяю, на мой взгляд, сложен для привыкших к императивному программированию (коих среди программистов большинство) своей непривычностью — и уж никоим образом не многословностью и необходимостью учитывать множество мелочей, как ассемблер. Мне, например, не приходилось писать действительно огромные простыни именно на SQL (а не на каком-нибудь расширении его для создания хранимых процедур): всё самое сложное, что было нужно, укладывалось в несколько (всяко меньше десятка) связанных друг с другом подзапросов. Правда нередко сразу было не очевидно, как их писать, и приходилось писать код не с начала, а буквально с середины. Но в итоге код запроса получался довольно компактным.
А вот в защиту бизнес-логики на хранимых процедурах у меня слов мало найдется. По-моему это — пережиток той уже давней эпохи, когда приложения были чисто клиент-серверные, с «толстым клиентом» (иными словами — «тощим» сервером, который был по сути СУБД), а удобных технологий вынесения бизнес-логики на сервер («трехзвенные приложения») ещё не существовало. Вот тогда, с теми ограничениями, бизнес-логика на PL-SQL и ему подобных была как-то оправдана. Сейчас же оправдание такой архитектуры для новых проектов я найти не могу.
VolCh
Справедливости ради:
Как-то, по-моему, не сильно влияет на способ мышления разные способы записи.
PsyHaSTe
SQL всяко лучше любого кастомного дсл. Но как я уже замечал, проблема в маппинге этого SQL на типы. В ОРМ весь билдер тащит типчики, а при написании запросов надо не забыть, что на что маппися и как.
mvv-rus
Справедливо. Но ORM может использоваться как средство написания запросов на SQL. Но есть и другие средства. В частности, упомянутый выше 15-секундный запрос был написан отнюдь не ORM, потому что испольуемая там технология была VB+ASP(не .NET ещё).
Этот код не «псевдо-» — он декларативный по сути, просто он записан не на SQL. Кстати, с непривычки такой код писать и читать труднее (сужу, естественно, по себе), чем привычные с юности вложенные циклы.
PsyHaSTe
То что нельзя сделать чистую вставку в бд или вывод на консоль — это заблуждение. Конечно же на хаскелле можно и в базу ходить, и на экран печатать. В конце концов хелло ворлд же на хаскелле написать можно:
Тем не менее этот
main
— чистая функция.Если на пальцах, то чистота означает не то что функция не делате ничего полезного, а то что её резульат действий всегда есть в сигнатуре. Например если функция возвращает
()
(юнит тип, воид в терминах сишных языков), то значит она ничего полезного не делает. А вот если она возвращаетSqlInsert ()
илиIO ()
, значит она выполняет какие-то действия, в результате которых получится тот же самый()
. Но теперь кроме результата у нас есть действия, которые функции должна выполнить.В си у вас функция которая ничего не делает выглядит как
void foo()
и функция которая печатает в консоль тоже выглядит какvoid bar()
. В хаскелле же первая будетfoo :: ()
а втораяbar :: IO ()
. И эта разница принципиальна.Так что чистота, если грубо на пальцах объяснить, это когда из сигнатуры видно, будет ходить функция в базу или нет. А не то, ходит ли она в принципе или нет.
unel
А я думал, что это ещё и означает, что при одинаковых входах она возвращает одинаковый результат… Но c другой стороны, если считать результатом функции не сам вывод в консоль (который может при равных входящих как завершиться как успешно, так и с ошибкой, а это считай два разных результата), а операцию вывода в консоль (ну типа паттерн "команда"), то наверное такую функцию и правда можно назвать чистой
PsyHaSTe
Именно так.
unel
А из сигнатуры будет видно — пойдёт функция в базу или в консоль?
PsyHaSTe
Ну например из моего пет проекта:
Собственно
SqlPersist backend [User]
показывает, что операция будет ходить в базу и вернет список юзеров.unel
хм, и эта функция тоже считается чистой?
и как чисто по этой аннотации понять, что IsSqlBackend — это какой-то внешний источник и каждый вызов
может вернуть разные результаты (а стало быть может быть мемоизован лишь с оговорками)?
0xd34df00d
Понятие чистоты неконструктивно и вызывает слишком много вопросов, лучше оперировать моделью «функция сообщает о своих намерениях в сигнатуре». И да, эта функция о своих намерениях говорит достаточно точно (хотя, конечно, можно и точнее, но тут надо бы систему типов помощнее, чем в хаскеле, а то это будет очень больно).
А это не так. Вызов
getPersonsInner backend
всегда возвращает один и тот же набор действий. Непосредственно юзеров возвращает уже лишь некоторый интерпретатор, который берёт то, что вернул этот вызов, и, собственно, интерпретирует эти действия, залезая в базу и что-то там делая. Или залезая в мок. Или ничего не делая.unel
Хм, действительно, интересный подход!
И по типам можно определить, является ли результат функции действиями для такого интерпретатора (и для какого именно) или "базовыми примитивами" (тут я понимаю, что вступаю на скользкую дорожку и что скорее всего мне ответят, что действия тоже являются базовыми примитивами)
svr_91
> И да, эта функция о своих намерениях говорит достаточно точно
Как теперь заставить программиста четко выражать свои намерения, а не ставить везде IO?
0xd34df00d
Точно так же, как заставить программиста не писать весь код в одном файле и не копипастить код, а использовать функции.
svr_91
Ну тоесть никак. Чем тогда это лучше, чем в любом другом языке согласиться в имя функции приписывать суффикс pure?
0xd34df00d
Тем, что компилятор проверит, что вы из pure-функции не вызвали не-pure-код.
svr_91
А наоборот проверить можно? Чем это защитит от программиста, который везде будет пропихивать монады?
0xd34df00d
Никак наоборот не проверить, вы действительно всё можете писать в IO, никто вам не запретит. Но такой цели и нет.
PsyHaSTe
Как выше написали, этот внешний источник возвращает данные разные при интерпретации, но функция
getPersonsInner
возвращает не результат запроса, а описатель этого запроса.Вот я в качестве интереса набрасывал мою реализацию IO на расте:
https://gist.github.com/Pzixel/3fc17be254f6c6bcaf88711e12bddd2c
Обратите внимание, что get_line или read_line чистый по-определению, например можно сделать:
эквивалентно:
Такое разделение очень полезно по многим причинам. Во-первых сразу видно, какие виды действий делает функция. Отдельно выделяется функция, которая не делает никаких вычислений, например функция Length или какой-нибудь Sqrt. Во-вторых появляется возможность "отменять" действия по каким-нибудь причинам, потому что пока вы не начали интерпретировать, действие не запустится.
на самом деле в этом нет никакой магии, как видно в примере выше любой язык позволяет писать в таком духе. Вопрос только, хотят ли этим люди заниматься или нет. В общем случае, если СТД и библиотеки в таком виде не написаны, то воевать против всего мира не получится.
никакой магии нет, но определенная сложность в понимании есть. Самый простой способ — попробовать в таком виде пописать. Сразу вся "волшебность" отпадает и остается только зрелое понимание, как это работает и зачем нужно.
ViktorZ
Помимо Scala есть еще и Kotlin…
Ruins007
Увидел тут недавно F# в стиле
удивился что так можно было. По сути прокачанный ООП, где вместо стандартного возврата типа было бы что-нибудь такое
То есть абсолютно типизированная обработка и входов и выходов функции — рай ООП.
p.s. даже не знаю теперь, фреймворк написать или сразу язык.
PsyHaSTe
Ну это обычное использование Either для ошибок, весь раст на этом построен (там типчик Result называется).
В ООП языках неудобно потому что там или нет достаточно мощного свитча для этого, или он есть, но писать постоянно обработку кейса когда наш WorkerResult это и не WorkerOk и не WorkerError, а что-то другое.
funca
Either и Result это частные случаи sum types. В ООП есть похожие по смыслу Chain of Responsibility и Strategy. Но для большинства случаев встроенных в язык конструкций (if-else и try-catch) хватает выше крыши.
PsyHaSTe
Either и Result — это одно и то же. То что это частный случай sum types — очевидно, как например "синглтон" или "фабрика" это частный случай классов.
А вот как chain of responsibility это расширяет — непонятно. Как и со стратегией — стратегия предполагает открытое множество, собственно, "стратегий", а АДТ — нет.
Если же искать что-то похожее в ООП, то самое близкое это визитор.
mayorovp
Уточнение: визитор тут тоже ни при чём.
PsyHaSTe
Ну визитор это эффективно способ эмулировать АДТ всё же. Каждый VisitFoo это обработка одной ветки матча, все вместе они дают обработку АДТ целиком.
svr_91
А с точки зрения ФП монады (не IO) считаются чистыми функциями или нет? И насколько вообще стоит предпочитать обычную чистую функцию монаде?
St_one
если говорить о haskell, то с точки зрения языка — всё чистое, даже IO. Собственно, IO и нужна как-раз для того что-бы «чистым» образом описать «грязный» ввод-вывод.
PsyHaSTe
Монады можно применять в том числе чтобы делать эффекты чистыми. Само ИО тоже чистое, если взять определение. Оно и было придумано, чтобы "упаковывать" всякую грязь в чистый язык.
Соответственно чистая функция монаде не противопоставляется, поэтому второй вопрос не очень корректный. Если же вопрос, когда стоит писать в монаде а когда нет — аналогичен "когда стоит выносить функционал в интерфейс/базовый класс, а когда — нет". Если так удобнее, то стоит, если нет — то нет.
svr_91
> когда стоит выносить функционал в интерфейс/базовый класс, а когда — нет
Отличие в том, что в интерфейсах самих по себе ничего плогохо нет. В той же Java, например, интерфейс — это основопологающая концепция. Как правило, нет ничего плохого чтобы создать очередной интерфейс. Да, всегда можно упороться и начать городить «фабрики фабрик», но как правило человека от этого останавливает обычная лень. А что останавливает человека от оборачивания всего и вся в IO? И насколько всеже IO антипаттерн? Может мы неправильно понимаем ФП, и нужно вообще все писать в IO?
chersanya
Ровно та же "лень", про которую вы пишете, ну и вообще здравый смысл
Вон выше пример писали — функция add в монаде IO, и чистая:
svr_91
Для такой простой функции да. Но простые функции можно вычитать и глазами, увидев и так, что никаких побочных эффектов там нет. А вот про функции от 3 строк разница уже не сильно большая.
chersanya
По-моему при написании функции обычно не возникает проблемы определить, требуется ей доступ в сеть или нет (для примера). Нужен — используете, не нужен — пишете чистую функцию.
svr_91
«Нечистой» функция может стать не только при доступе в сеть, в этом вся проблема. Например, какойто алгоритм проще написать в императивном стиле, и в этом вся проблема.
Если я вижу, что в императивном мне писать проще, я могу использовать монады вместо читсых функций, вот в чем вопрос
mayorovp
Если алгоритм требует императивного стиля, но не требует доступа во внешний мир — то для него подойдёт монада ST. В отличии от IO, она может находиться внутри чистой функции, runST в помощь.
svr_91
Тоесть монадой ST можно пользоваться в любом удобном случае?
PsyHaSTe
А что останавливает человека от того, чтобы все функции писать как асинхронные, а где асинхронности по факту не надо просто возвращать
Task.FromResult()/Promise.resolve/...
? Здравый смысл, наверное.svr_91
Если бы написание асинхронных функций было бы проще, чем синхронных, возможно в итоге к этому бы и пришли (возможно, в какихто ЯП уже и пришли, я не знаю).
Меня интересует, какие препятствия ставит haskell от неправильного применения монад, в том числе IO. Насколько сложнее писать код в монаде IO, чем не в монаде IO?
PsyHaSTe
Примерно настолько же, насколько легко писать асинхронные функции с async/await :dunno:
Я вопрос не понимаю. Правило простое: пишите без монад пока вам компилятор не скажет что не компилируется т.к. какая-то из вызываемых функций требует монаду. Профит. Прямо как с асинком. Пишем синхронно пока компилятор не говорит что у нас тут асинхронность. Вставляем авейт, становимся асинхронными.
svr_91
Я понял. Вы считаете, что стилем кода по большей части управляет здравый смысл. Я считаю, что им управляет лень. То есть мы просто говорим на разных языках.
Потомучто здравый смысл — а что считать здравым смыслом? Давайте в любом другом языке будем приписывать суффикс pure к чистым функциям. Это будет считаться здравым смыслом?
> Примерно настолько же, насколько легко писать асинхронные функции с async/await
Ну например, теже акторные фреймворки вынуждают программистов писать в асинхронном стиле. Там очень сложно засинхронизироваться, и поэтому, один раз написав асинхронную функцию, вы на большую часть разработки оказываетесь привязанны к асинхронному коду.
PsyHaSTe
С точки зрения лени IO — лищние буковки, которые можно не писать.
асинк это один из видов монад. Его особенности распространяются на их все, включая ИО. Если где-то внизу коллстека появился эффект, то за редким исключением он будет протекать до самого верха.
svr_91
> С точки зрения лени IO — лищние буковки, которые можно не писать.
А если эти лишние буковки упрощают написание остального кода? В результате лишних буковок становится много меньше
Поймите, я не хочу писать весь код в IO просто ради хулиганства. Я хочу его писать там просто потому, что мне например проще мыслить в императивном стиле, а не функциональном. Мне проще сходить в сеть когда мне захочется, а не когда мне это позволят. Записать в лог чтото в произвольном месте кода. Что меня остановит?
> асинк это один из видов монад
Мне кажется, обсуждение ушло в другое русло
chersanya
Ничего не остановит от написания всех функций в IO — ровно так же, как от написания всего кода проекта в одном файле или даже одной функции, например. Вы же в условной джаве указываете тип функции не object f(object a, object b), а int f(string a, int b), хотя в принципе можно везде с object'ами работать.
svr_91
Ну работать с голыми объектами всеже не так удобно.
А вот написать весь код в одном файле — удобно. Поэтому с этим борются всякими административными методами.
St_one
это как? Премии лишают за слишком большие файлы?
svr_91
Не знаю. Как обычно в компаниях борются с несоответствием корпоративным правилам форматирования кода?
St_one
показывают хороший код и плохой. Когда показывают плохой код — бьют током. Так у программиста вырабатывается условный рефлекс, и он больше не пишет плохой код.
chersanya
Нет, не удобно и не административными методами с этим борются, по крайней мере преимущественно. Иначе бы все личные pet projects на гитхабе состояли из одного файла — ведь никакой "администрации" там не руководит.
svr_91
Когда проект становится большим, то уже неудобно становится писать все в одном файле. Приходится разбивать на несколько.
Тем не менее и на гитхабе попадаются проекты из одного файла
PsyHaSTe
Обычно чем более квалифицированный человек пишет код на гитхабе, тем больше он похож на корпоративный проект. У достаточно опытного разработчика будет декомпозиция по файлам, ридми, примеры, опубликованная в местном пакетнике версия, настроенные билды, и так далее.
svr_91
Но достигается все это не за счет языка, а за счет самодисциплины и корп. стандартов. Даже у самых прокачанных программистов можно встретить недостаточно хорошее разбиение программы по файлам.
Вопрос в том, что если вырабатывать в себе какуюто самодисциплину, то какая по большому счету разница, в каком языке это делать? Да, haskell в какихто случаях лучше других языков, как и любой один язык в чемто лучше любого другого. Но есть ли прям какаято киллер-фича, без которой никуда?
«Пиши код в таком стиле — тогда не будет побочных эффектов». Да, круто, но в таком стиле я могу писать код и на другом языке, это как я уже сказал, вопрос самодисциплины
chersanya
"Самодисциплина" не проверяется компилятором: достаточно один раз с недосыпа забыть где-нибудь во внутрнней функции вставить какой-нибудь запрос к сети, и это пойдёт через весь код который её вызывает.
Такого нет ни в каком языке: если бы была фича, "без которой никуда", то ни на каком языке без этой фичи писать было бы невозможно.
PsyHaSTe
И в каком другом языке компилятор не разрешит из функции
sqr :: Int -> Int
вызвать printf?PsyHaSTe
ИО не позволяет писать в императивном стиле. State позволяет ST позволяет, а ИО — нет.
svr_91
Я не очень силен в хаскеле, так что пусть так
chersanya
Так если вы просто хотите внутри функции писать код "в императивном стиле" с изменением локальных переменных, то можно его писать в монаде ST и вызывать runST как тут уже писали. Итоговая функция будет безо всяких монад в сигнатуре: действительно, пользователю не нужно знать как она реализована, если внешне-видимой разницы от этого нет.
svr_91
Ну если можно так и ладно. Я просто интересуюсь, насколько это обычно приемлемо.
То есть должен ли я насиловать себя, и стараться писать любой код «чисто» (то бишь без монад в таком понимании), или это позволительно расслабиться и сделать как проще?
chersanya
Вы видимо исходите из того, что "проще" писать в императивном стиле — хотя множество примеров показывает, что часто это не так.
svr_91
Какието примеры показывают это, какието наоборот. Что делать, если в проекте наблюдается по половине и таких и таких примеров? Насиловать себя и заставлять приводить все к одному стилю? Или разрешить использовать где что удобнее?
chersanya
Вы точно читаете предыдущие сообщения? Буквально 10 минут назад я писал про монаду ST и чистые функции. В других цепочках тут тоже про неё упоминали в контексте написания алгоритмов, которые более естественно ложатся на мутабельные переменные.
PsyHaSTe
Пишите, где удобнее. Но по факту St не вижу почти нигде, разве что когда кальку с императивных алгоритмов снимают типа инплейс квиксорта.
funca
Монада это пара функций над значением некоторого типа, которые должны удовлетворять нескольким общим правилам. Можно про нее думать как про интерфейс. Напрямую сайд-эффекты ни кто не запрещает. Но при их наличии удовлетворить определенным свойствам (скажем такому, что композиция должна быть ассоциативной) скорее всего будет невозможно — сайд эффекты будут выполняться не в том порядке, что сильно сужает область их полезного применения.
housequake
А что разве в данном случае append_to_list2 — не создает побочных эффектов? Она же меняет внешний newlist, который потом выводится?
anonymous
Лишний мемори траффик убивает все шансы на использование ФП в low latency задачах(с коими я последние несколько лет только и вожусь).
Да, приятно ваять петпроектики на F#, но вот в суровый бой идет голая императивщина без прикрас :|
PsyHaSTe
Тут вопрос не в ФП, а в гц. На каком-нибудь сишарпе наверное лоу-латенси не особо попишешь.
anonymous
Легко пишется low latency на C#, если GC не насиловать, благо struct, ref struct, array pooling, Span, ValueTask и прочие штуки это позволяют делать легко и непринужденно.
Как пример-прямо здесь и сейчас за месяц накидан арбитражер латентности датафида котировок с издержками на обработку одного сообщения ровно в 0.2 микросекунды, на тестовых прогонах(в реале потому что нет такой нагрузки)-100 млрд. сообщений прокатываются без единой сборки мусора. Да, на F# можно сделать в один в один(mutable в зубы и ref struct в F# тоже доступы), но это будет сильно противоестественно самой природе F#. Если бы я тоже самое делал на плюсах, то за полгода вряд ли справился с тем качеством и стабильностью кода, что получена разработкой на C#. Да, все герои писать код на плюсах, только с 9 годами работы в Лаборатории Касперского за плечами я с большим скепсисом смотрю на этих всех «хероев».
PsyHaSTe
Товарищ из майкрософта который actix писал (на который azure iot перевели) как раз жаловался, что код на шарпах аллоцирует как не в себя даже на IO bound задачах, на затюненном приложении. И уверяю, что если майкрософт не умеет готовить C#, то никто не умеет.
anonymous
Мнение конкретного криворукого персонажа из Микрософта мне до одного места, у меня есть несколько проектов, что не аллоцируют динамически память на сетевом IO, имея тестовые нагрузочные прогоны на 2 и более недель на триллионы прошедших сетевых сообщений и при этом написанные на голом C#. :D Ща меня будут разубеждать, что это фантастика. Да и пофиг, я уже давно убедился что сетевые дискуссии -пустопорожнее занятие, редко в них участвовал и видимо надо к возвращаться к этой практике, я лучше своего сына чуть лучше в задачах программирования натаскаю, надо же свой опыт передавать :D
mayorovp
Это он жаловался до появления Pipelines или после?
PsyHaSTe
ну Actix писался в 2017-2018 годах, полагаю сравнение было примерно в этом районе.
mayorovp
Pipelines тоже в 2017-2018 годах делались...
PsyHaSTe
Ну я не думаю что там что-то поменялось. Аллокации на каждый чих (если пользоваться структурами, то о наследовании можно забыть), при том что все стандартные типы типа строк и массивов — исключительно хиповые. Span на стакаллок память работает хорошо только на бумаге, все задачи всегда выполняются на тредпуле, который нафиг для ИО не нужен, и так далее… Это всё кишки CLR и BCL, я не думаю что Pipelines или что-то ещё с этим может что-то сделать.
mayorovp
Есть же ArrayPool
PsyHaSTe
Но способа выполнять таски не на тредпуле а на current thread например я не знаю. В BCL зашито что оно все в тредпуле, все эти ISyncronizationContext и все остальное прям вопит об этом.
Пул это хорошо, но не всегда удобно и/или возможно.
mvv-rus
TaskScheduler.FromCurrentSynchronizationContext() (в ContinueWith, в конструкторе TaskFactory) — не оно?
Правда, для этого упомянутый SynchronizationContext должен быть. Впрочем, там где это очень важно (в GUI к примеру, где все обращение к GUI должно быть в выделенном для этого потоке) MS уже изначально об этом позаботилась, а для желающих странного есть возможность реализовать SynchronizationContext самостоятельно.
anonymous
Да плевать что этот криворукий идиот не сумел сделать, я своими руками писал сетевой код на C# имеющий ноль аллокаций на сетевом траффике в десятки терабайт.
PsyHaSTe
"этот криворукий идиот" это команда разработчиков из 20 человек в майкрософте. Но конечно, в интернете каждый на 300 IQ умнее любого из них, понимаю.
anonymous
Видимо да, я их умнее, если сделал сетевой код, работающий без аллокаций :D
Есть же SocketAsyncEventArgs и его обвязка(да, не современная и не модная технология), что полностью исключает необходимость динамически аллоцировать память для сетевой коммуникации :D
А можно вообще прямую запись и чтение в Socket делать синхронно, самостоятельно пулируя работу с памятью, если латентность обработки сильно интересует.
anonymous
Pipelines совсем не образец эффективности.
VanquisherWinbringer
Попишешь но это будет такой код что поддерживать его да и вообще второй раз в жизни видеть желания у тебя не будет. Хотя и на Rust это все с лайфтаймами делается и с вставками unsafe местами. Няша, лоу латенси на самом деле не то говно с которым хочется иметь дело на постоянной основе. Очень на много компромиссов приходиться идти.
anonymous
И? С 2004 по 2009 года писал много low latency, потом 9 лет работал в Лаборатории Касперского, много кровавой энтерпрайзы (правда не всегда и не везде), ушел опять low latency писать, и я опять счастлив, уже как 2+ года.
Интересные задачи на пределе сетевого железа и CPU, за которые платят хорошо денег, что еще надо? Ну да, код местами непростой, но за простые вещи и платят мало.
P.S. И код low latency уж точно не самый страшный код, что я видел, а видел я свою практику разработки много всякого и разнообразного :D
PsyHaSTe
Зачем только делать на шарпе х10 работы если на расте можно получить все то же (и даже лучше) из коробки — неясно. Разве что из чувства авантюрности.
anonymous
Какие x10? Вы вообще современный C# видели? Версии эдак 8.0?
И что на текущий момент представляет собой .NET Core 3.1?
Явно нет, живете в каких-то иллюзиях, что были сформированы о C# лет эдак 10 назад. Если вы тогда еще не в начальной школе учились, хехе.
0xd34df00d
Ну, скажем так, лоу-летенси — это прикольные ребусы и задачки. Писать такой код прикольно. Впрочем, вот второй раз в жизни его же видеть уже не очень хочется, это да, лучше писать следующий код.
VanquisherWinbringer
Вот я об этом и говорил.
HemulGM
Всё что тут описано как "функциональный подход" используется в ООП. Влюбляются в фп те, кому не хочется разбираться с возможности ООП, т.к. единственное, что отличает фп от ооп — отсутствие классов (если грубо округлить)
St_one
А ООП без классов это просто процедурное программирование. Значит ФП это просто процедурное программирование.
Вывод: то что действительно важно — это классы(если грубо округлить).
PsyHaSTe
Пишу на сишарпе с иерархиями, наследованием и всем остальным в ФП стиле (за исключением монад, но тут уж понятно). ЧЯДНТ?
St_one
А классы у вас там есть? Ну это тогда просто ООП получается(если грубо округлить)
P.S. а вывод «ФП это просто процедурное программирование» вас не смущает? Это как сказать чёрное это белое…
PsyHaSTe
Нет, не получается просто ООП. Даже дядюшка Боб уже писал, что ООП и ФП непротиворечат друг другу. И сказать "Да эт опросто ООП" — это свернуть весь спектр до одной точки.
VanquisherWinbringer
Вся Scala это ООП + ФП. Да и в хаскеле какая нибудь монада State это такой типичный объект. В Эрланге акторы это вообще эталонное ООП. ФП против Мутабельности. ФП против императивности. ФП вместе с ООП идет в комплекте в большинстве ЯП. Тобишь для ФП главное это иммутабельность и изоляция эффектов за счет вынос их в объекты отложенного выполнения рантаймом.
St_one
VanquisherWinbringer
Ниже дедфуд и выше психаст уже ответили. Мне просто интересно, почему по вашему монада State это не объект?
St_one
Что-то не вижу ответа на мой вопрос — ни ниже, ни выше. состояние торчит наружу, объекты имеют внутреннее состояние. Разные объекты могут взаимодействовать друг с другом путём отправки сообщений, обрабатывать сообщения. В Java, например, этот механизм редуцирован до (subtype)полиморфных методов. State же подобных механизмов не имеет.
И да, это не значит что ООП на hs нельзя изобразить, вполне себе можно. Только, как и везде(в т.ч. Java), это очень редко нужно.
Yuuri
А какой-нибудь data-bean с геттером-сеттером и без других методов – это уже не объект?
VolCh
Недообъект :)
Yuuri
Semiobjectoid
St_one
Геттеры и сеттеры выступают в роли сообщений, которые принимает data-bean, так он становится подобным объекту.
Но объект это только формально, по факту data-bean не управляет своим состоянием, а, значит, объектом не является. Это просто структура, мимикрирующая под объект.
VanquisherWinbringer
Глаза протрите.
То есть по вашему ООП про приватное состояние и получается что в каком нибудь Python ООП нет? Шире мыслить надо. Objects are poor man's closures ООП оно про то что результат вычисления зависит не только от входных параметров его метода но и от каких-то еще данных и эти данные вполне могут быть публичными. Какой нибудь контроллер который возвращает какое нибудь значение которое ему возвращает какой-то репозиторий которым он обладает это таки типичный объект потому что он возвращает значения на основе данных которые ему отдает его репозиторий (который тоже может обладать стейтом или в просто виде массива или в виде БД) а не только тех данных которые ему передали в качестве параметров.Только вот обычно сам объект идет неким параметром в каждом методе. this или self. Ничего не напоминает? А ну да например >>= первым параметром принимает саму монаду т. е. this aka self. Для любой монады результат do x< — monad return x зависит от того какое значение было до этого в монаде (например если было None то None и вернется) поэтому вообще любая монада это объект. Не несите фигни. А то как в той поговорке, научили грамоте осла раньше он просто глупости говорил а теперь он говорит глупости грамотно. Еще раз для таких как вы повторяю — ООП оно про то что функция или метод связанный с объектом возвращает значение или вызывает эффект на основе текущего состояния этого объект и абсолютно не важно публичное ли это состояние или нет.
St_one
Это был один из критериев, вы путаете инкапсуляцию и сокрытие. Далее был описан пример data-bean, в котором есть сокрытие, но нет инкапсуляции.
Опять же — Вы путаете ООП и процедурное программирование, ООП совсем не про это.
ООП про объекты, их поведение и взаимодействие(сообщения), про инкапсуляцию внутреннего состояния.
Да чего мелочиться то? f x = x зависит от того какое значение было до этого в x (например если было None то None и вернется) поэтому вообще всё что угодно это объект. А если назвать x 'this', то и комар носа не подточит. f this = this. Матерь божья, где-то я это уже видел:
VanquisherWinbringer
Объект это структура данных и набор функций ака методов для работы с ней. Связанных с ней. Поэтому List и Maybe это объекты.
Те же каррированные функции это объекты и какой нибудь
def add(a)(b)
val c = add(1)
val r = c(2)
будет равен по сути своей вызову у инстанса объекта Int метода add вот так
var r = 1.add(2)
Вообще да в скале 1 реально инстанс объекта и у 1 реально есть метод + и по сути 1 + 2 это в Scala синтаксический сахар над вызовом метода объекта Int. Такие дела.
St_one
VanquisherWinbringer
Вообще да, все что угодно это объект а тайпклассы это способ работы с категориями объектов. Работа со всеми объектами которые можно сравнивать (aka имеют метод eq) например. Тайпклассы это как раз таки ООП часть ФП.
0xd34df00d
Особенно если учесть, что там есть отношение сабтайпинга — например,
Eq a => a -> T <: Ord a => a -> T
,или
MonadReader m Foo => m Bar <: (MonadReader m Foo, MonadWriter m Baz) => m Bar
.St_one
Тайпклассы просто механизм, который способен выступить аналогом диспатчера сообщений(виртуальных методов) в ООП языках. Т.е. они позволяют добиться аналогичного эффекта, но это не делает всё, что использует тайпклассы, объектно ориентированным. Даже на ООП языках можно писать не объектно ориентированно(возьмите ту же анемичную модель).
VolCh
Анемичная модель (класс со свойствами и только тупыми геттерами/сеттерами) — хороший пример одновременно ООП(грамирования) без ООП(роектирования).
0xd34df00d
Но классы — это такой частный случай лямбда-исчисления. Пирсовский TAPL ближе к концу книги описывает, как на (очень малом подмножестве) System F вместе с bounded quantification сделать ООП, с инкапсуляцией, наследованием и привычным вам сабтайпинг-полиморфизмом.
HemulGM
Можно — не значит нужно.
HemulGM
Какие же дегенераты… Что, не нравится фраза? В чем, бл, проблема?
HemulGM
Гнилье тут, а не народ..
HemulGM
Минусите больше, дегенераты, вам ведь только это надо
PsyHaSTe
telhin
Немного есть опыт работы с C++ и Haskell, хотя оба языка не являются идеальными ООП и ФП в вакууме. Если сравнивать более менее сформировавшиеся проекты, в которые до сих пор вносятся изменения, то их состояние можно описать как неустойчивое и устойчивое равновесие.
Все особенности ООП подхода с разными шаблонами, постоянными спорами "ты не так понимаешь принцип единственной ответственности" делает состояние согласованной и однородной кодовой базы весьма красивым, но со временем оно склонно разваливаться. Костыли писать весьма легко. Делать зависимость локального логгера от подключения к БД вам никто не помешает. Конечно можно сказать "Нормально делай — нормально будет", но формального определения нормального в ООП нет, есть только несколько общепринятых трактовок, внутри которых также остается пространство для спекуляций. Чтобы проект придерживался архитектуры приходится прикладывать усилия.
С другой стороны, особенность системы типов Haskell состоит в строгих ограничивающих требованиях к процессу разработки. Обходить эти требования больнее, чем пользоваться языком в рамках нормы. Ограниченность побочных эффектов автоматически проецируется в гексагональную (луковую) архитектуру приложения. Притом это происходит не из-за усилий идеологов чистого ФП, а само собой. Как в ситуации с устойчивым равновесием.
Плюсами я пользуюсь в большей степени, уж как-то пришлось разобраться с особенностями ООП, но любовь к ФП у меня точно не из-за того, что мне лень разбираться с классами.
HemulGM
Интересует эта часть. Что именно и по какой причине разваливается?
VolCh
Тщательно выстроенные красивые, согласованные иерархии и композиции со временем разваливаются, потому что костыли, их нарушающие, писать легко.
funca
Потому, что разработчикам свойственно влюбляться во все новое, загадочное и прекрасное. Это что-то вроде эффекта Кулиджа в интерпретации на программирование. Но строить долговременные отношения, все же, лучше с теми, кто тебя поддерживает и понимает.
В разработке основная борьба идет со сложностью. Хаскели копают уже больше 30 лет. Согласитесь, это достаточный срок для того, чтобы индустрия могла развернуться к ним лицом, давай все эти милые ФП трюки, хотя бы 20% преимуществ на практике (в терминах сроков, бюджетов или сопровождения). Пусть даже не вся, я согласен принять как аргумент в пользу, если доминирование ФП будет заметным хотя бы в каких-то определенных нишах.
0xd34df00d
Можно начать, например, с компиляторов или с систем доказательств.
VolCh
На чём пишут компиляторы C++, Java, C#, Fortran, Rust, Go?
PsyHaSTe
Часто пишут на OCaml, он для этого очень хорош. А потом обычно бутстрапят, просто потому что с догфудингом повышается надежность и качество фичей языка. Ну и плюс это некоторый критерий успешности и взрослости.
0xd34df00d
Зависит.
«Официальные» компиляторы, как правило, self-hosted, потому что, как рядом PsyHaSTe написал, это показатель взрослости языка. Ну и пиар.
А вот если чуть выйти за рамки… На одной прошлой работе соседний отдел писал что-то очень компилятороподобное для фортрана на хаскеле. Компилятор для другого предметного язычка я же сам писал на хаскеле (хотя изначально язык был сделан на сях, и у этого было очень много болезненных последствий). Раст, насколько я знаю, изначально был написан на окамле. Хаскель написан на хаскеле. Кок написан на окамле. Агда и первый идрис написан на хаскеле. Второй идрис написан на идрисе.
Да даже для каждого следующего игрушечного язычка выбирается, насколько я могу судить, условный хаскель, а не C++ или JS.
AlexSpaizNet
Было бы интересно посмотреть как уживаются фп и деревья… ну АСТ я имею ввиду.
mayorovp
А с ними-то что не так?
0xd34df00d
Более чем прекрасно. Деревья отлично описываются через алгебраические типы данных, полиморфизм позволяет в одной структуре данных (описывающей эти самые деревья) таскать с собой произвольные дополнительные данные, и так далее.
Ну и конкретно в случае хаскеля есть всякие приятные вещи типа uniplate, которые позволяют обойти всю структуру дерева (потенциально из взаимно рекурсивных типов данных) с почти-однострочниками вроде такого, переименовывающего все вхождения переменной во всём дереве сразу:
или такого, заменяющего переменную на терм в типе:
Более того, я прямо сейчас пишу код, который работает с деревьями (правда, деревьями проверки типов, а не АСТ, но какой компилятор без типов?), и, в частности, доказывает утверждения вроде того, что если ? ? e: t и ? ? ?', то ?' ? e: t. Вот прям в коде. Как это сделать на ООП?
AlexSpaizNet
Вот это вот переименование и замена переменных, с точки зрения фп и чистых функций, я правильно понимаю что дерево должно копироваться полностью от функции к функции? И копируется ли?
mayorovp
На языках со сборщиком мусора подобные структуры данных копируются при необходимости модификации и не копируются (передаются по неизменяемой ссылке) когда модификация не требуется.
На языках без сборщика мусора так тоже можно делать, но понадобится подсчёт ссылок.
0xd34df00d
Хороший вопрос.
С одной стороны, в любом случае копируется не всё дерево, а только та часть, которая изменилась (плюс узлы над ним). То есть, если вы что-то поменяли в корне, то копировать толком ничего и не надо, все остальные узлы берутся от предыдущей версии дерева.
С другой стороны, если компилятор видит, что старая версия дерева не используется, то он может скомпилировать такой код в изменяющий дерево на месте (и это на самом деле нередко происходит, а с наличием в языке линейных типов у программиста будет больше контроля над этим).
PsyHaSTe
Короткий ответ — да, должно копироваться, и нет, по факту не копируется. См. персистентные структуры данных.
За длинным есть классическая книжка на эту тему, что-то вроде банды четырех для ООП или Кнута для сишников:
prefrontalCortex
И сразу с места в карьер мимо. Функциональное программирование — оно про функции как объекты первого порядка, поэтому оно и называется функциональным. Здесь и далее в статье вы описываете Haskell. По вашему описанию, вся из себя функциональная Scheme функциональщиной не является, потому что про побочные эффекты там вообще ничего нет, и доступно мутирование состояния через set!.
Снова чушь. Прежде чем позориться, взяли бы на себя труд узнать про то, как и зачем в питоне работает self.
PsyHaSTe
Именно, не является. Как и любой лисп, в общем-то.