Pull to refresh

Comments 128

Мне кажется или полиморфизм в ООП (ad-hoc) вы представили в неком странном виде… в виде того, что у разных обьектов метод с одним и тем же названием — Полиморфизм…
хм… довольно странная трактовка

НО! одинаковые методы у разных обьектов не гарантируют полиморфное поведение! Это должен быть один интерфейс, то есть все поведение должно происходить в рамках одной абстракции (либо интерфейс как конструкция, либо наследники c переопределением методов родителя)
Именно в этом же суть — один метод в рамках одной абстракции?
Мне кажется или полиморфизм вы представили в неком странном виде… в виде того, что у разных обьектов метод с одним и тем же названием — Полиморфизм…
Именно в этом суть — один метод в рамках одной абстракции

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

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

На таком уровне и названий никаких нет.

Ну в статье написано, что объекту передаётся сообщение с именем f, значит название таки есть ))


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

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

что хотя ООП и ФП ортогональны, это не взаимно исключающие понятия

Вы точно понимаете, что такое ортогональность, чтобы употреблять этот термин?

Я, конечно, понимаю что это такое, но на всякий случай уточню, что термин употребляю не я, а Дяд Боб )))

Тем не менее вы повторяете эти глупости за ним.

Раскройте, пожалуйста, свою мысль. Почему ООП и ФП не ортогональны?

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

Взаимоисключающая отогональность — оксюморон.

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

Это потому что мы привыкли к тому, что для выражения 2. неявно подразумевается особая семантика поведения, которой нет у выражения 1. Эта особая семантика поведения — полиморфизм.

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

Вообще-то полиморфизм есть в обоих случаях.

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


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

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

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

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

Я уже не раз пытался понять «что же такое ООП» и не смог найти того кирпичика который является его основой.
Он раскрывает эту тему достаточно доходчиво и с обзором истории в «Clean Architecture: A Craftsman’s Guide to Software Structure and Design», 2017

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

Данные относительно функции всегда такие) Просто в ООП языках есть неявная конвенция передавать this первым аргументом. В расте это собственно и вынесли на уровень самого языка, и всем очевидно, что 1.into() и Into::into(1) это совершенно одно и то же.

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

ООП говорит нам — лучше да, чем нет, но необязательно.
Можно поинтересоваться, где он так говорит?

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

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

Автору термина «OOP», Alan Kay, например, это не очевидно (или, даже, очевидно не это): «OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.»
Автору термина «OOP», Alan Kay, например, это не очевидно

Но всем очевидно, что сейчас под ООП имеют в виду не то, что имел в виду Алан Кей )))

Но всем очевидно, что сейчас под ООП имеют в виду не то, что имел в виду Алан Кей
Так всем очевидно, или Вам? Вы говорите от лица всех? Интересно…
Так всем очевидно, или Вам?

Мне кажется, большинству, и в первую очередь Алану Кею.


Вы говоритот е от лица всех? Интересно…

Ну, если вы со мной не согласны, то точно не от лица всех )). Но сейчас мало кто не согласится с тем, что Java — объектно-ориентированный язык или с тем, что С++ поддерживает ООП. Но со слов Алана Кея, ООП систему на можно построить только на Лиспе или Смоллтоке.

Позвольте вернуться к началу разговора. Когда вы говорите:
ООП говорит нам — лучше да, чем нет, но необязательно.
то вы посягаете на первоисточность. Это немного не то, чтобы:
кажется, большинству
или
мало кто не согласится с тем

Что говорит нам ООП устами автора этого термина — я вам озвучил.
то вы посягаете на первоисточность

Нет, только на текущее значение аббревиатуры ООП. Слова ведь означают то, что договорились иметь в виду те, кто их использует ))


Что говорит нам ООП устами автора этого термина — я вам озвучил.

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

Вы уверены в том, что значение ООП, заложенное автором этого термина, не реализуется ни одним современным языком программирования? Интересно…

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

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

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


Тут многое зависит не от самого термина, а от субъекта восприятия.

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


Раз уж Вы убеждены в том, что Ваше мнение очевидно всем, то невольно напрашивается вопрос — чем Вы тогда здесь сейчас занимаетесь?

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


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

Да, совершенно согласен. Я просто хотел сказать, что использовать определение Алана Кея для доказательства своей позиции не совсем корректно, потому что сейчас под ООП имеют в виду не то, что имел в виду Алан Кей

что использовать определение Алана Кея
Вы все время уходите в сторону, и путаете «определение» с конкретным формулированием отношения ООП к сокрытию данных, данное автором этого термина. Еще раз напомню — речь идет о сокрытии данных.

для доказательства своей позиции
Процитируете «мою позицию»?

Нет, я уверен в том, что многие языки, которые общепризнано считаются объектно-ориентированным, не реализуют то ООП, которое имел в виду Алан Кей.
сейчас под ООП имеют в виду не то, что имел в виду Алан Кей
Последние два утверждения интересны (хотя и бесполезны в отношении вопроса сокрытия данных), потому что признав в первом утверждении существование современных «чистых» (в концепции Alan Kay) ООП-языков, вы автоматически оспорили свое второе утверждение. Значит, все-таки не все так считают. Вообще, это плохая привычка говорить от лица всех.
Вы все время уходите в сторону, и путаете «определение» с конкретным формулированием отношения ООП к сокрытию данных, данное автором этого термина.

Не путаю. Мнение Алана Кея об отношении ООП к сокрытию данных содержится в процитированном вами определении ООП.


Еще раз напомню — речь идет о сокрытии данных.

Да, я помню.


Процитируете «мою позицию»?

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


признав в первом утверждении существование современных «чистых» (в концепции Alan Kay) ООП-языков, вы автоматически оспорили свое второе утверждение.

Нет, не оспорил. Из того, что существуют чистые по мнению Алана Кея ООП языки не следует, что большинство использует термин ООП в том смысле, в котором его использует Алан Кей.


Значит, все-таки не все так считают.

Да, я сказал об этом в предыдущем комментарии и в комментарии, который был до предыдущего.


Вообще, это плохая привычка говорить от лица всех.

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

Ладно, давайте с другой стороны. Ок, Alan Kay — личность известная далеко не всем начинающим программистам, обратимся к (ну, не знаю, к первому взятому наугад) авторитету современности, к автору бестселлера «Code Complete» 2-е изд. Steve McConnell:

«Don't expose member data in public. Exposing member data is a violation of encapsulation and limits your control over the abstraction.» (далее идут отсылки источникам такой формулировки и примеры).

Я не заостряюсь на том, что инкапсуляция — это принцип, и даже отличительная особенность OOP, т.е. без инкапсуляции ООП теряет свои отличительные признаки.

Если верить приводимой вами же Википедии, то вы пришли к "распространённому заблуждению — рассмотрению инкапсуляции неотрывно от сокрытия.". Там же: «некоторые языки (например, Smalltalk, Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности скрытия в принципе.»

Что такое «Encapsulation»? Смотрим у МакКоннела же:

«Encapsulation picks up where abstraction leaves off. Abstraction says, „You're allowed to look at an object at a high level of detail.“ Encapsulation says, „Furthermore, you aren't allowed to look at an object at any other level of detail.“»

Здесь речь идет об управлении сложностью, но очевидно, что «Encapsulation» неразрывно связана с «Abstraction».

Что такое «Abstraction»? Это то, что обуславливает само существование класса как Abstract Data Type (ADT).

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

Более того, никаких расхождений с Alan Kay я не обнаружил.

Когда мы говорим об ООП как о парадигме, а не о реализации языковых конструкций для сокрытия данных, — то инкапсуляция, как отличительный признак ООП, непосредственно связанный с абстракцией и самим смыслом существования класса/ADT — должна быть.

Давайте для объективности заглянем к другому (тоже взятому наугад) авторитету, известному по принципам GRASP, Craig Larman, «Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development» 3-е изд.

«Encapsulation — A mechanism used to hide the data, internal structure, and implementation details of some element, such as an object or subsystem. All interaction with an object is through a public interface of operations.»

Интересно, и что же такое «интерфейс»?

«Interface — A set of signatures of public operations.»

О как… интересно, а «operations» — это, случайно, не «данные»?

«Operation — In the UML, „a specification of a transformation or query that an object may be called to execute“ [RJB99]. An operation has a signature, specified by its name and parameters, and it is invoked via a message. A method is an implementation of an operation with a specific algorithm.» (почти как Alan Kay сказал)

Ну, пожалуй, остановимся на двух. С третьим, я думаю, будет тоже самое.

Рад был бы поверить Вам, что большинство считает также как и Вы, но… похоже, что большинство, включая самую авторитетную его часть, все-таки больше на стороне Alan Kay в этом вопросе.
Да, кстати, как же я мог забыть… Жирной точкой в этом вопросе можно считать сайт Ward Cunningham (надеюсь, этот человек в представлениях не нуждается). Будем его считать третьим авторитетом (правильней было бы сказать — отцом целого ряда авторитетов). Много текста, поэтому цитировать не буду. Посмотреть можно здесь. Предвосхищая возражения, сразу прошу обратить внимание на «Last edit May 26, 2013» внизу страницы. Если этого мало — загляните еще и в глоссарий ГОФ, а так же в «Библию» ООП — «Object-Oriented Software Construction» 2nd Edition by Bertrand Meyer. В общем, большинство (по крайней мере, авторитетное большинство) — на стороне Alan Kay в этом вопросе.

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

У него наверняка были наилучшие мотивы, когда он это говорил. Увы, то, что он когда-то определил, превратилось в монстра. Особенно последняя часть, про позднее связывание. Обычная участь распопуляризованной идеи.
А лично мне это очевидно, потому что ООП я занимался очень много, очень плотно и на разных ЯП, включая те, что изначально не были для этого предназначены (C, JS). И вот ООП часто отлично применимо в рамках языка, но периодически надо жертвовать инкапсуляцией.
Грубо говоря, для ООП достаточно одного полиморфизма, просто это будет уж очень неприглядно с виду.

Чем ООП принципиально отличается от процедурного стиля? Умением хранить локальные состояния. А переиспользование кода (наследование) и легкое переключение между функциями (полиморфизм) — это фичи ООП, которые появляются следом.

Хотя в современном мире фичи стали важнее/полезнее сути. Ну значит так и должно быть.
Чем ООП принципиально отличается от процедурного стиля? Умением хранить локальные состояния.

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

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

Вот это интересный поворот. А можно ещё раз, как полиморфизм и наследование связано с хранением локального состояния?

Кстати, в некотором виде полиморфизм и наследование уж точно присутствуют и в языке Си.

А вообще, полно людей геттеры+сеттеры генерируют для всех локальных переменных в классах, а во всей из себя «ООП» Джаве это ещё и в компайл-тайм вынести пытаются костылями( projectlombok.org/features/GetterSetter ), и называют это инкапсуляцией. Так что отсылка к мнению некого «большинства», ну, так себе аргумент.

P.s. И я больше за отказ от термина ООП в принципе, то есть прекратить вбивать новичкам в голову про «Три Кита» и всё такое, потому что в текущей ситуации и трактовке вреда от него больше чем пользы. Впрочем, видимо, такое быстро не произойдёт.
Локальное состояние легко реализуется даже «процедурном» Си. Переменные объявленные в определенном файле будут скрыты и недоступны изначально, если вы сами не сделаете их «публичными» через указание в заголовочном файле.
Да, так тоже можно реализовывать ОО-подход.

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

А вообще, полно людей геттеры+сеттеры генерируют для всех локальных переменных в классах, а во всей из себя «ООП» Джаве это ещё и в компайл-тайм вынести пытаются костылями( projectlombok.org/features/GetterSetter ), и называют это инкапсуляцией. Так что отсылка к мнению некого «большинства», ну, так себе аргумент.
Мне кажется Java-C#-C++ задизайнили в этом смысле не очень как-то (то-ли не было понимания что такое ООП, то-ли оно не очень-то было и нужно), поэтому правильное ООП в них применять неудобно (в отличии от Smalltalk-а) и, в результате, оно выродилось в полу-ООП. Ну и ладно, значит для большинства задач это подходит (и мы видим, что новые ЯП не очень и стремятся наладить обмен сообщениями между объектами, а юзают get/set). Поэтому я и не спорю с общепринятыми понятиями, просто уточняю их в своей речи.

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

А что такое ОО-конструкции?

Мне кажется Java-C#-C++ задизайнили в этом смысле не очень как-то (то-ли не было понимания что такое ООП, то-ли оно не очень-то было и нужно)

А я за правильность и нужность того оригинального понятия, собственно, не топлю. Вполне возможно. А ООП назвали чтобы лучше продать.

ну и ладно, значит для большинства задач это подходит

Можно формализовать что значит «подходит»? Так то и Си подходит, можно сказать, для всего.
Но не факт что подходит больше чем что-то другое и эффективнее других инструментов.

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

и мы видим, что новые ЯП не очень и стремятся наладить обмен сообщениями между объектами, а юзают get/set

Странно вы трактовали мою фразу… Сразу сделаю сноску, что это не языки юзают get/set а конкретные программисты.
А get/set это процедурное программирование в чистом виде(есть исключения не значимые в рамках текущей дискуссии). И если исходить упоминавшегося тезиса что популярные и продвигаемые сообществом инструменты лучше непопулярных, ООП/ФП не нужно и всем хватит процедурного кода.

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

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

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

Так я и считаю.
А что такое ОО-конструкции?
Объекты/классы. Хотя классы уже ближе к наследованию.

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

Кстати, во многом хайп вокруг ФП связано с недостатками именно полу-ООП подхода.

Можно формализовать что значит «подходит»? Так то и Си подходит, можно сказать, для всего. Но не факт что подходит больше чем что-то другое и эффективнее других инструментов.

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

не языки юзают get/set а конкретные программисты
Претензия к ЯП может быть только если они делают использование get/set легче, чем полное инкапсулирование.

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

Классы могут быть без наследования, а наследование может быть без классов )
И Объект может быть без класса.
Ну то такое, эти концепты уже и так по другому называются.
Скрин из презентации Кея, например


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

David West вещает о том же примерно( youtube.com/watch?v=RdE-d_EhzmA&list=LLd6OFj5xQf9ZhwBb4EVbdSw&index=58& )

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

Да, и сам Кей в письмах писал что ST стал чем-то «Что нужно изучать». Так не только с ООП, так происходит с очень многими популярными идеями, они упрощаются до такой степени что ничем не отличаются от того что было до этого и натягиваются(название) на уже существующие решения.
Аля «Мы теперь Agile, поэтому будем делать больше митингов», а таски в спринт все-равно менеджер какой-нибудь накидывает единолично.

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

Кроме как названиями некоторых штук(названиями а не функционалом), Java не особо чем на ST похожа. Это скорее «better c/c++» + крутая фича в виде кроссплатформеннсоти за счёт JVM (Ну девиз их там, типа «write once run everywhere»)

В общем получается, что Алан Кэй и «раскачал» ООП, и он же примешал к нему излишества, которые настолько отождествились со смыслом ООП, что оно в страхе свернуло не туда…

Там были проблемы с лицензированием, о котором syn и xerox не смогли договориться, из-за чего Sun начали пилить свой язык(Опять же — youtube.com/watch?v=RdE-d_EhzmA&list=LLd6OFj5xQf9ZhwBb4EVbdSw&index=58& ).

Претензия к ЯП может быть только если они делают использование get/set легче, чем полное инкапсулирование.

Мм… Проблема то не в языках. Я больше к тому что и на Java пишут процедурщину, и на всём.
get/set используют потому что на краткосрочной дистанции это проще и быстрее, а чтобы правильно инкапсулировать нужные данные и понять что с чем, собственно, инкапсулировать — думать нужно, а неправильная инкапсуляция/декомпозиция может оказаться совсем не лучше чем процедурный код.

И ещё
а) На уровне кода (программист): здесь, чем больше возможностей предоставляет ЯП, тем лучше.

Ну такое. Важно ещё сколько ограничений предоставляет и возможностей ограничений. Например отсутствие множественного наследования я вряд ли стал бы вписывать в исключении, потому что в каком-нибудь kotlin/C# столько удобных способов реюзать код, что наследование уже и еденичное нужно крайне редко.
Согласен. Просто считаю, что в настоящее время хорошего ООП не хватает ничуть не меньше, чем чистых функций.
Аля «Мы теперь Agile, поэтому будем делать больше митингов», а таски в спринт все-равно менеджер какой-нибудь накидывает единолично.
Если бы только этим все ограничивалось… Проблема гораздо более глубокая. Кстати, да, есть сходство.
В расте это собственно и вынесли на уровень самого языка, и всем очевидно, что 1.into() и Into::into(1) это совершенно одно и то же.

А если into() будет принимать интерфейс, то раст подберёт в рантайме функцию для переданной реализации?

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

Не понял вопроса.

Допустим функция into принимает параметр типа InterfaceType (где InterfaceType — интерфейс). И у нас есть другая функция — caller, которая примает параметр типа InterfaceType по имени a. Реальный тип a прикомпиляции неизвестен. Внутри caller написано Into::into(a). Будут ли вызываться разные функции в зависимости от реального типа a?


Динамическая диспетчеризация в расте определенно есть, но её стараются избегать

Мартин вот советует наоборот, почаще её использовать ))


С тайпклассами и макросами она особо и не нужна.

Макросы это насколько я понимаю инструмент времени компиляции. А вот насчёт тайплассов как?

Мартин вот советует наоборот, почаще её использовать ))

С генериками оно особо не нужно, как ниже показано.

С генериками оно особо не нужно, как ниже показано.

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

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


Любой виртуальный метод abstract class Bar { virtual void Foo() } можно заменить на void Foo<T>(T self) where T : Bar, и смысл будет ровно тот же, с точностью до приватных переменных и прочей оопшной машинерии.

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

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


Любой виртуальный метод abstract class Bar { virtual void Foo() } можно заменить на void Foo(T self) where T: Bar

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

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

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


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

Ну потому что стирать типы не стоит, тогда все будет работать ожидаемым образом. А так даже лучше. можно выбрать поведение снаружи объекта (то есть выбрать "базовое" или "специальное" поведение).


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

Любой виртуальный метод abstract class Bar { virtual void Foo() } можно заменить на void Foo<T>(T self) where T : Bar

А с override что делать?

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

А что дальше? Хотели полиморфизм — получили. Квадраты-треугольники вызывают соответствующие методы, все довольны.

Вот есть два класса:


class Foo {
    public virtual void Baz() { 
        Console.WriteLine("I am Foo");
    }
}

class Bar : Foo {
    public override void Baz() {
        Console.WriteLine("I am Bar");
    }
}

На какой такой хитрый дженерик вы сможете заменить метод Foo?

Я всё еще не вижу как where T : IFoo поможет переписать метод Baz на дженерики.

interface IFoo {
   void Baz();
}

class Foo : IFoo {
    public void Baz() { 
        Console.WriteLine("I am Foo");
    }
}

class Bar : IFoo {
    public void Baz() {
        Console.WriteLine("I am Bar");
    }
}

void UseBaz<T>(T t) where T : IFoo => t.Baz();

Чем такой вариант не устраивает?

Тем, что он не демонстрирует вашего исходного утверждения:


Любой виртуальный метод abstract class Bar { virtual void Foo() } можно заменить на void Foo<T>(T self) where T : Bar

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

Ок, я неправильно сформулировал.


Любое использование вида void UseFoo(Bar bar) где abstract class Bar { virtual void Foo() } можно заменить на void UseFoo<T>(T bar) where T : Bar.

Ок, но таким образом вы не избегаете динамической диспетчеризации...

Избегаем почти везде по кодовой базе. В редких случаях где нам нужно забыть конкретный тип (например сделать Bar[]) придется сделать так же, как принято в ООП, да, там не избегаем.


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

Как же вы её избегаете, если ваш пример выше её использует?

Где? UseBaz генерик и никакой виртуализации не использует, оба метода статические. Интерфейс в данном случае просто маркерный "У типа есть такой-то метод", никакой виртуализации от него не требуется. Могу ровно то же на расте написать, там-то никакого наследования очевидно нет.

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

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

UseFoo<T>(T foo) where T : IFoo — это ни разу не "видно какой тип приходит". Он такой же неопределенный как был и в классическом варианте с UseFoo(IFoo foo)

Это совсем разные вещи. Это явно видно когда реализации IFoo, например, структуры.


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

Да, для структур это и правда видно. Но тут обсуждаются классы.


Получается достаточно равноценным.

Чтобы оно было равноценным — оно должно быть альтернативой, а тут никакой альтернативы нет. Метод UseFoo<T> использует И интерфейс с динамической диспетчеризацией, И дженерик одновременно.

Да не использует он динамическую диспетчеризацию.


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


Да, для структур это и правда видно. Но тут обсуждаются классы.

Я всегда рассуждаю в терминах типов. Структуры/классы — маргинальный выбор когда нужна та или иная семантика копирования.

Да не использует он динамическую диспетчеризацию.

Тогда он — не альтернатива, очевидно.


К слову, composition over interitance работает через динамическую диспетчеризацию. Без сабтайпинга наследование композицией заменить нельзя.

Наверху код знает какой конкретный тип у объекта.

Но на практике-то не знает.

У нас как-то получается проектировать, что знает.

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

Не в рантайме, а при компиляции.

Хотелось бы именно в рантайме


В рантайме в общем тоже можно, но тут уже вопрос, есть ли доступ к типаж-объекту. И мало кто таким занимается.

Тут же всё равно придётся пересобирать код, если хочется добавить новый объект, я же правильно понял? Наверное именно поэтому это мало кому нужно.

Тут же всё равно придётся пересобирать код, если хочется добавить новый объект

Эээмм… А как иначе? Это же не скрипт.
Допустим, можно искрутиться и подключить dll/so, закрыв глаза на отсутствие стабильного ABI, и вынуть типаж-объекты оттуда, не владея конечными типами. Rust это прекрасно съест, пока на ограничении типажа не висит 'static. Показать на примере увы, некак, песочница так не умеет, а тестовый пример годичной давности я давно потер. Допускаю, что у меня был не совсем удачный пример, потому что и хост, и плагин я собирал на одной и той же системе, одним компилятором, с разницей в пару минут, а это тепличные условия.

По крайней мере для ФП он прав, ссылочная прозрачность — единственное, что отличает ФП от остальных парадигм.

Это если я на js пишу везде с const то у меня ФП сразу?

в жс const к сожалению совсем не означает того что должен. И два вызова console.log не эквивалентны одному.

Я всё это проигнорирую и перейду сразу к самой сути, к неотъемлемому свойству которое отделяет ФП от других стилей. Вот оно:

f(a) == f(b) если a == b.

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

Ерунда какая то несусветная.
Пусть f(.) — функция потокового шифрования блоков с гаммированием… а некая f_init() ее инициализация. Естественно f(a)=/=f(b) если a=b хотя это самое настоящее ФП. И это самый простой пример когда поведение определено состоянием.
Интересное у вас понимание ФП. В ФП слово «функция» используется в математическом смысле — как отображение элементов одного множества на элементы другого множества.

Соответственно, никакой «инициализации» не может.
Вы только что отменили все, что написано в ФП в автоматном стиле. А это пласт софта объемом в пропасть. С вашим подходом проще признать что ФП не существует — ибо функция в вашем понимании слишком абстрактна.
ФП в автоматном стиле? Можно пример?

Результат работы f_init должен передаваться в функцию f, которая должна работать детерминировано. Тогда будет настоящее ФП

Тогда получается что:

fp=fopen('blablabla.txt',wb);
fwrite('blablabla',9,1,fp);
fclose(fp);

Где то внутри скрыт счетчик от начала файла, и хотя он и передается в скрытом виде через fp — поведение то у fwrite с математической точки зрения, все равно недетерменировано, да еще и место на диске кончилось давно (в математической абстракции то диск естественно бесконечен) т.е. это «не настоящее ФП». А где тогда существует настоящее ФП? разве, что в таких статьях 'ниочем'…
UFO just landed and posted this here

А зачем это проверять? Ну вот какая вызывающему коду разница куда я там под капотом обращаюсь?

UFO just landed and posted this here
  1. Библиотека и функция — немного разные понятия. Удобно было бы выдать права один раз на библиотеку (или колстек), а не на каждую функцию в ней.


  2. Есть много разных способов инвертировать зависимости помимо DI с ломанием инкапсуляции.


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


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


  5. А это-то тут при чём?


UFO just landed and posted this here
Вы просто пишете тип каждой функции точно так же, как писали раньше (ну или он точно так же выводится).

Я просто пишу getConfig() и меня не волнует откуда он берётся, из базы или памяти или файла или ещё откуда. Это не моя зона ответственности.


есть вспомогательный метод parseJsonFromFile

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


где здесь ломается инкапсуляция

В этом как бы вся суть DI — выворачивать систему кишками наружу.


Это у вас просто язык не ленивый.

А ленивый язык волшебным образом знает что надо кешировать (и на сколько), а что не надо?


это точно те же эффекты.

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

UFO just landed and posted this here
Хороший подход к программированию.

Это не так.


Честно говоря, мне не интересно всё это обсуждать по десятому кругу.

Коду вообще все без разницы, а программисту/человеку — нет.

Ниже вы правильно говорите:
Я просто пишу getConfig() и меня не волнует откуда он берётся, из базы или памяти или файла или ещё откуда. Это не моя зона ответственности.

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

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

P.S. надеюсь тут не будет возражений в духе «читайте контракты».

Ок, вот вам аналогия по проще: Когда вы пишете a=1 вы можете быть уверены в какой именно регистр процессора попадёт ваша переменная? Ассемблер вам эти гарантии даёт. Любой язык высокого уровня — нет. Это называется абстракция.

UFO just landed and posted this here
Смысл появляется тогда, когда у вас существуют функции, которые не живут в IO.

Ну почему же. С IO-устройствами тоже можно обращаться функционально, если наложить ограничение на изменяемость данных, т.е. если от CRUD отбросить Update и Delete. Именно в этом и заключается идея Event Sourcing, со слов автора термина CQRS Greg Young. Там у него есть еще вот такой интересный доклад на тему функциональной обработки данных.
UFO just landed and posted this here
А извне их кто-то при этом может менять?
Если Вы правильно наложите ограничение — то не может. Можно даже наложить это ограничение технически, используя, например, обособленный сетевой периметр, ACL, DB-триггер и т.п. А лучше даже использовать специализированные хранилища. Хотя, чисто технически, можно подменить даже stack вызова функции, — так что тут все зависит от баланса стоимости затрат на реализацию этого ограничения и выгод от его взлома.
Смысл появляется тогда, когда у вас существуют функции, которые не живут в IO. Которые гарантированно (компилятором) не могут выдавать команды для работы с файловой системой. Или которые гарантированно (компилятором) не могут менять глобальные переменные. Или даже их читать.

Хм, а подобные проверки никто не пытался прикрутить к компиляторам mainstream языков? В теории должно быть не очень сложно для Java или C#. А если все-таки очень надо, то помечать аннотацией какой-нибудь. (кажется я знаю, чем займусь этим летом).
Или которые гарантированно (компилятором) не лезут в интернет или в БД.

Такое скорее всего не получится, к сожалению.
UFO just landed and posted this here
Напомню что у нас проблема вообще то в классификации.
Очевидно, что примеры идентичны.
Вопрос стоит являются ли они ОБА ФП — (заявленный признак бред)
Или оба НЕ ЯВЛЯЮТСЯ (заявленный признак истинна — но тогда чем они являются? ООП что ли?! ).
Вы в ответ на это можете возразить: «ну так давайте считать наши программы на С такой же чистой последовательностью команд, C — чистый язык!» И вы, самое интересное, будете абсолютно правы! Вы можете считать, что весь ваш сишный код просто живёт в монаде IO. Компилятор её там неявно за вас дописывает.
Я думаю, что после этого предложения стоило бы дописать:
Впрочем, в вашем сишном коде нет функций, потому что ваш сишный код — экземпляр нефункционального типа IO.

И вывод: ФЯП нужны, поскольку писать без функций, когда в предметной области есть функциональные зависимости, неуютно.
Где то внутри скрыт счетчик от начала файла, и хотя он и передается в скрытом виде через fp — поведение то у fwrite с математической точки зрения, все равно недетерменировано т.е. это «не настоящее ФП»

Да, всё так, это классический пример нарушения чистоты функции.


А где тогда существует настоящее ФП?

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

И зачем нужен движок игры — без самой игры и игрока? может быть движок калькулятора без недетерменированого ввода с клавиатуры того что нужно калькулировать какое то применение имеет? Как у вас это работает? движок игры — ФП. а все игры на нем написанные — что тогда?

99% функций чистые 1% грязные — как будем это классифицировать?
И зачем нужен движок игры — без самой игры и игрока?

Чтобы встраивать в игру, в которую будет играть игрок ))


движок игры — ФП. а все игры на нем написанные — что тогда?

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

UFO just landed and posted this here
Естественно есть холивар и противопоставление.

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

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

UFO just landed and posted this here
Вы (или я в своём комментарии) сразу постулируете полезность статической типизации как средства для того, чтобы избегать ерунды

Собственно Мартин как раз говорит, что не так уж она и полезна. Хотя тут я с ним решительно не согласен.


И получается, что некоторые каноничные ООП-фишки не очень хорошо работают с некоторыми ФП-фишками.

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


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


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

Кстати, если добавить сюда юнит тесты — точка зрения Дяди Боба.

UFO just landed and posted this here
Наверное, пора написать постик на эту тему [что сама по себе референциальная прозрачность бессмысленна].

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


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

Готовый тайпчекер слишком жёсткий, если сделать кастомный, то код получится проще и выразительнее

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

К этому все привыкли, но в последнее время неизменяемые объекты встречаются в коде всё чаще и чаще.


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

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

Когда мы видим выражение 1. мы видим функцию f, которая вызывается в которую передаётся объект o. При этом подразумевается, что есть только одна функция с именем f

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

А как же перегрузка функций?

Дальше из текста статьи понятно, что под функцией с именем f Мартин имеет в виду функцию с именем f, которая принимает параметр o какого-то конкретного типа. Наверное не счёл нужным уточнять.


И как же статический полиморфизм с выбором реализации на основе типа возвращаемого значения, как это в ФП любят?

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

Дальше из текста статьи понятно, что под функцией с именем f Мартин имеет в виду функцию с именем f, которая принимает параметр o какого-то конкретного типа

А как насчёт вот такой функции?


id :: forall a. a -> a

Тут forall необязателен, но я его написал чтобы было понятно, что это именно что одна функция, а не семейство функций… Какой конкретный тип параметра она принимает? :-)

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

Похоже-непохоже, но в статье про ФП пропускать Хаскель всё равно нельзя.


Да, в Джаве был бы дженерик.

Тут важный момент, что это магия времени компиляции, а Мартин делает акцент на динамический полиморфизм. Что касается Хаскеля, то у меня сложилось впечатление, что у Мартина ФП автоматически означает Clojure

Ну вот вам магия рантайма:


data Foo = forall s. Show s => Foo s
instance Show Foo where
    show (Foo x) = show x

Тут функция show (аналог toString) только в рантайме узнает аргумент какого типа ей надо превратить в строку.

UFO just landed and posted this here

А это уже детали реализации.

UFO just landed and posted this here

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

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

Sign up to leave a comment.

Articles