Pull to refresh

Comments 88

Для тех, кого как меня смутило незнание автора про auto в с++ и подобные вещи, оригинальная статья была написана в 2011 году.
Я не касался функционального программирования на Scala, для этого нужно сравнивать Scala с другими функциональными языками, он я не FP'шник (FP guy)

Есть всё же несколько вещей, которые мне не понравились, за чуть более чем шести летний опыт работы со Scala.


Oh wow.

Да и вообще, это уже сколько обсасывалось — пока клепаем милые case class'ы и просто пишем col.map { _.toString } — сложного ничего.
Как лезем в дебри самого .map с его implicit CanBuildFrom — начинается хардкор.

Вывод: Для конечного формошлепа скалка проста. Для либописателя уже все не так радужно и питонисто.

btw, чтоб не было ВНЕЗАПНЫХ имплиситов, на которые жалуется автор оригинала, нужно или нормальные либы юзать, где нормальные ребята все пишут через implicit classы внутри объектов, либо не импортить весь com.shiiiet.DAT_IMPLICIT_GOD_OBJECT._
Ну лично я использую Scala в backend логике, и пока что никаких проблем не замечено.
Так и неоткуда им взяться. Если смутило слово «формошлеп», то я таким образом обозначил просто основную массу кодеров, которые просто используют готовые стеки инструментов, не заморачиваясь с low level деталями.

Я сам успешно написал и пишу сервисы на Scala + Play + Akka
Извините, я совершенно не знаком с Scala, в итоге, пробежав по вашей статье, никаких преимуществ не заметил. Вижу, что перевод, оригинал так же не содержит ничего внятного.

Вывод типов
Scala:
val aGuy = "John"

C++:
auto theGuy = "Ivan";

Упрощенное определение классов
Scala:
class Person (val firstName:String, val lastName:String)
C++:
struct Person{ std::string firstName; std::string lastName; }; // мне лично такой стиль форматирования не нравится
Преимуществ не видно.

Объединение методов и операторов
Извините, но я не наблюдаю описанных проблем в С++
Scala:
ListBuffer(1,2,3) append 4
или
ListBuffer(1,2,3) += 4
C++ (Qt5):
(QList<uint64_t>{1, 2, 3}) << 4;
(QList<uint64_t>{1, 2, 3}).append(5);
(QList<uint64_t>{1, 2, 3})+=6;
Вообще говоря, += выглядит сомнительно, в контексте какого-нибудь алгоритма иногда можно понять по-другому.

После примера
"Mary" with "a purse" and "red shoes" should look "nice"
, стало доходить, что может быть на самом деле, речь об отсутствии точек и скобочек. Выглядит круто, но только если очень хорошая подсветка синтаксиса, иначе тяжело разобрать что есть что. Тем не менее чего-нибудь такого или типа linq в C++ не хватает.
С++
Person("Marry").with("purse").with("shoes").should(Look("nice"));
<troll-mode>Что скажут любители функциональных языков?</troll-mode>

Типы
В C++ вопрос контрвариации и ковариантности действительно не прост, возможно, по этому его как-то неявно избегают, и большой проблемы не ощущается.

Только конструкторы?
Не совсем понятно о чем речь, вроде ничего сложного тут нет.
case Person(first,last) => println (" name is " + first + " " + last)
template<typename OS>
OS& operator<<(OS& os, const Person& p) {
  return os << p.first << p.last;
}
Вообще эта фича «скалы» выглядит страшновато.

Думаю, что программисты C# еще и не такое покажут.
Вывод типов
Статья была написана до появления «auto». Тут есть ещё одна интересная особенность. Ключевым словом «val» отмечается неизменяемая переменная, а «var» — изменяемая.

Упрощенное определение классов
В Scala формируются не поля, а полноценные свойства с get и set методами.
Спасибо за комментарий, но ни о чём таком ни в статье, ни в переводе не написано.
— Насколько я помню, в конце 2011 года auto уже было.
— В C++ есть ключевое слово const, пожалуйста, пусть будет
struct Person{ const std::string firstName; const std::string lastName; };
— Чем помогают геттеры и сеттеры, сгенерированные компилиятором?
— Чем помогают геттеры и сеттеры, сгенерированные компилиятором?
Anyway, всегда можно нагородить макрос, который и поле объявит, и соответствующие геттеры/сеттеры :)
Геттеры и сеттеры можно переопределить.
Не вижу никакого смысла делать поля структур/классов в C++ константными. Я считаю, это даже вредно.

Сможете ли вы отсортировать массив/вектор таких структур? Нет.
Передать владение такой структурой (std::move + move constructor)? Нет.

Лучше позвольте пользователю класса самому при необходимости объявить переменную типа Person const, эффект будет тот же.

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

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


Потому что в Java/C# экземпляры классов всегда передаются по ссылке (в терминологии C++ это указатель). Отсортировать массив указателей на структуры с неизменяемыми полями можно (поскольку свопаться будут указатели, а не данные). Свопать же содержимое объектов с read-only полями, по понятным причинам, нельзя.

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

вместо физического копирования он копирует значения структур. Но это же бред какой-то…

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

Полностью согласен. Однако
Потому что в Java/C# экземпляры классов всегда передаются по ссылке (в терминологии C++ это указатель).

это кто вам такое сказал? Вот создал структуру с неизменяемыми полями:
    struct MyStruct
    {
        public readonly int X;
        public readonly int Y;
        public MyStruct(int x, int y)
        {
            X = x;
            Y = y;
        }
    }

(кривовато, публичные поля и т.п., но для примера сойдет, что это «настоящие неизменяемые поля», а не просто свойства с приватными сеттерами).

В массиве будут храниться сами значения, а не ссылки (за это отвечает ключевое слово struct). И тем не менее, следующий код отработает безо всяких ошибок:
            MyStruct[] structs =
            {
                new MyStruct(10, 20),
                new MyStruct(50, 35),
                new MyStruct(35, 100),
            };

            Array.Sort(structs, (a, b) => a.X.CompareTo(b.X));
            Array.ForEach(structs, a => Console.WriteLine("{0} {1}", a.X, a.Y));

возможно, я просто чего-то не понимаю, проясните, пожалуйста.
>экземпляры классов
это кто вам такое сказал? Вот создал структуру с неизменяемыми полями:

И тем не менее, следующий код отработает безо всяких ошибок:

Честно говоря, это я не понимаю, почему вот такой код компилируется
var x = new MyStruct(1, 0);
x = new MyStruct(2, 3);

Т.е. поля у нас неизменямые, но оператор присваивания это не останавливает. Налицо разная семантика «константности».
В плюсах const означает (грубо) «участок памяти, занятый объектом, не должен модифицироваться».
В шарпе, видимо, readonly означает «нельзя изменить это поле, но можно переприсвоить весь объект целиком, подифицировав память».
Честно говоря, это я не понимаю, почему вот такой код компилируется
var x = new MyStruct(1, 0);
x = new MyStruct(2, 3);


Эээ? переменная X не привязана жестко к области памяти. Во второй строке просто она будет привязана к другой области памяти. И, если у меня ещё не случилось помутнение рассудка, в C++ та же фигня.
X не привязана жестко к области памяти

Как это не привязана? MyStruct — это структура, а не класс, и должна жить на стеке. В C# одинаковый синтаксис для инициализации структур на стеке и классов на куче — оператор new.
если у меня ещё не случилось помутнение рассудка, в C++ та же фигня

Обратитесь за помощью к компилятору:
struct point
{
    const int x;
    const int y;
};

int main()
{
    point x = {1, 0};
    point y = {2, 3};
    x = y;
    return 0;
}

error: cannot define the implicit copy assignment operator for 'point', because non-static const member 'x' can't use copy assignment operator

Если в C++ написать new, то у x будет тип не MyStruct, а MyStruct *.
Да, вы правы, посыпаю голову пеплом.
В плюсах const означает (грубо) «участок памяти, занятый объектом, не должен модифицироваться».
В шарпе, видимо, readonly означает «нельзя изменить это поле, но можно переприсвоить весь объект целиком, подифицировав память».

Так и есть. Не вижу в этом ничего плохого. В случае необходимости сам объект можно сделать readonly. А что касается const-параметров у функций, то тут Эрик неплохо все расписал.
Думаю, теперь всё встало на свои места:

> почему нельзя отсортировать массив структур с readonly-полями?
Потому что у C++ и у C# разная семантика константности, const != readonly.

C++ const более строгий, поскольку константный объект может лежать в read-only сегменте памяти, и попытка записать туда что-то может привести к SIGSEGV. Кстати, const в С++ как раз и появился в контексте read-only памяти в железе и операционных системах.
Val/var — и поди потом отыщи багу, когда глаз замылится…

А как-нибудь можно управлять, чтобы геттеры/сеттеры для каких-то полей не создавались?
никаких преимуществ не заметил

Конечно не заметили. Вы же статью прочитали «по диагонали». И тут же не к месту набросились со своим С++.

А между прочим, статья то не про преимущество Scala над C++, а про сложность самой Scala.
Про то, что Scala все считают через чур сложной. А на самом деле, не сложнее чем другие языки с ООП.
А если сравнивать преимущества, то корректнее сравнивать все таки с Java, а не С++. Ибо Scala, язык для JVM.
Сравнение с C++ имеет мало смысла. И применимо только для узкого круга проектов.

P.S. И да, тут недавно была статья про С++, там много было примеров в каментах как же «удобно» писать на С++.
Вот там такой камент был бы больше в тему.
Конечно не заметили. Вы же статью прочитали «по диагонали». И тут же не к месту набросились со своим С++.
Во-первых, тон статьи говорит о том, что посмотрите как это легко в Scala и как сложно в C++ или Java, там уж точно придется написать две стриницы кода. Я же, сомневаюсь в такой категоричности. Во-вторых, это не я придумал сравнивать с C++, а автор статьи.
Как же на хабре люди любят занудствовать и доматываться ко всему)
Смысл статьи в первую очередь говорит что Scala не так сложна как ее представляют.
И уже просто в качестве примера приводятся примеры с Java и С++. В ключе «На Scala не сложнее».
А тон статьи, это то, за что зануды цепляются чтобы по холиварить на пустом месте)
Последний пример не у тему немного — в Скале это блок кода, а не функция. И такой «деконструкции» в сях действительно не хватает…
Интересный блок кода, заодно имплементирующий trait PartialFunction…
Простите, при чем тут PartialFunction?! Это же просто блок case и немного паттерн матчинга…
Я про то, что в другом контексте этот же «блок» — PartialFunction[AnyRef, Unit]. В случае использования match синтаксис тот же, аналогично применяются экстракторы (через unapply или доступ к полям) и т. п.

Только в случае использования match компилятор ещё сгенерирует case, кидающий MatchError, если не покрыт какой-то из путей.

Т. е. тот же pattern-matching/extractors можно использовать для создания анонимных функций. Многие библиотечные функции явно или неявно принимают PartialFunction[T, R].
Под «деконструированием» тут описывается простейший вариант очень мощной техники pattern matching. Это чем-то похоже на регулярные выражения, но не для строк, а для произвольных данных. Впервые это придумали в языках Prolog (в несколько расширеном виде) и ML.
Техника на столько мощная, что по легенде в языке Erlang даже не хотели вводить оператор if, но уступили только под дпалением маркетинга :-).
Сложность разная бывает:
1) Сложность написания. То есть количество времени для получения конкретного результата. Самый незначимый вид сложности, хотя дающий максимальный эмоциональный эффект.
2) Сложность чтения. То есть сколько времени нужно потратить чтобы восстановить мысль автора по коду. Так как код читается гораздо больше чем пишется, то это самый значимый вид сложности.
3) Сложность понимания как оно работает. То есть сколько времени нужно потратить до того чтобы понять как код работает. Эта сложность вызывает больше всего холиваров.

Так вот язык C и Java имеют среднюю сложность написания и понимания и низкую сложность чтения.
А большинство ФЯ снижают сложность написания и повышают сложность понимания и чтения.
IMHO, дело привычки. После Haskell (при опыте использования C++) мне читать код на Scala проще, чем на Java.
Мне кажется автор путает сложность и громоздкость.

Ява проста как валенок и но в следствие этого код становиться довольно громоздким и требующим IDE для комфортной работы.

Scala же позволяет писать красивый и лаконичный код. Но требует понимая всего что скрывается за этой лакончиностью.
Понимания того как работает «магия». А там уже возникают сложности. Особенно когда у разных разработчиков магия своя ( привет implicits).

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

Разрешать перегрузку операторов? Префексная инфиксная формы. Ну :: ::: очень очевидное поведение. А еще при этом есть .append. Я до сих пор вспоминаю как на курсере людей ставили в тупик такие способы работы со списками. Опять таки implicits позволяет перегрузить операторы. Т.е. они становяться еще и контекстно зависимыми. Это сильно «облегчает» понимание.

Скала черезвычайно мощный и выразительный язык. Но называть ее простой?
Я вот вообще считаю, что с каждым языком нужно работать аккуратно. Использовать его сильные стороны во благо и избегать его слабых сторон. Вот и получается, что при таком подходе, Scala становится мощным и удобным языком. А при желании напихать в проект всего-всего да побольше, конечно всё закончится печально и «Scala слишком сложна!».
SBT. Это всегда сложно. Серьезно, если вам нужно сделать что-то выходящее за рамки типичной работы с SBT, то вы попали. Существует столько разных не очевидных DSL, что Google будет сходить с ума пытаясь найти для вас как нужно сделать то, что вам нужно. Им нужно было сделать либо какой-то отдельный синтаксис, наподобие JSON или какой-то свой внутренний DSL, который компилятор / IDE мог бы понимать и помогать при работе с SBT.
Абсолютно всё в sbt можно писать и без DSL. Документация просто шик, API удобный, возможностей куча.
И по своему опыту скажу, что начать нормально «работать» намного быстрей c SBT, чем с maven, например.
Что вы подразумевает под «начать работать с maven»? Просто использовать — легко. А вот писать свои скрипты сборки — там да, порой отменные танцы начинаются. Писать свои инструменты — тоже не самое простое занятие.
Мне, после make, sbt кажется слишком сложным и неполноценным. Правда maven я вообще освоить не смог, а с sbt немного разобрался.
1. Конечно он сложный по сравнению с make. Ведь описание сборки происходит с помощью DSL на полноценной Scala.
2. Как он может быть неполноценным по сравнению с make? Ведь описание сборки происходит с помощью DSL на полноценной Scala.

Таски на Scala гораздо более удобные чем make, потому что каждый таск возвращает какое-то значение (функциональщина же), что позволяет гибко комбинировать их.
При этом sbt сам построит древо зависимостей, определит независимые таски и запустит их параллельно, увеличив скорость сборки проекта в разы (а то и на порядок).
Простого способа объединить несколько текстовых файлов в один на этапе компиляции, с перегенерацией его если исходные файлы менялись я не нашел. На make это делается в две строчки.

make делает только то, что явно определено. От sbt мне часто хотелось исключить некоторые файлы с расширением scala из сборки, хотя мне для отладки удобно что бы они жили в той же директории.
Лично мне в Scala не хватает checked-исключений. Это так «здово», когда код падает по исключению на ровном месте, а потом выясняется, что есть некая функция (и вполне себе библиотечная), которая там где-то глубоко в своих кишочках бросает исключение, но ты об этом не знаешь (в докумментации не всегда пишут) и компилятор уже тебе не подскажет, что это исключение никем не ловится… Интересно, кто-нибудь сталкивался с такой проблемой? Как решаете? Может ключик scala-компилятора какой есть?
Можно использовать try-catch с выхлопом в Either[Throwable,R]
Когда ты знаешь, что вот эта конкретная функция кидает исключение, то да. А вот если не знаешь, то компилятор тебе этого уже не подскажет как в Java.
Checked исключений нет, потому что Scala пропагандирует принцип «let it fail» (inspired by Erlang). То есть, если вы явно видите, что определенные ваши действия могут привести к исключению, и вы можете его обработать «на месте», то вы его обрабатываете, в противном случае исключение уходит «наверх», где кто-нибудь о нем позаботится, будь это caller или servlet container.
Если очень хочется, то в scalaz есть Validation — оно даже удобнее, чем checked exceptions.
Имена из «закорючек» еще можно создавать в Haskell. И приоритеты при постфиксной записи там задаются явно, а не на основе первого символа (неожиданное поведение совместного использования "->" и "+" вместе с неявным преобразованием аргумента "+" к строке мне как-то стоило нескольких часов рабочего времени :-)). Правда первая буква там тоже играет значение — различаются большие и малые буквы, а среди «закорючек» большой считается только ":" (возможно еще "[", но я надеюсь, что ее нельзя просто так использовать где попало :-)).
И в Java и в C++ шаблоны имеют определенное зашитое ограниченное поведение (т.е. ковариантность)
Не знаю, как в C++, но в Java generic'и инвариантны. Для получения ковариантного/контравариантного поведения используются wildcards. Массивы в Java, кстати, ковариантны.
В Java ко(нтра)вариантность класса указывается всеми пользователями класса. В Scala это свойство самого класса.
А я про что написал? Цитирую
Для получения ковариантного/контравариантного поведения используются wildcards.
Вы упомянули механизм, я лишь описал разницу в механизмах с точки зрения их использования. В Java сложность ложиться на пользователей класса, в Scala — на автора класса.
В целом и то, и другое — проблемы разработчика библиотеки.

Классические проблемы с wildcard'ами проявляются при портировании библиотек с java 1.4.2 на 1.5. Например: issues.apache.org/jira/browse/CONFIGURATION-561
но в Java generic'и инвариантны

Инварианты значит «НЕ ковариантны и НЕ контравариантны». В остальном все правильно. :)
Именно это и значит. List<String> не является не сабтипом или супертипом List<CharSequence>.

Если нужно ковариантное или контравариантное поведение функции по каким-то аргументам достигается использованием wildcard'ов.

Например, метод
class Arrays {
  public static <T> List<T> asList(T... a)
}
является инвариантным по результату. Т. е. нельзя, скажем, написать
String[] args = ...;
List<CharSequence> l = Arrays.asList(args); // оно же Arrays.<String>asList(args)
но можно
List<String> l = Arrays.asList(args); // оно же Arrays.<String>asList(args)
или
List<CharSequence> l = Arrays.<CharSequence>asList(args);
т. к. массивы являются ковариантными.

Приведу пример с wildcard'ом:
String join(Collection<? extends CharSequence> cs) {
  StringBuilder sb = new StringBuilder();
  for(CharSequence c: cs) {
    sb.append(c);
  }
  return sb.toString();
}
Он ковариантен по аргументу cs, т. е. готов работать как на коллекциях CharSequence, так и на коллекциях подтипов CharSequence (например String).

Есть классическая мнемоника PECS: producer extend, consumer super. В предыдущем примере коллекция из параметра cs порождает CharSequence, т. е. является producer'ом.
Что-то не получается открыть.
> Все языки программирования, которые я знаю, проводят жесткую грань между методами с именем, типа «append», и операциями, типа "+="
Я не совсем понимаю, чем "+=" лучше слова append? Операции +,-,*,/ идут из алгебры и интуитивно понятны в контексте операций с числами, векторами, etc… Тогда как операции !, /:, :/, ++=, &~ заставляют лезть в дебри API и делают код нечитаемым. Если перегрузка и имеет смысл, то исключительно для алгебраических операций. Scala же по-максимому злоупотребляет везде где только возможно.

> Есть всё же несколько вещей, которые мне не понравились, за чуть более чем шести летний опыт работы со Scala. Вот некоторые из них:
Собственно все то, чем так гордилась Scala коту под хвост ;) Можно много спорить о том, что скала сложна или разумно проста, но есть один неоспоримый факт в пользу первого: отсутствие до сих пор адекватной поддержки IDE. То есть я говору не о подсветке синтаксиса, а о полном понимании контекста кода, автоподстановке, так необходимой в DSL, быстрой дифференциальной компиляции, etc.

К вопросу о сложности. Простота не в том, чтобы как можно короче выразить в коде идею, а в том, чтобы существовал только один способ это сделать. В Scala же как и в Perl-е существует куча вариантов написать одно и то же. Это не есть хорошо, ибо любая свобода — это увеличение хаоса в системе. К тому же есть два постулата:
1. Свобода хороша для тех, кто умеет ей пользоваться.
2. Пользоваться свободой не умеет никто.
Так что выбирайте.
Я использую IDEA с плагином для Scala уже второй год и описанных Вами проблем еще не было. Насчет компиляции проблема всё еще есть, но не так чтобы прям это мешало бы жить. Множество способов сделать одно и тоже… Опять таки, кто заставляет пользоваться всеми этими способами…
Вы правы в том, что Скала позволяет легче выстрелить себе в ногу.

Это верно и для перегрузки операторов. Но если не злоупотреблять, то можно получить довольно красивые вещи, например,
val tomorrow = today + 1 day
А вот, кстати, как такое сделать? У меня получается только
val tomorrow = today + (1 day)

или
val tomorrow = today + 1.day
Например, так:
  case class CDate(ms: Long) {
    def +(i: Long) = new {
      def day = CDate(i * 1000 * 3600 * 24 + ms)
    }
  }

  val now = CDate(System.currentTimeMillis())
  val nowWithDayAdded = now + 1 day


Этот способ не лишен недостатков, например, не получится написать
  val someTimeInFuture = now + 1 day + 1 day
Забавно, о таком не подумал, хотя и крайне странный способ. Но, имхо, в скале вообще довольно плохо реализован вызов метода без аргументов без точки. Т.е. что не получиться
val someTimeInFuture = now + 1 day + 1 day

— да и хрен с ним. Гораздо печальнее, что не сработает
val nowWithDayAdded = now + 1 day
println(nowWithDayAdded)
Не понял, что именно не сработает?
У меня парсер компилятора ломается на подобных вызововах. Не понимает что выражение закончилось.

[ant:scalac] /home/nasledov/projects/godoftime/User/src/main/scala/timetest/Main.scala:16: error: recursive value nowWithDayAdded needs type
[ant:scalac]     println(nowWithDayAdded)
[ant:scalac]             ^         
[ant:scalac] /home/nasledov/projects/godoftime/User/src/main/scala/timetest/Main.scala:15: error: timetest.Main.CDate does not take parameters
[ant:scalac]     val nowWithDayAdded = now + 1 day
[ant:scalac]                                   ^


Если выводить не nowWithDayAdded, а что-нибудь другое, то остается только вторая ошибка. Лечится 3мя способами:
1. явно поставить ";"
2. оставить пустую строчку после «val nowWithDayAdded = now + 1 day»
3. начать след. строчку с любого ключевого слова (т.е. println — ломает, val… — нет)
В последних версиях Scala такой финт ушами по умолчанию запрещен, так как может создавать неодназначность синтаксиса. Создатели и разработчики языка тоже понимают, что сложностью языка надо как-то управлять.

Использование точки предпочтительнее, но если прямо совсем не в моготу можно добавить импорт:
import scala.language.postfixOps
Т.е. во всех других языках все действия можно сделать только одним способом? Вы серьезно?

Ну и постулаты конечно крутые. Всегда удивляли люди которые размышляют анекдотами)

И да, создание технологий для инженеров, не должно ориентироваться на дебилов.
Ваш комментарий сделал мой день!
И в Java и в C++ шаблоны имеют определенное зашитое ограниченное поведение

А List<T super Person> в Java уже отменили?
То, что вы написали и case class — не одно и то же. Ключевое слово «case» автоматически добавляет в класс множество полезных вещей, превращающий объекты этого класса в универсальные DTO.
Навскидку, генерируется конструктор, equals/hashCode, apply/unapply (для pattern matching), copy (очень полезный аналог clone), toString. И, кстати, case classes are immutable.
Еще пара мыслей по поводу equals/hashCode в java и scala:

В java существуют определенные правила и соглашения, как нужно писать equals/hashCode. В 99% случаев при написании приложений и необходимости переопределять equals/hashCode приходится генерировать «стандартный» код с помощью IDE. Более того, обычно необходимость написания equals/hashCode нестандартно или в нарушении conventions сигнализирует о недостатке дизайна приложения.

Так вот, то, что в java приходится делать средствами IDE, причем каждый раз заново при изменении сигнатуры класса, в Scala отдали на откуп компилятору. Причем то, что case class запрещено наследовать решает проблему работы equals/hashCode при наследовании.
О боже… ну напишите препроцессор на мавене/анте/.../аннтоациях, который обработает ваш класс так как вам надо.
Я не понимаю почему синтаксический сахар превозносится как нечто невероятное.
Так скала по сути и есть препроцессор, который обрабатывает наш код так, как надо (+ либы).
Скала — это другой язык, пусть и построенный с использованием старого (или вы будете и си называть препроцессором ассемблера?).
Не надо путать хрен с пальцем.
Scala это не синтаксический сахар над Java.
Иначе получается, что все что не машинные кода — синтаксический сахар на ними.
И как следствие ваших слов негодно к употреблению.
И кто же путает скалу с синтаксическим сахаром?.. Вы строчкой комментария не ошиблись, случайно?
Если же, вдруг, случайно, так получилось что вы о моём комментарии на два слоя выше (и тогда вы всё-таки строчкой нихреново ошиблись), то обращаю ваше внимание, что читать нужно одновременно с контекстом — я отвечал на конкретный комментарий, в котором речь шла исключительно о синтаксическом сахаре геттеров-сеттеров и ещё ряда стандартных функций в классе.
Вы путаете. Берете один простой пример про гетеры сеттеры и обзываете скалу сахаром)
Но ведь суть скалы далеко не в этих гетерах сеттерах. Это всего лишь один из многочисленных приятных моментов в скале. Замахаетесь писать препроцессоры на мавене/анте/.../аннтоациях для каждого случая)
Вы вообще комментарии читаете?
Ещё раз повторю:
я не называю всю скалу синтаксическим сахаром

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

Надеюсь теперь я достаточно ясно мысль выразил? Или и дальше будете меня убеждать, что скала не СС?
Лучше приведу аналогию, тогда возможно будет понятно почему я так написал.
Собственно многие люди делают ту же ошибку что и вы. Они думают про скалу как «несколько удобных плюшек для Java», а некоторые вообще всю скалу называют СС.
Так вот, представьте что идет обсуждение сравнения автомобиля ВАЗ и BMW. Показываются различные моменты и один из участников говорит «Блин какие же клевые, удобные, кожаные сиденья у BMW».
Тут врываетесь вы и говорите «О боже… ну обтяните кожей сиденья в ВАЗ».
Понятно, что можно обтянуть, никаких технических преград тут нет. Но какой блин в этом смысл?
ВАЗ не станет от этого лучше/удобнее BMW. Даже не автомобиль в целом, а те же кресла удобнее не станут. Да и дело то вообще не в коже. Это всего лишь один маленький момент из целого автомобиля.
Так и с вашим каментом. Вы его написали так, будто препроцессор на мавене/анте/.../аннотациях решит проблемы. Т.е. да, он позволит, потратив время и силы, сделать жалкое подобие case классов на Java. Но это все равно будет не то же самое и выглядеть будет костыльно, как и множество подобных решений.
Хотя… если для вас формализм важнее всего и практическая ценность не имеет значения, то да, препроцессор на мавене/анте/.../аннотациях позволит сделать подобие case классов.
Давайте попытаюсь ещё раз:
автор комментария выставляет определённый СС языка за эпическую фичу, при сравнение с другим языком, я ему отвечаю, что при некотором желании и потребности (а раз до сих пор нет — значит никому и не надо… мне во всяком случае пользы от этого почти никакой — это менее 5% моего кодинга) аналогичный СС можно прикрутить к другому языку, поэтому нет смысла выпячивать эту фичу.

И ваш пример некорректен, правильнее: «Блин, какие же клёвые удобные кожаные сиденья у BMW, не то что у вашего БелАЗа». А ответ «Ну поставьте такие же в БелАЗ».

Теперь понятнее?
Во первых, про эпическую фичу вы сами придумали. Никто про это не говорил.
Никто не говорил что case классы это киллер фича, это всего лишь пример одной особенности.
Просто это очень удобная особенность. Ее обычно примечают все кто начинает пользоваться скалой. Она наглядна и легко воспринимается программистами при сравнении, особенно джавистами. Идет из коробки и не требует костылей. Поэтому ее всегда приводят при сравнении скалы с другми языками.
Как следствие, ваши каменты выглядят глупо.
Ну что же — и флаг вам в руки и барабан на шею. Больше пытаться убедить в том, что белое это не триколор не буду.

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

Где грань между «синтаксическим сахаром» и фичей, которая позволяет использовать новый подход в программировании?

Простой пример, ленивая инициализация.
В Java я ее использую только тогда, когда затраты на ее написание оправданы. Хотя, если задуматься, то это очень мощный механизм, который иногда дает хороший прирост производительности и уменьшает затраты памяти. В Scala это одно ключевое слово «lazy». С его использованием нет проблем, оно не мешает читаемости кода. Таким образом, выходит, что Scala поощряет использовании такого подхода, а Java — нет. Хотя, фактически, синтаксический сахар.
Сори, не всегда понятно, где ирония (а вы тег забыли), а где человек действительно так думает.

А по поводу ленивки — так может ещё будет что-то подобное и в Java. Тут вопрос скорее в том, что это далеко не всегда нужно.
Деконструкцию case class'ов тоже на препроцессоре сделаем?
При желании — сделаем. Только желания нет — не вижу практической применимости. Можете пример привести?

Хотя вынужден признать, что не очень понял из примера, что же такое эта ваша «деконструкция» (может быть преобразование класса в структуру и поиск в ней?).

И да — как и к solver у меня к вам предложение читать мои комментарии в контексте предыдущего (на который я и отвечал). А там речь идёт лишь о плюшках, которые суть — синтаксический сахар.
Объявление case class'а в Scala:
case class Person(name: String, age: Int)


«Эквивалентный» код в Java:
public class Person { ...
public class Person {
    private String name;
    private int age;


    public Person(String name, int age) {
        this.name = name;
        this.age  = age;
    }


    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    
    public equals(Person other) {
        return other.name.equals(this.name) &&
               other.age == this.age;
    }


    @Override
    public String toString() {
        return "Person(" + name + ", " + Integer.toString(age) + ")";
    }


    @Override
    public int hashCode() {
        int hash = 1;
        hash = hash * 31 + name.hashCode();
        hash = hash * 42 + age;
        return hash;
    }


    public Person copy() {
        return this;
    }

    public Person copy(String name) {
        return new Person(name, this.age);
    }

    public Person copy(int age) {
        return new Person(this.name, age);
    }

    public Person copy(String name, int age) {
        if (name.equals(this.name) &&
            age == this.age) {
            // prevent new non-uniq object of immutable
            // data structure from being created
            return this;
        } else {
            return new Person(name, age);
        }
    }
}

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



Так вот, синтаксический сахар, это когда:
// можно так
x = x + 1
// а можно так
x += 1


А case class'ы и другие фичи Scala — это уже «конструкции языка».
Извините, но вот это:
try (InputStream is = file.openStream()) {
   // do something
}


синтаксический сахар для

InputStream is = null;
try {
   is = file.openStream();
   // do something
} finally {
   if (is != null)
      is.close();
}


Аналогичный СС можно проидумать для предложенной ситуации со стандартными методами класса.

PS а с каких пор СС перестал иметь возможность быть конструкцией языка?
В первыйх эквивалентный класс вот это:

public class Person {
    public final String name;
    public final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        if (!name.equals(person.name)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


Ибо кейз класс иммутабелен

Во вторых если вы у вас на написание этого класса уходит больше подобного кода уходит больше 2 минут, то вам либо стоит подумать об смене инструмента или о более его глубоком освоении.

Ибо после напиания

public class Person {
    public final String name;
    public final int age;
}


Все остальное делается средствами IDE.
Нет. Геттеры тут важны. case class может имплементировать интерфейс с методами соответствующими.
copy вы тоже зря упускаете.

И да, это не только писать, но и читать надо. И поддерживать (например актуальность equals при добавлении поля).
И далеко не всегда есть возможность использовать IDE. Например при экспериментах в REPL.

Кстати, почему-то все радостно забывают про фабричный метод.
Sign up to leave a comment.

Articles