Комментарии 89
Упростил текст, убрал технические детали под кат, чтобы не пугать ими тех, кому это не нужно. Написал смешное обновление от 2024 года. Обновил картинку с зарплатами
Примеры, ссылки и остальные картинки оставил старые.
Если кто-то забредет и найдет в статье что-то еще, что просит улучшения, или принесет полезную подходящую ссылку / картинку / пример, я буду рад
много инженеров потратило десятки человеко-лет на разработку виртуальной машины и достигли скоростей, превышающих Си++
А можно ссылку на замеры? Или это просто так сказано, чтобы похвалить JVM?
www.androidauthority.com/java-vs-c-app-performance-689081
Причины:
У скалы один из самых сложных компиляторов
важно знать как устроена виртуальная машина джава, и учитывать ее особенности. Например, такие как Type Erasure
появилось типизированное абстрактное синтаксическое дерево компилятора
Объектно-ориентированное программирование
Функциональное программирование
умение пользоваться иммутабельными структурами данных
безопасный многопоточный код
гибкие и красивые DSL, вкладывать абстракции друг в друга
ну и самое главное
вакансий для скала-программистов значительно меньше, чем для джава-программистов
Питон — это не про ооп и не про стиль. Из-за гибкости и вседозволенности питона в нём на ура используются подходы, которые в других языках считаются антипаттернами.
1) наследование в питоне есть, но его почти не используют. Из-за динамической типизации очень легко сломать код при наследовании, сложных иерархий не делают.
2) типизация утиная, интерфейсы и абстрактные методы практически бесполезны. Их тоже почти не используют. Теоретически, можно обложиться модулями типа abc, но это не похоже на pythonic way.
3) инкапсуляции нет, давать название с двойным подчёркиванием — просто общепринятая практика, но эти поля всё равно доступны для изменения.
А вот Java реально хороша для изучения — язык прям создан под ООП и он довольно строгий, чтобы заставлять пользователя красивый с точки зрения ООП код.
В чем именно проблема с наследованием в Питоне?
Из-за динамической типизации? Если так, то это легко лечится
3) инкапсуляции нет, давать название с двойным подчёркиванием — просто общепринятая практика, но эти поля всё равно доступны для изменения.
Ну тогда и в джаве её нет, через рефлексию можно достучаться до приватных членов. Зато в JS внутрь замыкания, насколько я знаю, нельзя залезть из самой программы. Вот там инкапсуляция так инкапсуляция!
Более того, иногда эта "инкапсуляция" через модификаторы доступа иногда мешает работать, когда в какой-то библиотеке в классе есть приватный метод, а тебе хочется изменить его работу. Но увы, автор такого не предусмотрел, и ты вынужден оверрайдить другой метод, переписывая иногда пол-класса, чтобы изменить поведение одной строчки.
На самом деле, инкапсуляция и видимость членов класса — это немного разные вещи, хотя многие java/c/c# программисты считают их синонимами. Настоящий смысл инкапсуляции — в сокрытии деталей реализации, а не в невозможности их каким-то образом посмотреть.
Ну тогда и в джаве её нет, через рефлексию можно достучаться до приватных членов
иногда эта «инкапсуляция» через модификаторы доступа иногда мешает работать, когда в какой-то библиотеке в классе есть приватный метод, а тебе хочется изменить его работу.
Нет, совсем не странно, такие вещи писать должно быть вообще стыдно.
Человек пишет то, что считает правильным.
Может, он знает/понимает меньше чем Вы, а может — больше (из короткого диалога это скорее всего будет непонятно).
Никакие вещи не должно быть стыдно писать.рукалицо
(А может и нет, может, наоборот, он хуже Вас знает. Но это в любом случае должно выясняться аргументами (ну или не выясняться вообще — разошлись при своих мнениях из-за нехватки времени), а не «ай-ай-ай»/«фу-фу-фу» и подобными эмоциональными манипуляциями.)
Не стоит додумывать за меня, что я хочу вернуть, а что нет. Я же писал, что инкапсуляция — это больше про сокрытие деталей, нежели про модификаторы доступа.
Публичные модификаторы доступа в некоторых языках, или список экспортов из модулей в js, haskell и др. — это по-сути просто способ описания публичного интерфейса, апи для работы с неким компонентом системы (ну и один из способов реализовать инкапсуляцию, да). Автор класса/модуля таким образом определяет, что потребители могут использовать извне, а что нет. В идеальном мире авторы предусматривают наперёд все возможные случаи и апи у них идеальный. В нашем мире, увы, это не так.
Недавно надо было изменить алгоритм проверки жизни jwt-токена в одной библиотеки для oidc. Пришлось делать форк библиотеки просто потому что авторы не предусмотрели, что эту проверку нужно будет заменить, а вместо этого решили запрятать её глубоко в недра. Было бы там побольше экспортируемых классов — всё обошлось бы малой кровью и небольшим количеством кода. Самое смешное, что в другой библиотеки для работы с oidc такие же проблемы — метод с проверкой токена приватный, так что нужно переписывать почти весь класс, вместо того чтобы поменять 1 строчку.
Я не особо горжусь, что переписал половину библиотеки, чтобы заменить одну простую вещь. Но и ничего стыдного тут не вижу. Я рад за вас, что вы никогда не сталкивались с такими проблемами. Но мне вот пришлось.
Ну, это все же не совсем одинаковые случаи.
Их можно в определённой степени совмещать, но в основе своей идеологии (без которой это больше синтаксический сахар, а не цельная парадигма) они всё-таки друг другу противоречат. В ООП объекты существуют ради инкапсулированного состояния, которое может изменяться в ответ на внешние раздражители. ФП, ради ссылочной прозрачности, изменяемого состояния старается избегать.
EDIT: ой, OleksiiVoropai уже описал это подробнее ниже.
Не вижу причин ударяться в какую-то «метафизику» на пустом месте.
В ООП программа будет представлять собой комбинацию объектов. Поля объектов хранят его внутреннее состояние. Методы содержат логику поведения объекта — изменения внутреннего состояния и получения доступа к нему. Объекты вызывают методы друг друга динамично меняя свое состояние.
Отличие в том, что программа в ООП стиле имеет внутреннее состояние (переменные и поля объектов), которое меняется по в процессе выполнения программы. Программа в ФП стиле такого состояния не имеет, функции просто трансформируют входные аругменты в выходные. Это фундаментальное отличие этих стилей. Они не сходятся, они существуют и развиваются параллельно. Одну и ту же задачу можно реализовать обоими способами, и структура программы будет разной.
Scala поддерживает оба стиля. Их можно смешивать, часть программы написать в одном стиле, часть в другом, выбирая наиболее удобный для конкретной задачи. Или просто вставить в класс несколько чистых функций, реализовав с помощью ФП какие-то сложные вычисления.
И тогда мы приходим к средующему обобщению: у функции в общем случае могут быть внешние эффекты, просто зона появления этих эффектов может быть ограничена (в более продуманном языке — самой сигнатурой функции, в менее продуманном — увы, лишь документацией функции), в том числе аж до нуля (т.н. «чистая» функция), а может быть не ограничена (в худшем случае, когда функция воздействует наружу программы, например, в ОС, т.о. зоны внешних эффектов таких функций мы уже не можем разграничить).
При столкновении с реальным миром эта красивая теория конечно рушится. Пользовательский ввод/вывод, работа с БД, аппаратной частью связаны с побочными эффектами. В чисто функциональных языках эту проблему пытаются обходить с помощью разных хитрых трюков по типу монад. В некоторых случаях бывает, что усилия по сохранению чистоты функции создают такую сложность, что проще работать с обычными «грязными» функциями.
Но «слить» чистые функции с грязными на теоретическом уровне не получится. Появление побочных эффектов убивает все преимущества чистых функий.
Чистые фукции без побочных эффектов имеют целый ряд преимуществ.Так кто ж спорит-то…
При столкновении с реальным миром эта красивая теория конечно рушится.Частично.
Но «слить» чистые функции с грязными на теоретическом уровне не получится. Появление побочных эффектов убивает все преимущества чистых функий.Мне кажется, всё немного проще. Достаточно позволить функции быть нечистой, но заставить в объявлении функции указывать максимально возможную зону нечистых проявлений. Увы, я знаю не так много языков, которые позволяют делать это качественно.
Функция считается чистой, если вычисление зависит только от значений входных аргументов, и она не меняет состояние среды. Если функция считывает какие-то значения из памяти, оборудования, базы данных, откуда угодно кроме входных аргументов, то есть вероятность, что при следующем вызове с теми же аргументами она вернет другой результат. Абсолютно не важно, что на это повлияет, и из какого слоя появилась «нечистота». Результат начинает зависеть от последовательности вызовов функций.
Соответственно, теряются все преимущества чистоты функций, которые базируются на том, что функция абсолютно независима от всего, кроме своих аргументов.
Если вам для андроида — то да, котлин пойдет лучше, без вопросов. А если нет — то скажем опять же лично для меня у котлина нет особых преимуществ перед старым-престарым груви. И перед скалой. Не вообще нет — а достаточно существенных, чтобы перейти.
нет особых преимуществ перед старым-престарым груви
Статическая типизация. Для неокрепшего (и не только) мозга это серьезная помощь в написании программы.
И перед скалой
Более простой синтаксис, отсутствие мощных (и непростых для понимания) концепций «в шкафу».
Пардон, но у груви тоже статическая. Ну, точнее так: Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities. Ну то есть, если хотите — то все у вас будет.
>Более простой синтаксис,
Ну, возможно, но можем рассмотреть на примере. Я бы сказал, что если не лезть вглубину, то вы не заметите разницы. Ну то есть, она конечно будет, и наличие сложных конценций будет тоже, но на мой взгляд это решаемо.
Это замечательно когда вы программируете для души, вы можете себе выбрать подмножество. Но что будет если этот новичок(в программировании вообще) придет в команду скалистов, и столкнётся с горой придвинутых конструкций, может столкнуться с cats и так далее. Продолжит ли он его любить?
А вот выучил я подмножество scala и как мне двигаться дальше? Постепенно изучать оставшееся подмножетсво на хобби проектах? Так можно, но этот путь будет сильно длинным и где брать наставников на это время. В случае с java наставниками выступали коллеги.
Все таки для первого языка очень крутой порог вхождения, имхо.
Не вижу разницы.
Джава простая, но на ней могут реализовать кучу довольно сложных и неочевидных для новичка штук с аннотациями, кодогенерацией, тестами, библиотечками для dependency injection или вообще энтерпрайз фреймворками. Вам придётся их осваивать.
В скале аналогично, просто язык сложнее и гибче, и некоторые внешние библиотеки за счёт этого получаются проще. Та же cats хороша тем, что она общая для разных кодовых баз, и при переходе на другую работу вам не придётся осваивать всё заново.
Ну смотрите, я это в общем не предлагал. Я это пробовал, но не учить по факту как первому языку — у меня просто нет таких подопытных кроликов. Я учил аналитиков, они не профессиональные программисты. Но языки какие-то знают. И в общем, все проходит достаточно спокойно, при том что у нас не было в общем-то систематических занятий.
Т.е. то что порог крутой — ну это с какой стороны зайти, на уровне скажем переменных, констант, вещей типа if, функций, коллекций и т.п. некоторые вещи наоборот проще. Оне не обязательно такие же — после java скажем регулярки у вас поначалу могут вызвать недопонимание.
Ну так, в качестве примера — попробуйте в java сделать стрим по массиву. И попробуйте в скале. У массива в java нет методов stream(), ну и дальше нет .map, .filter и т.п. — это все можно, но с некими приседаниями. Несложными. Массив — это такая кривая коллекция. Специальная. Которая усложняет жизнь.
А теперь попробуйте в скале найти коллекцию, чтобы у нее не было .map? В итоге все вещи типа перебора коллекций делаются или так же, или проще. А это в общем-то вещи совсем базовые.
И еще я пожалуй присоединюсь к комменту ниже. У вас и в java такие же неочевидные моменты будут. И путь может быть длинным. Просто конечная точка будет разная.
— новых ниш, где она была бы явно востребована пока нет;
— в старых нишах хорошо себя чувствуют другие ЯП, которые оптимальны для них по набору своих сильных и слабых сторон;
— весь интерес к ней — это просто желание разработчиков к чему-то новому, но так как все ЯП развиваются, то в них тоже можно удовлетворять свое любопытство;
— мало вакансий даже на прежних версиях Скалы.
Единственное, за счет чего она сможет взять свой небольшой процент от общего пирога — это стать лучшим функциональным ЯП из всех существующих.
А так даже приятно, что люди с такими серьезными лицами фаном занимаются…
Вангую, что Scala 3 тоже не взлетит:
Даже если и не взлетит, возможно, заложенные идеи перетекут в другие языки. Если бы в свое время не появились Groovy, Scala, Clojure, кто знает сколько бы еще ждали в Java лямбд и Stream API. А сейчас в Java уже и var и аналоги case классов и чуть ли не паттернматчинг.
Статья очень интересная.
Спасибо.
Честно прочитал статью, но так и не понял, почему стоит браться за Scala. Вижу много умных слов, но не понимаю, что за ними стоит. А те, что понимаю, не кажутся мне важными. Долгая компиляция? Ну, вряд ли ближайшие пару лет меня это как-то коснётся — хеллоуворлд скомпилируется в приемлемое время. То же самое касается и производительности.
Быстрые изменения? Значит, то, что я сейчас с таким усердием учу, завтра будет неактуально. Типизация и ООП? Да-да, я помню, TForm1.Button1Click…
А все остальные разделы помечены как «не для новичков», и я там ожидаемо мало что понял.
Так чем же хорош Scala именно как первый язык?
Если коротко, то Scala — это возможность научиться функциональному программированию на безопасном языке, у которого есть большое будущее
println(new Stack().push("hello").push(new Object()).push(7))
Это же не Case классы.
new
.на самом деле в третьей скале можно уже не писать new
это то, какой она должна была быть с самого начала
Ну, с самого начала обычно сложно предсказать, что на самом деле важно. Вот я скажем сижу сейчас на 2.11.8, примерно, потому что у меня спарк с ней собран, и совместимости нет. При этом я легко могу себе вообразить проект, для которого это вообще не проблема — пересобрали и в путь. Но пересобрать спарк, размер кодовой базы которого на порядок-другой больше, чем у типового своего проекта — это такое развлечение, которым вряд ли кто-то будет заниматься.
Функциональное программирование — это многообещающая тенденция. Скале она присуща в не меньшей степени чем ООП, и они взаимно обогащают друг друга благодаря этому языку. Рискну предположить, что скала — первый язык промышленного уровня с такими свойствами.
Разумеется не первый. Задолго до Scala вполне существовали подобные языки. Из тех что я знаю, это Common Lisp (CLOS) и Ocaml (диалект ML с элементами ООП). Если верить википедии, то эти языки повлияли на Scala. Также многие реализации языка Scheme имеют поддержку ООП, но это не определено в стандарте языка (например GNU Guile).
Мало какой язык может похвастать математически точным исчислением, лежащем в его основе.
Опять же из списка языков, которые повлияли на Scala. Standard ML и Scheme. Оба имеют формальную спецификацию. Но да, таких языков действительно мало.
Язык-то далеко не первый, это правда, но мне кажется (и, видимо, автору тоже), что он получил заметно больше распространения в «широком IT», чем упомянутые вами языки. Так ли это на самом деле — не знаю, но впечатление такое есть. Фраза «рискну предположить» в этом смысле вполне честный disclaimer.
"вспомним регулярные выражения, паттерн-матчинг и парсер-комбинаторы" - это примеры плохого?
Вот как выражения для паттерн-мачинга можно назвать трудночитаемыми я ни когда понять не мог. Те же самые данные, только с плейсхолдерами. Вызовы конструктора же мы сложными для чтения не считаем.
Регулярные выражения и парсер-комбинаторы - с чем сравнивать. С реализацией руками или через DSL типа yacc? Это сложность предметной области. Если на понимать работу грамматик то читать будет сложно независимо от того, в каком синтаксисе они записаны.
вот например такой простой паттерн-матчинг очень плохо читается (и это не предел)case Seq(xs @ _*) =>
Во первых нормально читается.
Во вторых альтернативные способы читаются хуже.
В третьих совсем не обязательно питать слишком сложные паттерны.
насчет того, что похоже не существует альтернатив чтобы писать регулярки и парсер-комбинаторы понятней, я согласен.
Моя мысль была в том, что новичку в скале будет очень трудно разобраться. Понимаю, что тебе это читается вполне нормально. Но меня несколько раз спрашивали (опытные программисты, которые недавно начали писать на скале), что это вообще за случайные наборы символов, и есть ли где посмотреть полную документацию на синтаксис паттерн-матчинга. Документации я тогда никакой не смог найти.
Упростил текст, убрал технические детали под кат, чтобы не пугать ими тех, кому это не нужно. Написал смешное обновление от 2024 года. Обновил картинку с зарплатами
Примеры, ссылки и остальные картинки оставил старые.
Если кто-то забредет и найдет в статье что-то еще, что просит улучшения, или принесет полезную подходящую ссылку / картинку / пример, я буду рад
Scala как первый язык