Языки функционального программирования это
Знакомство с функциональным программированием в Python, JavaScript и Java
Функциональное программирование (ФП) представляет собой процесс создания ПО путем компоновки чистых функций. В современном мире работодатели ищут программистов, способных применять к решению задач различные парадигмы программирования. При этом наблюдается рост популярности именно функциональной, так как она очень эффективна и позволяет легко масштабировать проекты.
Как же можно половчее переключиться от ООП к ФП?
Сегодня мы изучим ключевые принципы функционального программирования, рассмотрим их реализацию в Python, JavaScript и Java, а также прикинем, в каком направлении лучше всего продолжать двигаться.
По ходу статьи мы ответим на следующие вопросы:
Что такое функциональное программирование?
Функциональное программирование — это парадигма декларативного программирования, в которой программы создаются путем последовательного применения функций, а не инструкций.
Каждая из этих функций принимает входное значение и возвращает согласующееся с ним выходное значение, не изменяясь и не подвергаясь воздействию со стороны состояния программы.
Для таких функций предусмотрено выполнение только одной операции, если же требуется реализовать сложный процесс, то используется уже композиция функций, связанных последовательно. В процессе ФП мы создаем код, состоящий из множества модулей, поскольку функции в нем могут повторно использоваться в разных частях программы путем вызова, передачи в качестве параметров или возвращения.
Чистые функции не производят побочных эффектов и не зависят от глобальных переменных или состояний.
Функциональное программирование используется, когда решения легко выражаются с помощью функций и не имеют ощутимой связи с физическим миром. В то время как объектно-ориентированные программы моделируют код по образцу реальных объектов, ФП задействует математические функции, в которых промежуточные или конечные значения не сопоставляются с объектами физического мира.
К наиболее распространенным областям, применяющим ФП, относятся проектирование ИИ, алгоритмы классификации в МО, финансовые программы, а также продвинутые модели математических функций.
Проще говоря: функциональные программы выполняют много чистых однозадачных функций, совмещенных в последовательность для решения сложных математических или не связанных с физическим миром задач.
Преимущества функционального программирования
Языки функционального программирования
Функциональная парадигма поддерживается не во всех языках. Некоторые из них, например Haskell, спроектированы именно для этой задачи, в то время как другие, например JavaScript, реализуют возможности и ООП, и ФП. Есть же и такие языки, где функциональное программирование невозможно в принципе.
Функциональные языки:
Языки с функциональными возможностями
Принципы функционального программирования
Переменные и функции
Ключевыми составляющими функциональной программы являются уже не объекты и методы, а переменные и функции. При этом следует избегать глобальных переменных, потому что изменяемые глобальные переменные усложняют понимание программы и ведут к появлению у функций побочных эффектов.
Чистые функции
Для чистых функций характерны два свойства:
Побочные эффекты же возникают, если функция изменяет состояние программы, переписывает вводную переменную или в общем вносит какие-либо изменения при генерации вывода. Отсутствие же побочных эффектов снижает риски появления ошибок по вине чистых функций.
Ссылочная прозрачность означает, что любой вывод функции должен допускать замену на ее значение, не изменяя при этом результата программы. Этот принцип гарантирует, что вы создаете такие функции, которые выполняют только одну операцию и достигают согласованного вывода.
Ссылочная прозрачность возможна только, если функция не влияет на состояние программы или в общем не старается выполнить более одной операции.
Неизменяемость и состояния
Неизменяемые данные или состояния не могут изменяться после их определения, что позволяет сохранять постоянство стабильной среды для вывода функций. Лучше всего программировать каждую функцию так, чтобы она выводила один и тот же результат независимо от состояния программы. Если же она зависит от состояния, то это состояние должно быть неизменяемым, чтобы вывод такой функции оставался постоянным.
Подходы функционального программирования обычно избегают применения функций с общим состоянием (когда несколько функций опираются на одно состояние) и функций с изменяющимся состоянием (которые зависят от изменяемых функций), потому что они уменьшают модульность программы. Если же вы не можете обойтись без функций с общим состоянием, сделайте это состояние неизменяемым.
Рекурсия
Одно из серьезных отличий объектно-ориентированного программирования от функционального в том, что программы последнего избегают таких конструкций, как инструкции if else или циклы, которые в разных случаях выполнения могут выдавать разные выводы.
Вместо циклов функциональные программы используют для всех задач по перебору рекурсию.
Функции первого класса
Функции в ФП рассматриваются как типы данных и могут использоваться как любое другое значение. Например, мы заполняем функциями массивы, передаем их в качестве параметров или сохраняем их в переменных.
Функции высшего порядка
Эти функции могут принимать другие функции в качестве параметров или возвращать функции в качестве вывода. Они делают возможности вызова функций более гибкими и позволяют легче абстрагироваться от действий.
Композиция функций
Для выполнения сложных операций функции можно выполнять последовательно. В этом случае результат каждой функции передается следующей функции в виде аргумента. Это позволяет с помощью всего одного вызова функции активировать целую серию их последовательных вызовов.
Функциональное программирование в Python
В Python реализована частичная поддержка ФП, и некоторые используемые в нем решения математических программ легче реализуются с помощью именно функционального подхода.
Самая сложная часть перехода к использованию такого подхода в сокращении числа используемых классов. В Python классы имеют изменяемые атрибуты, что усложняет создание чистых неизменяемых функций.
Попробуйте оформлять весь код на уровне модулей и переключайтесь на классы только по мере необходимости.
Давайте посмотрим, как добиться чистых неизменяемых функций и функций первого класса в Python, после чего познакомимся с синтаксисом для их композиции.
Чистые и неизменяемые функции
Многие из встроенных в Python структур данных являются неизменяемыми по умолчанию:
Кортежи особенно полезны при использовании в качестве неизменяемой формы массива.
Этот код вызывает ошибку, потому что старается переопределить неизменяемый объект кортежа. Эти неизменяемые структуры данных рекомендуется использовать в функциональных программах Python для получения чистых функций.
Нижеприведенную функцию можно считать чистой, так как у нее нет побочных эффектов, и она всегда возвращает одинаковый вывод:
Функции первого класса
Отметим, что в Python функции рассматриваются как объекты, и ниже мы приводим краткое руководство по их возможному использованию:
Функции в качестве объектов
Передача функции в качестве параметра
Возвращение функции из другой функции
Композиция функций
В завершении на строке 6 мы возвращаем результаты этой композиции функций.
Функциональное программирование в JavaScript
В связи с поддержкой функций первого класса JavaScript уже давно предлагает функциональные возможности. ФП на этом языке с недавних пор начало набирать популярность, так как повышает производительность при использовании в таких фреймворках, как Angular и React.
Давайте взглянем на то, как можно реализовывать разные функциональные принципы с помощью JS. Сосредоточимся мы на создании ключевых компонентов, а именно чистых функций, функций первого класса и композиций функций.
Чистые и неизменяемые функции
Функции первого класса
JavaScript поддерживает функции первого класса по умолчанию. Вот краткое руководство по возможным действиям с функциями в этом языке:
Присвоение функции к переменной
Добавление функции в массив
Передача функции в качестве аргумента
Возвращение функции из другой функции
Функциональная композиция
В JavaScript мы можем компоновать функции при помощи цепочек вызовов:
В качестве альтернативы можно передать выполнение функции в следующую функцию:
Первая функция в этом списке использует в качестве ввода начальный аргумент, а последующие функции наследуют свои вводные аргументы из вывода предшествующих.
Функциональное программирование в Java
Java очень ограниченно поддерживает ФП по сравнению с Python или JS. Тем не менее в нем есть возможность имитировать функциональное поведение при помощи лямбда функций, потоков и анонимных классов.
В конце концов, компилятор Java создавался без учета функционального программирования, в связи с чем не может использовать многие из преимуществ этой парадигмы.
Чистые и неизменяемые функции
В Java есть несколько неизменяемых структур данных:
Ключевое слово final в классе предотвращает создание дочернего класса. Использование final для name и regNo делает невозможным изменение значений после построения объекта.
В этом классе также присутствуют параметризованный конструктор и геттеры для всех переменных, но при этом отсутствуют сеттеры, что помогает добиться для данного класса неизменяемости.
Функции первого класса
В Java для получения функций первого класса можно использовать лямбда функции. Лямбда принимает список выражений, например методов, но не требует имени или предварительного определения.
Лямбда выражения можно использовать вместо функций, так как они рассматриваются как стандартные объекты класса, которые можно передавать или возвращать.
Композиция функций
И наоборот — метод andThen выполняет сначала внешнюю функцию, а затем функцию из своих параметров.
Каждая из этих композиций выполняет обе изначальные функции, но в разном порядке. Теперь вы можете вызвать композиции для выполнения обеих исходных функций с одинаковым вводом.
Что изучать дальше
Сегодня мы пробежались по наиболее общим принципам функционального программирования и узнали, как они проявляются в Python, JavaScript и Java.
Одним из ведущих функциональных языков, переживающим этап возрождения, является Scala. Многие технологические гиганты, такие как Twitter и Facebook, начали использовать этот язык и уже ищут программистов с соответствующими навыками, поэтому рекомендуем выбрать в качестве следующего этапа на пути освоения ФП именно Scala.
Современные языки программирования, которые заставят вас страдать: Часть 2, функциональные языки
Современные языки программирования, которые заставят вас страдать: Часть 2, функциональные языки
Прим. ред. Это перевод статьи Ильи Суздальницкого. Мнение редакции может не совпадать с мнением автора оригинала.
Это вторая и финальная часть перевода статьи про современные язки программирования. В первой части — «Современные языки программирования, которые заставят вас страдать: Часть 1, ООП», рассматривались объектноориентированные языки. В этой части автор подробно разбирает функциональные языки программирования которые принадлежат семейству ML ( и некоторые С-подобные).
Функциональные языки программирования
Haskell
Типизация: нет более мощной системы типов, чем в Haskell. Он поддерживает, как алгебраические типы данных, так и классы типов. Язык способен вывести почти любой тип.
Сложность изучения: чтобы продуктивно использовать Haskell нужно изучить теорию категорий. Даже для того, чтобы написать hello world, нужно понимать монады.
Сообщество: больше заинтересовано в академических дискуссиях, чем в решении реальных проблем.
Функциональная чистота: чистые функции — прекрасны. Побочные эффекты (взаимодействие с внешним миром, изменение состояния) причина большого количества ошибок в программах. Будучи чисто функциональным языком, Haskell полностью избавлен от них.
Конечно в языке есть обходные пути, для взаимодействия с внешним миром. Для этого используется набор инструкций (монад ввода-вывода). Это могут быть такие инструкции: получить строку с клавиатуры, использовать её в некой функции, напечатать результат в консоль. Среда выполнения делает это за нас. Мы никогда не выполняем код взаимодействующий с внешним миром напрямую.
На практике, такая сосредоточенность на функциональной чистоте значительно увеличивает число абстракций, усложняет код и уменьшает продуктивность разработчиков.
Поддержка NULL: как и в Rust, Haskell не поддерживает нулевые ссылки. Вместо этого в нём есть Optional, на случай, если значения может не быть.
Обработка ошибок: некоторые функции могут выбрасывать ошибки, но более свойственный для языка подход похож на Result в Rust.
Иммутабельность: язык имеет первоклассную поддержку иммутабельных структур данных.
Сопоставление с образцом: поддерживается.
Экосистема: стандартная библиотека неорганизованна. По умолчанию, в Haskell используются функции выбрасывающие исключение, вместо возврата Option (золотой стандарт для функционального программирования). В довершение всего, у Haskell есть два менеджера пакетов — Cabal и Stack.
Вердикт: мне бы очень хотелось полюбить Haskell, однако он навсегда застрял в академических кругах. Является ли он худшим из функциональных языков? Решать вам, но я думаю, что это так.
OCaml
Типизация: не поддерживает классы типов, но есть функторы (модули высшего порядка). Язык статически типизирован и выводит типы почти также хорошо, как Haskell.
Экосистема: имеет небольшое сообщество и страдает от недостатка библиотек. Языку не хватает достойного веб-фреймворка. Документация хуже чем других языков.
Инструментарий: инструменты языка неорганизованны. Имеет три менеджера пакетов: Opam, Dune, и Esy. Язык известен некачественными сообщениями об ошибках компилятора. Это не критично, но снижает производительность программистов.
Параллелизм: разработчики годами ждут поддержки многоядерности, но её пока что не предвидится.
Поддержка NULL: нет нулевых ссылок, использует Option для неуказанных значений.
Обработка ошибок: нативный подход — использование типа Result.
Иммутабельность: язык имеет первоклассную поддержку иммутабельных структур данных.
Сопоставление с образцом: поддерживается.
Вердикт: Ocaml — хороший функциональный язык. Его основные недостатки: отсутствие поддержки многоядерности и небольшое сообщество (причина недостатка обучающих материалов и библиотек). Поэтому, я бы не рекомендовал язык к использованию в работе.
Scala
Экосистема: Scala — это язык из семейства Си, который выполняется на виртуальной машине Java. Это значит, что у вас есть доступ к огромной экосистеме библиотек Java.
Типизация: язык плохо справляется с приведением типов. Однако Scala поддерживает Higher-Kinded типы и типы классов.
Немногословность/читаемость: хотя программы на Scala и отличаются лаконичностью (особенно по сравнению с Java), читаемость страдает. Scala — один из немногих функциональных языков, принадлежащих к семейству Си. Си-подобные языки были предназначены для императивного программирования, а ML для функционального. Поэтому функциональный код на Scala может иногда выглядеть странно.
Синтаксис для алгебраических типов данных оставляет желать лучшего:
Этот же код на языке ReasonML:
Скорость: hello world на языке Scala может компилироваться до 10 секунд, на слабом железе. Компиляция производится только на одном ядре процессора, что отрицательно влияет на скорость.
Из-за того, что Scala работает на виртуальной машине Java, программы запускаются дольше.
Сложность изучения: один из самых сложных функциональных языков. Scala, как и C++ обладает множеством функций, которые, однако, усложняют его изучение.
Иммутабельность: Scala обладает первоклассной поддержкой неизменяемых структур данных (с использованием классов образцов).
Поддержка NULL: с одной стороны, Scala поддерживает нулевые ссылки. С другой стороны, характерный для языка способ обработки отсутствующих значений — паттерн Option.
Обработка ошибок: нативный подход — использование типа Result.
Параллелизм: можно использовать отличный инструмент — Akka.
Сопоставление с образцом: поддерживается.
Вердикт: Scala пытается делать слишком многое. Его разработчикам пришлось пойти на множество компромиссов, чтобы поддерживать как ООП, так и функциональное программирование.
Сообщения об ошибках: компилятор выдаёт самые понятные сообщения об ошибках, что я когда-либо видел.
Обработка ошибок: в языке нет ошибок выполнения и исключений. Как и другие функциональные языки, использует тип Result для обработки ошибок.
Функциональная чистота: как и Haskell, Elm — чисто функциональный язык. И в данном случае это скорее минус, потому что любой рефакторинг превращается в кошмар.
Слишком строгий:
Скриншот с сайта https://www.reddit.com/r/ProgrammerHumor/comments/8we9zh/im_learning_elm_and_it_immediately_declared_war/
Elm настолько строгий, что использование табуляций считается синтаксической ошибкой.
Сосредоточенность на отсутствии ошибок убивает язык. В версии 0.19, взаимодействие с JS библиотеками сделали практически невозможным. Конечно для того, чтобы стимулировать людей писать свои библиотеки на Elm. Но компаний, у которых есть для этого достаточно ресурсов, крайне мало.
Поддержка React: Elm создаёт свою собственную виртуальную модель DOM и не использует React. Это лишает разработчиков доступа к обширной экосистеме библиотек и компонентов, созданных для React.
Состояние языка: с каждым новым релизом в языке происходят сильные изменения, которые могут лишить вас возможности использовать его.
К сожалению, прошло уже больше года с тех пор, как была выпущена новая версия Elm (0.19.1). О состоянии разработки ничего не известно. Возможно, что она вообще больше не ведётся.
Сопоставление с образцом: поддерживается.
Иммутабельность: обладает первоклассной поддержкой неизменяемых структур данных.
Поддержка NULL: нет нулевых ссылок, использует Option для неуказанных значений.
Вердикт: Elm — отличный язык, но к сожалению у него нет будущего.
Типизация: единственный минус его системы типов — отсутствие Higher-Kinded типов. Тем не менее система типов очень надежна, компилятор способен вывести практически все что угодно. F# имеет надлежащую поддержку алгебраических типов данных.
Не полностью функциональный: в отличие от Haskell/Elm, F# очень прагматичен и не обеспечивает функциональную чистоту.
Обучающие ресурсы: есть действительно хорошие учебные ресурсы.
Сложность изучения: F# — один из самых простых функциональных языков.
Экосистема: имеет довольно небольшое сообщество и в отличие от таких языков как Elixir, оно не имеет таких же замечательных библиотек.
Параллелизм: работает поверх CLR, который не имеет такой же превосходной поддержки параллелизма, как Elixir на виртуальной машине Erlang.
Поддержка NULL: NULL-значения обычно не используются. Неуказанные значения обрабатываются с помощью паттерна Option.
Обработка ошибок: ошибки обрабатываются с помощью паттерна Result.
Иммутабельность: обладает первоклассной поддержкой неизменяемых структур данных.
Сопоставление с образцом: поддерживается.
Вердикт: F# — очень надежный язык программирования с действительно хорошей системой типов. Он почти так же хорош, как Elixir для разработки Web API (подробнее об этом далее). Однако проблема F# заключается не в том, что у него есть, а в том, чего у него нет. Если сравнить его с Elixir, его функционал параллелизма, богатая экосистема и удивительное сообщество перевешивают любые преимущества статической типизации, которые предоставляет F#.
Однако F# — лучший язык для финтеха. Также язык отлично подойдёт для энтерпрайз разработки. Его мощная система типов позволяет моделировать сложную бизнес логику. Очень рекомендую прочитать эту книгу — «Domain Modeling Made Functional».
ReasonML
Не является надмножеством JavaScript: синтаксис ReasonML похож на JavaScript, что делает его более доступным для всех, кто имеет опыт работы с JavaScript. Однако, в отличие от TypeScript, ReasonML даже не пытается быть надмножеством JavaScript. ReasonML не должен был унаследовать плохие дизайнерские решения, десятилетиями принимаемые в JavaScript.
Сложность изучения: ReasonML является одним из самых простых функциональных языков.
Не полностью функциональный: ReasonML очень прагматичен, ориентирован на производительность разработчиков и быстрое достижение результатов.
Типизация: его система типов почти так же хороша, как у Haskell. Самым большим недостатком является отсутствие классов типов, но он поддерживает функторы (модули высшего порядка).
ReasonML статически типизирован и выводит типы почти так же хорошо, как и Haskell.
Экосистема: как и TypeScript, ReasonML имеет доступ ко всей экосистеме JavaScript.
Взаимодействие с JavaScript/TypeScript: компилируется в обычный JavaScript. Поэтому, в одном проекте можно использовать как ReasonML, так и JavaScript/TypeScript.
ReasonML and React — отличное сочетание: поскольку ReasonML статически типизирован, нет необходимости беспокоиться о PropTypes. В отличие от JavaScript, при использовании ReasonML ничто не перерисовывается без необходимости — вы получаете отличную производительность React из коробки!
Инструменты: язык далеко не такой зрелый, как его альтернативы, так что могут возникнуть некоторые проблемы с инструментами. Например, официально рекомендуемое расширение VSCode — reason-language-server в настоящее время не работает.
ReasonML использует компилятор OCaml под капотом, а OCaml известен посредственными сообщениями об ошибках компилятора. Это не критично, но может повлиять на производительность разработчиков.
Поддержка NULL: нет нулевых ссылок, использует Option для неуказанных значений.
Иммутабельность: обладает первоклассной поддержкой неизменяемых структур данных.
Сопоставление с образцом: поддерживается.
Вердикт: это отличный язык для веб-разработки. ReasonML, вероятно, является тем, чем всегда стремился быть TypeScript, но потерпел неудачу. ReasonML добавляет статическую типизацию в JavaScript, убирая при этом все плохие фичи (и добавляя современные фичи, которые действительно нужны).
Elixir
Экосистема: это сильная сторона языка. Автор языка также разрабатывает крутые библиотеки: Phoenix и Ecto. В отличие от других языков, у Elixir нет множества библиотек с дублирующимся функционалом, а существующие — очень хороши.
Имеет хорошую документацию, даже к стандартной библиотеке.
Фреймворк Phoenix: поддерживает из коробки: вебсокеты, routing, HTML templating language, internationalization, JSON encoders/decoders, seamless ORM integration(Ecto), sessions, SPA toolkit и многое другое. Также фреймворк известен своей производительностью — способен обрабатывать миллионы одновременных подключений на одной машине.
Фуллстек Elixir: Phoenix недавно представил LiveView, который позволяет создавать насыщенные веб-интерфейсы реального времени прямо в Elixir. LiveView даже заботится о синхронизации состояния клиента и сервера, а это значит, что нам не нужно беспокоиться о разработке и обслуживании REST/GraphQL API.
Обработка данных: Elixir может быть надежной альтернативой Python для многих задач связанных с обработкой данных. Скрепер есть как у Python, так и у Elixir, и последний предлагает гораздо лучшее решение и экосистему для этой задачи.
Такие инструменты, как Broadway, позволяют строить конвейеры приема/обработки данных в Elixir.
Типизация: на мой взгляд, отсутствие корректной статической типизации — самый большой недостаток Elixir.
Скорость: компилятор Elixir является многопоточным и обеспечивает невероятно высокую скорость компиляции. В отличие от JVM, виртуальная машина Erlang запускается быстро. Производительность во время выполнения очень хороша.
Надёжность: код на Elixir выполняется поверх Erlang, который использовался более 30 лет для создания самого надежного программного обеспечения в мире. Некоторые программы, работающие на виртуальной машине Erlang, смогли достичь надежности 99,9999999%. Ни одна другая платформа в мире не может похвастаться таким же уровнем надежности.
Параллелизм: язык использует альтернативный подход к параллелизму — модель акторов. При таком подходе, у акторов (процессов) нет ничего общего. Единственный способ общения между различными процессами — отправка сообщений.
Elixir, в отличие от Go, убивает только тот процесс в котором произошла ошибка, а не всю программу. Более того, этот процесс будет автоматически перезапущен его супервизором.
Elixir строится на основе Erlang, который известен своими большими возможностями распараллеливания, и использует совершенно другой подход к параллелизму, называемый моделью актора. В рамках этой модели, между процессами (акторами) нет ничего общего. Каждый процесс поддерживает свое собственное внутреннее состояние, и единственный способ общения между различными процессами — отправка сообщений.
Процессы в Elixir очень легковесны, их можно запускать тысячами на одной машине.
Масштабирование: параллельные вычисления в Go быстрее чем в Elixir, если это происходит на одной машине. Но при масштабировании происходит обратное. Elixir легко справляется с такими вещами как: кластеризация, RPC и сетевые взаимодействия. В некотором смысле, виртуальная машина Erlang работала с микросервисами за десятилетия до того, как они вошли в обиход. Каждый процесс можно рассматривать как микросервис — как и микросервисы, процессы независимы друг от друга. Микросервисы без сложностей Kubernetes? Именно для этого и был создан Elixir.
Обработка ошибок: язык использует уникальный подход к обработке ошибок. В то время как чисто функциональные языки (Haskell/Elm) предназначены для минимизации вероятности появления ошибок, Elixir предполагает, что ошибки неизбежно произойдут.
Выбрасывать исключения в Elixir — правильно, в то время как обрабатывать исключения обычно не рекомендуется. Вместо этого супервизор процесса автоматически перезапустит неудачный процесс, чтобы программа продолжила работать.
Сложность изучения: язык можно освоить за пару месяцев. Однако освоение OTP может занять некоторое время. OTP — киллер фича языка. OTP — это набор инструментов и библиотек от Erlang, на которых строится Elixir. Это секретный ингредиент, который значительно упрощает построение параллельных и распределенных программ.
Обучающие ресурсы: их существует огромное количество. И почти все из них подойдут для новичков.
Сопоставление с образцом: поддерживается.
Вычисления: язык не справляется с задачами в которых требуется большое количество вычислений.
Вердикт: Elixir, вероятно, является самым зрелым из всех функциональных языков. Он работает на виртуальной машине, созданной для функционального программирования. Язык был разработан с нуля для параллельных вычислений, и идеально подходит для современной эры многоядерных процессоров. Это лучший язык для Web API. OTP и модель акторов делают язык лучшим решением для параллельных и распределённых программ.
Подходящий инструмент
Точно также, как не стоит пытаться забить гвоздь отвёрткой, не нужно использовать один язык программирования повсюду. Каждый из них имеет свою область применения.
Go — лучший язык для системного программирования. Для фронтенда несомненно стоит выбрать ReasonML. Абсолютный лидер для разработки Web API — Elixir. Как и для любых задач связанных с параллельными и распределёнными программами. Python, это к сожалению единственный адекватный вариант для data science.
Это довольно неоднозначная статья. Очевидно, что автор предпочитает функциональные языки программирования объектноориентированным. Если вы не согласны с рейтингом и можете аргументировать свою точку зрения, добро пожаловать в комментарии.