Статьи

10 самых досадных вещей, возвращающихся на Яву после нескольких дней Scala

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

После нескольких дней, когда вы действительно привыкли к удивительному синтаксису Scala, вот 10 лучших вещей, которые мне больше всего не хватает при возвращении к написанию Java:

1. Многострочные строки

Это мой личный фаворит и действительно потрясающая функция, которая должна быть на любом языке. Даже в PHP это есть: многострочные строки. Так же просто, как писать:

1
2
3
4
5
6
7
println ("""Dear reader,
 
If we had this feature in Java,
wouldn't that be great?
 
Yours Sincerely,
Lukas""")

Где это полезно? С SQL, конечно! Вот как вы можете запустить простой SQL-оператор с jOOQ и Scala :

1
2
3
4
5
6
7
8
9
println(
  DSL.using(configuration)
     .fetch("""
            SELECT a.first_name, a.last_name, b.title
            FROM author a
            JOIN book b ON a.id = b.author_id
            ORDER BY a.id, b.id
            """)
)

И это не только хорошо для статических строк. С помощью интерполяции строк вы можете легко вставлять переменные в такие строки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
val predicate =
  if (someCondition)
    "AND a.id = 1"
  else
    ""
 
println(
  DSL.using(configuration)
      // Observe this little "s"
     .fetch(s"""
            SELECT a.first_name, a.last_name, b.title
            FROM author a
            JOIN book b ON a.id = b.author_id
            -- This predicate is the referencing the
            -- above Scala local variable. Neat!
            WHERE 1 = 1 $predicate
            ORDER BY a.id, b.id
            """)
)

Это довольно круто, не правда ли? Для SQL в Scala большой потенциал.

jooq-The-лучший способ к записи-SQL-в-Скале-Smalll

2. Точки с запятой

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

1
2
3
val a = thisIs.soMuchBetter()
val b = no.semiColons()
val c = at.theEndOfALine()

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

3. Скобки

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

1
myObject method myArgument

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

1
val s = myObject.toString

4. Тип вывода

Этот действительно раздражает в Java, и кажется, что многие другие языки сделали это правильно, тем временем. Java имеет только ограниченные возможности вывода типов, и все не так ярко, как могло бы быть .

В Scala я мог бы просто написать:

1
val s = myObject.toString

… и не заботится о том, что s имеет тип String. Иногда, но только иногда мне нравится явно указывать тип моей ссылки. В этом случае я все еще могу сделать это:

1
val s : String = myObject.toString

5. Кейсы

Я думаю, что мне хотелось бы написать еще один POJO с 40 атрибутами, конструкторами, геттерами, сеттерами, равно, hashCode и toString

— Никто не сказал. Когда-либо

В Scala есть тематические классы. Простые неизменные pojos, написанные в однострочниках. Возьмем класс Case, например:

1
case class Person(firstName: String, lastName: String)

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

А как вы создаете экземпляр такого класса case? Легко, вам даже не нужен new оператор (на самом деле, он полностью ускользает от моего воображения, зачем вообще нужен new оператор):

1
Person("George", "Orwell")

Вот и все. Что еще вы хотите написать, чтобы соответствовать требованиям Enterprise?

Примечание

ОК, некоторые люди теперь будут спорить, чтобы использовать проект ломбок . Генерация кода на основе аннотаций — это бессмыслица, и ее лучше избегать. На самом деле, многие аннотации в экосистеме Java являются простым доказательством того факта, что язык Java очень ограничен в своих возможностях эволюции. Возьмите, например, @Override . Это должно быть ключевое слово, а не аннотация. Вы можете подумать, что это косметическая разница, но я говорю, что Scala доказала, что аннотации — это почти всегда неправильный инструмент. Или вы недавно видели сильно аннотированный Scala-код?

6. Методы (функции!) Везде

Это, на мой взгляд, одна из самых полезных функций на любом языке. Почему мы всегда должны связывать метод с определенным классом? Почему мы не можем просто иметь методы на любом уровне контекста? Потому что мы можем с помощью Scala:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// "Top-level", i.e. associated with the package
def m1(i : Int) = i + 1
 
object Test {
 
    // "Static" method in the Test instance
    def m2(i : Int) = i + 2
     
    def main(args: Array[String]): Unit = {
 
        // Local method in the main method
        def m3(i : Int) = i + 3
         
        println(m1(1))
        println(m2(1))
        println(m3(1))
    }
}

Правильно? Почему я не могу определить локальный метод в другом методе? Я могу сделать это с классами в Java:

1
2
3
4
5
public void method() {
    class LocalClass {}
 
    System.out.println(new LocalClass());
}

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

Они также поддерживаются в JavaScript или REPL . Это замечательно для тестирования небольшого алгоритма или концепции, выходящей за рамки вашего приложения.

В Java мы обычно склонны делать это:

01
02
03
04
05
06
07
08
09
10
public class SomeRandomClass {
 
    // [...]
   
    public static void main(String[] args) {
        System.out.println(SomeOtherClass.testMethod());
    }
 
    // [...]
}

В Scala я бы просто написал это в REPL:

1
println(SomeOtherClass.testMethod)

Обратите внимание также на всегда доступный метод println . Чистое золото с точки зрения эффективной отладки.

8. Массивы НЕ (это большая часть) особый случай

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

массив

Да, правила как:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// Compiles but fails at runtime
Object[] arrrrr = new String[1];
arrrrr[0] = new Object();
 
// This works
Object[] arrrr2 = new Integer[1];
arrrr2[0] = 1; // Autoboxing
 
// This doesn't work
Object[] arrrr3 = new int[];
 
// This works
Object[] arr4[] = new Object[1][];
 
// So does this (initialisation):
Object[][] arr5 = { { } };
 
// Or this (puzzle: Why does it work?):
Object[][] arr6 = { { new int[1] } };
 
// But this doesn't work (assignment)
arr5 = { { } };

Да, список можно продолжать. С Scala массивы — это не особый случай, если говорить синтаксически:

1
2
3
4
5
6
7
val a = new Array[String](3);
a(0) = "A"
a(1) = "B"
a(2) = "C"
a.map(v => v + ":")
 
// output Array(A:, B:, C:)

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

9. Символические имена методов

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

1
2
3
val x = BigDecimal(3);
val y = BigDecimal(4);
val z = x * y

Очень интуитивно, значение z должно быть BigDecimal(12) . Это не может быть слишком сложно, не так ли? Мне все равно, является ли реализация * действительно методом с именем multiply() или чем-то еще. При написании метода я хотел бы использовать то, что выглядит как очень распространенный оператор для умножения.

Кстати, я также хотел бы сделать это с SQL. Вот пример:

1
2
3
4
5
6
7
select (
  AUTHOR.FIRST_NAME || " " || AUTHOR.LAST_NAME,
  AUTHOR.AGE - 10
)
from AUTHOR
where AUTHOR.ID > 10
fetch

Разве это не имеет смысла? Мы знаем, что || означает concat (в некоторых базах данных). Мы знаем, что означает - (минус) и > (больше чем). Почему бы просто не написать это?

Выше приведен пример компиляции jOOQ в Scala , кстати.

jooq-The-лучший способ к записи-SQL-в-Скала-маленькие

Внимание: будьте осторожны

Всегда есть обратная сторона, допускающая что-то вроде перегрузки операторов или символических имен методов. Это может (и будет) злоупотреблено. И библиотеками, и языком Scala .

10. Кортежи

Будучи человеком SQL, это опять-таки одна из особенностей, которые мне больше всего не хватает в других языках. В SQL все является либо TABLE, либо ROW. мало кто на самом деле знает это , и лишь немногие базы данных поддерживают такой способ мышления.

В Scala нет типов ROW (которые на самом деле являются записями), но, по крайней мере, существуют анонимные типы кортежей. Думайте о строках как о кортежах с именованными атрибутами, тогда как классы case будут именоваться строками

  • Кортеж: анонимный тип с типизированными и индексированными элементами
  • Строка: анонимный тип с типизированными, именованными и индексированными элементами
  • case class: Именованный тип с типизированными и именованными элементами

В Scala я могу просто написать:

1
2
3
4
5
// A tuple with two values
val t1 = (1, "A")
 
// A nested tuple
val t2 = (1, "A", (2, "B"))

В Java подобное можно сделать, но вам придется писать библиотеку самостоятельно, и у вас нет языковой поддержки:

1
2
3
4
5
6
7
class Tuple2<T1, T2> {
    // Lots of bloat, see missing case classes
}
 
class Tuple3<T1, T2, T3> {
    // Bloat bloat bloat
}

А потом:

1
2
3
4
5
6
// Yikes, no type inference...
Tuple2<Integer, String> t1 = new Tuple2<>(1, "A");
 
// OK, this will certainly not look nice
Tuple3<Integer, String, Tuple2<Integer, String>> t2 =
    new Tuple3<>(1, "A", new Tuple2<>(2, "B"));

jOOQ широко использует вышеописанную технику для перевода выражений значений строк SQL в Java, и, что удивительно, в большинстве случаев вы можете обойтись без вывода пропущенного типа, поскольку jOOQ — это свободный API, в котором вы никогда не назначаете значения локальным переменным… пример:

1
2
3
4
5
6
7
8
9
DSL.using(configuration)
   .select(T1.SOME_VALUE)
   .from(T1)
   .where(
      // This ROW constructor is completely type safe
      row(T1.COL1, T1.COL2)
      .in(select(T2.A, T2.B).from(T2))
   )
   .fetch();

Вывод

Это была, безусловно, про-Scala и слегка противоречащая Java-статья. Не пойми меня неправильно. Я ни в коем случае не хочу полностью мигрировать в Scala. Я думаю, что язык Scala выходит далеко за рамки разумного в любом полезном программном обеспечении. Есть много маленьких функций и уловок, которые, кажется, приятно иметь, но неизбежно взорвутся на вашем лице, такие как:

  • implicit преобразование. Это не только очень сложно, но и ужасно замедляет компиляцию. Кроме того, вероятно, совершенно невозможно реализовать семантическое управление версиями разумно, используя implicit , поскольку, вероятно, невозможно предвидеть все возможные нарушения клиентского кода из-за случайной обратной несовместимости.
  • Поначалу локальный импорт кажется великолепным, но его мощь быстро делает код неразборчивым, когда люди начинают частично импортировать или переименовывать типы для локальной области видимости.
  • символические имена методов чаще всего используются неправильно. Возьмите, например, API синтаксического анализатора, который содержит имена методов, такие как ^^ , ^^^ , ^? или ~!

Тем не менее, я думаю, что преимущества Scala над Java, перечисленные в этой статье, могут быть реализованы и на Java:

  • с небольшим риском нарушения обратной совместимости
  • с (вероятно) не слишком большими усилиями, с точки зрения JLS
  • с огромным влиянием на производительность разработчиков
  • с огромным влиянием на конкурентоспособность Java

В любом случае, Java 9 будет еще одним многообещающим выпуском с горячими темами, такими как типы значений, декларация сайта , специализация (очень интересно!) Или ClassDynamic.

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