Как стать автором
Обновить

Комментарии 35

Очень похоже на руби
Scala чем-то похожа на все языки, которые я знаю. Ну почти на все.
И из всех она всех она взяла лучшее.
Согласен. Всего несколько дней на ней пишу, но ощущения лишь положительные.
Восхитительный язык. Есть что добавить, есть ограничения, унаследованные от Java, но в общем просто прекрасная штука. С первой строчки и по сей день, чем больше я пишу на Scala, тем больше удивляюсь как можно было писать на чём-то другом (я, правда, ещё не пробовал Erlang и Haskell).
Haskell тоже вполне )
Я работую со Скалой уже не первый год, и, вы знаете, я бы с вами не согласился.

Не все фишки, которые являлись «лучшими» в исходных языках, продолжают являться лучшими также и в Скале. Более того, эти вещи не всегда хорошо согласуются друг с другом. Очень много я бы из Скалы убрал лишнего.

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

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

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

Разве что xml в языке несколько странное впечатление производит как нечто пришпиленое сбоку чтоб было.

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

Или вот другой пример. Scala предлагает расширять существующие экземпляры классов(не сами классы, а их инстансы), путем неявного приведения к экземплярам подклассом. То есть, функция неявного приведения оборачивает один объект другим. Такая, на мой взгляд, очень громоздкая конструкция. И она используется в стандартной библиотеке(и многих известных сторонних) очень интенсивно, что делает семантику любой программы очень запутанной, тяжелой для компиляции. Да и рантайм ухудшается(из-за создания лишнего инстанса). Куда проще было бы, скажем, просто разрешить инъектировать новые методы в существующие классы. Инкапсуляция при этом не нарушается.
Мне довелось видеть код реального проекта, написанного на Scala. Очень много неочевидных вещей. Высокая вероятность выстрелить себе в ногу. Особенно хорошо порой проседает производительность. Так как Scala все равно компилируется в байт код, который потом выполняется JVM, то в этом плане когда пишешь на Java многие вещи намного прозрачнее, чем на Scala.

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

Больше всего граблей на мой взгляд на стыке Java и Scala. Когда ты используешь либы написанные на Java и пытаешься их использовать в Scala коде. С одной стороны это удобно. С другой стороны это трудноуловимые баги.
Согласен, в ногу себе выстрелить просто.
Мне кажется, что многие проблемы от неопытности и восторженности. Разработчикам хочется применить все средства языка и они пихают их где надо и не надо. Почему-то C# разработчики не часто пишут extension методы, хотя на них можно реализовать такие мощные вещи, как LINQ.

Так же многие не считаются с гайдлайнами и используют не оптимальные методы языка, как, например, использование implicit conversions вместо type class, хотя все специалисты вроде разработчиков typesafe stack рекомендуют использовать type class.

В стандартной библиотеке не так много implicit conversions, гораздо больше implicit parameters, но при использовании о них чаще всего можно забыть.
О некоторых фичах на мой взгляд нужно знать, что они есть, но использовать их только тогда, когда это нужно, а не везде и всюду. Так же как Reflection в Java. Я стараюсь обходиться без него столько, сколько можно. По правде мне реально никогда не нужно было его использовать так, что прямо «нужно и по другому никак». Когда натыкаюсь на такой Java код, который использует Reflection и вижу, что без него можно было вполне обойтись, что все было необоснованно усложнено, меня это злит.
Побочные эффекты легко контролируемы. При желании можно писать только чистые функции везде, но зачем? Есть места где иммутабельность не лучший выход.
Иммутабельность позволяет проводить многие оптимизации. А уж многопоточность без иммутабельности вообще превращается в пытку.
Вообще иммутабельность везде контролируется: val vs var – ваш выбор.

В каком смысле параметры функции иммутабельны? В том же, что м в java? Так и ответ тот же, что и в java, зато если требуется вернуть 2-3 значения можно использовать TupleN.

Или в том смысле, что внутри функции нельзя использовать параметр как присваиваемую переменную? Так это во многих ЯП, в том числе и с ООП парадигмой. Вообще присваивание значения параметру функции – это повторное использование переменной, что есть зло.

Про неявное преобразование: приводится не к подклассу, а к любому другому классу. Экземпляр на самом деле не создается и в рантайме просто вызывается статический метод (где это возможно).
Затасканный пример: String приводится к Traversable, при этом String наследовать нельзя.
Это полный аналог механизма extension методов в C# и Kotlin с тем лишь полезным отличием, что позволяет не только добавлять методы, но и приводить к интерфейсам. Из-за того, что extension методы не позволяют приводить к интерфейсу в Kotlin, например, введена структурная типизация для таких конструкций языка как for.
И тут тоже инкапсуляция не нарушается.
> Это полный аналог механизма extension методов
> Это полный аналог механизма extension методов

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

> А уж многопоточность без иммутабельности вообще превращается в пытку.

Состояние актера, например, мутабельно.

> Вообще присваивание значения параметру функции – это повторное использование переменной, что есть зло.

ок, я понимаю ваш подход. Но зачем тогда обычные переменные они разрешили объявлять как var, а параметры функции нет? Это пример неконсистентности языка.
Плюсов 2:
1 более удобное повторное использование кода:
Чтобы добавить все методы Traversable в String при помощи extension методов надо явно прописать все эти методы. При помощи же implicit converrsion достаточно StringOps унаследовать от Traversable и реализовать 1 метод: foreach. Это гораздо менее громоздко. А вот добавление по 1 методу как раз зачастую зло, ибо такой мощный механизм как расширение класса надо использовать очень редко, но с очень большими последствиями (как LINQ).
2 приведение к интерфейсу, что позволяет не использовать структурную типизацию как в Kotlin (повторяюсь).

Состояние актора мутабельно, но все изменения очевидно в 1 потоке а, следовательно, удобны и безопасны. Проблемы с мутабельностью при передаче между потоками. Посылка актору мутабельного сообщения – самоубийство. Если 2 потока владеют ссылкой на общий изменяемый объект, то синхронизация становится нетривиальной задачей: habrahabr.ru/post/143074/

Я не это имел в виду под повторным использованием. Повторное использование – это использование переменной в 2 разных смыслах. То есть если Вы заводите переменную count и в начале метода используете как счетчик одних объектов а в конце – как счетчик других, то это и есть повторное использование.
Параметры функции – это то, что ей передали.
В частности невозможность изменить значение параметра функции дает возможность полагаться при изменениях кода в конце метода на то, что мы работаем именно с параметром, а не с каким-то значением присвоенным в процессе без просмотра всего кода.
И нельзя забывать про оптимизации, доступные только для неизменяемых объектов.
> Плюсов 2:

Никто же не мешает в String инъективровать метод toTraversable(). Код тогда становится гораздо более понятным, явным.

> Состояние актора мутабельно, но все изменения очевидно в 1 потоке

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

> То есть если Вы заводите переменную count и в начале метода используете как счетчик одних объектов а в конце – как счетчик других, то это и есть повторное использование.

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

Справделивости раду тут стоит отметить, что Scala позволяет во вложенном контексте переобъявлять переменную с тем же именем. Вот это, на мой взгляд, порождает куда больше проблем.
Я, кажется, понял в чем мы не поняли друг друга: «А уж многопоточность без иммутабельности вообще превращается в пытку.» — это об отсутствии полноценной поддержки immutable, позволяющей удобно обмениваться данными между потоками, а не о наличии где-либо mutable state. Например отсутствие неизменяемых интерфейсов коллекций в Java.
По моему опыту присваивание другого значения параметру используется крайне редко. Точнее это было запрещено конвенциями, если даже ЯП позволял, везде, где я писал код. Единственное исключение — T-SQL хранимки, где стандартной практикой является переопределение Null, но это не наш случай, ибо null в скале не используется.

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

А вот перекрытие переменной из другой области видимости возможно в очень многих ЯП. Не могу сказать, что это абсолютное зло, но быть осторожным надо.
>Никто же не мешает в String инъективровать метод toTraversable(). Код тогда становится гораздо более понятным, явным.

А зачем тогда вообще расширять класс? С тем же успехом можно писать toTraversable(string).

Если, например, требуется отрезать от строки префикс в верхнем регистре, то гораздо проще и нагляднее написать
«ABCabc» dropWhile { _.isUpper }
чем
«ABCabc».toTraversable.dropWhile{ _.isUpper }.toString

В этом прелесть неявного преобразования: мы не просто добавляем 1 метод, мы получаем рассматривать строку как коллекцию символов, которой она, в принципе, и является.
*получаем возможность
> А зачем тогда вообще расширять класс?

Чтобы меньше смешивать в одном выражении инфиксную и префисную нотацию. Единооразный стиль легче воспринимать.

> В этом прелесть неявного преобразования

Мы сейчас обсуждаем не плюсы Скалы(которые безусловно в ней есть), а минусы. Так вот, неявное приведение имеет очевидный минус: его сложнее воспринимать, когда читаешь. Ну, если взять не такой тривиальный пример, как вы привели, а какой-нибудь гораздо более громоздкий.

Вообще, чем больше в коде неявных вещей, тем больше он получается «зашифрованным»(encrypted). И это плохо не только для стороннего читающего. Это значительно усложняет саму задачу написания компилятора. А про IDE и говорить нечего. Самый лучший плагин под IntelliJ Idea, и тот жутко тормозит, периодически падает на таких вот навороченных приведениях.
Сложно обсуждать без примеров, может быть вы приведете свой достаточно нетривиальный?

Для обсуждения сложности для расшифровки и сложности для разбора в IDE лучше брать не неявные преобразования, которые полностью эквивалентны extension методам, обрабатываются точно так же и создают столько же вопросов, а неявные параметры.

Именно из-за неявных параметров IDE с большим трудом обрабатывают scala. Именно ради сокрытия неявных параметров в документации придуман синтаксический сахар для записи type class и use case в scala doc.
Это неявные параметры, такие как CanBuildFrom вызывают наибольшее количество вопросов.

Но эти сложности — это та цена, которую приходится платить за удобства. За гибкость, которую они создают.
> Для обсуждения сложности для расшифровки и сложности для разбора в IDE лучше брать не неявные преобразования

Я говорил о «неявных вещах» вообще. Не только о неявных преобразованиях, конечно.

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

Я считаю, что в данном случае цена слишком высока. Это субъективно мое мнение, конечно. Не все же любят большие IDE. :)

> Сложно обсуждать без примеров, может быть вы приведете свой достаточно нетривиальный?

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

P.S. Не подумайте, пожалуйста, что я за то, чтобы не было вообще никаких неявных вещей в языке. Нет, я считаю, что все это нужно и полезно, но разработчики Scala уж слишком перегнули палку. Тот же Kotlin в этом плане спроектирован гораздо лучше. Хотя он тоже не лишен недостатков.
На скале очень просто и удобно пишутся простые не критичные по производительности парсеры (JavaTokenParsers).

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

А вот про Kotlin я готов спорить долго. На мой взгляд он крайне непоследователен и, в отличии от scala, предлагает не стройную концепцию, а разрозненный набор плюшек:

Pattern matching? Долго и сложно! Мы переименуем switch и расширим его проверкой типа.
Выкинем все со словом «неявный». Даже если замена полностью эквивалентна. Не получается привести к интерфейсу, а итерировать по строке хочется? Введем структурную типизацию.
Синтаксис для структурной типизации? А давайте введем ее только для встроенных элементов языка!
Обход type erasure а неявных параметров нет? Добавим синтаксическую конструкцию!
Каррирование? Нет, не слышали. А в отдельных фигурных скобках можно указывать последний параметр, если он лямбда.
Перегрузка операторов! Почему операторы не добавить в список разрешенных имен функций? Ну так они же операторы!
Не нравится подчеркивание в лямбдах? А давайте первую попавшуюся переменную интерпретировать как аргумент!
Изменение типа переменных в зависимости от проведенных проверок! Не очевидно без IDE? Так пользуйтесь IDE — там такие переменные красиво подсвечиваются зеленым!

И так далее…

Не поймите меня неправильно, я считаю, что команда JetBrains делает очень хороший язык. Андрей Бреслав — умнейший человек. Они действительно создают нечто простое в использовании и крайне функциональное.
Но kotlin, в отличии от scala, я не могу мысленно свести в единую стройную концепцию.
Если в scala я вижу набор универсальных принципов на которых вырастает функционал самым логичным и естественным образом, как доказательства теорем из аксиом. Мне не нужно что-либо заучивать: понял принципы и все остальное кажется самоочевидным и единственно верным. И как в мат. аппарате те же принципы, что использовались для построения основы я сам могу использовать для ее расширения. Это то, что я бы назвал академическим подходом.

В Kotlin же нет лаконичного набора идей, а есть довольно большой и все еще раздувающийся набор не универсальных решений. Его мне надо заучивать. В итоге получается тот же C#, только на jvm. А зачем еще 1 C#?
>Я согласен, что оба механимзма взаимозаменяют друг друга, но вариант Скалы несколько больее громоздкий. Я не вижу больших плюсов в таком усложнении(не считая некоторых исключений).

Как оказалось, разработчики scala с Вами согласны. Синтаксис упростили, теперь даже менее многословно, чем в C#, но все еще приводится к интерфейсы:
docs.scala-lang.org/sips/pending/implicit-classes.html
> Подождите-ка минуту! Но мне говорили, что перегрузка операторов — это зло!
И по-моему любая библиотека Scala живой тому пример.

> Допустим, Вы писали класс для комплексных чисел, и Вы хотите, чтобы комплексные числа можно было складывать.
На практике же символы арифметических операций в Scala используются для чего угодно, кроме арифметических операций. Например если написано !a — это не логическое отрицание, не инверсия, не обратное преобразование наконец, а оказывается, посылка сообщения. Таким же сакральным смыслом обладают операции +:, ++, /:, \:, +++, ~+, ::=, %, %% (взято из разных библиотек). Не говоря уж о том, что это непроизносимо.

Так что одной перегрузкой операторов просто испортили язык.
Про "!a" Вы несколько перегибаете. "!a" не может быть отправкой сообщения, так как нет адресата.
А «a! b» не может быть отрицанием, так как отрицание — унарная операция.

+: — это прибавление элемента ко множеству. Зачем тут ":" поймет любой, кто знает синтаксис scala. И не менее очевидно, что элемент добавляется в начало.

++ — объединение множеств. «Множественное добавление».

::= — в отношении этого оператора действует тот же принцип, что и для всех аналогичных: для immutable коллекций «a ::= b» — сокращенная запись для «a = a.::(b)» оно же «a = b :: a». Для mutable — аналог с изменением исходной коллекции.
Сам же оператор "::" крайне часто используется в scala и тесно связан с контейнером linked list (класс List).

"/:" — foldLeft. Рекомендуется его не использовать, так как foldLeft нагляднее.

Вот остальное уже более специфично и зависит от библиотеки. Например в scalaz есть огромное количество экспериментальных операторов от которых голова кругом, но они реализуют определенные концепции ФП, суть которых в имени функции не выразить, так что более развернутое имя понятнее не сделает.
В scalaz есть даже unicode-операторы, которые не всегда просто ввести :)
Я понимаю, что Вы знаете Scala. Разговор не про это.

>… А «a! b» не может быть отрицанием, так как отрицание — унарная операция.
Зачем использовать ambigious символ, если можно a.send(b)?

> +: — это прибавление элемента ко множеству.
Да, это то, что во всех христианских языках называется add().

> ++ — объединение множеств. «Множественное добавление»
addAll() в Java. В математике никто никогда не использовал символ ++ для объединения множеств. Раньше использовали +, а для пересечения * или &.

> "/:" — foldLeft. Рекомендуется его не использовать, так как foldLeft нагляднее.
А чем слово foldLeft не угодило? Ну или, lfold? Посмотрите чтоли коллекции в Groovy. В разы проще и изящнее.

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

Следует добавть: абсолютно бесполезных концепций, которые никем и никогда и нигде не будут использованы.
>Зачем использовать ambigious символ, если можно a.send(b)?

Учитывая, что отправка сообщений — одно из основных действий в используемой модели многопоточности, логичным выглядит сократить его запись до минимума. Самое базовое действие имеет право на отдельный символ.

>Да, это то, что во всех христианских языках называется add().

Не add, а addFirst. Только зачем 2 слова, когда все понятно и с 2 буквами?

>addAll() в Java

Скажите честно, а Вы объединяете строки как str1.concatenate(str2) или через ужасно непонятный оператор?
Ответ прост: для базового часто используемого действия вполне можно ввести сокращенное обозначение.

>А чем слово foldLeft не угодило? Ну или, lfold?
Вот lfold — это воистину ужасно. Это выглядит как 1 слово, но им не является. Я с первого раза прочитать не могу. Но согласен, что foldLeft лучше, чем /:. Попробовали, идея оказалась неудачной, больше такие идеи не внедряют без прохождения инкубатора.

>Следует добавть: абсолютно бесполезных концепций, которые никем и никогда и нигде не будут использованы.
Разработчики typesafe stack некоторые операторы используют и другим советуют. Вот тут с 26 минуты:
www.youtube.com/watch?v=YZxL0alO1yc
Другие операторы вымрут — на то и нужен инкубатор, чтоб проверять идеи на жизнеспособность.
Кстати да, "*" для пересечения — это тот еще ужас, учитывая наличие такой операции, как декартово произведение.
А "+" для объединения множеств не используется потому, что со времен iava "+" на всем подряд, кроме всевозможных чисел, приводит к toString и конкатенации строк. Оно так было до scala, пусть так и остается.

И совсем забыл:
>Я понимаю, что Вы знаете Scala. Разговор не про это.
А кто должен писать и читать код на scala? Тот, кто не знает scala?
Я — новичок в scala и у меня все эти ужасные 3 оператора на коллекциях (":+", "++", "/:") сложностей восприятия не вызывают, а кроме них и отправки сообщений операторов не распространенных в других языках в стандартной библиотеке фактически и нет.
Всё верно, с не-буквенными методами надо осторожней, можно жуткое мясо получить (пример: www.flotsam.nl/dispatch-periodic-table.html).

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

Мне за 3 года работы со Scala пока ни разу не понадобилось писать свои не-буквенные методы. Чужими пользовался постоянно, без проблем, многие из них были весьма удачными решениями.
А как в scala перегрузить операторы /* и //?
Воспользоваться экранированием:
scala> object test{ def `//`(i: Int) = i }
defined module test

scala> test `//` 1
res14: Int = 1
Или записью, доступной для совместимости с Java:
scala> test $div$div 1
res19: Int = 1
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории