Самые главные проблемы Java:
- Нету нормального вывода типов.
- Нету паттерн-матчинга.
- null
- Точки с запятой.
- Обязательное ключевое слово return.
- Нету синтаксиса для Value Object.
- Всё по умолчанию mutable.
- Примитивные типы оторваны от ссылочных.
- Usage site variance.
- Отсутствие хвостовой рекурсии.
Посмотрим, как решают вышеописанные проблемы языки-убийцы Java.
Xtend
1. Вывод типов
Есть:
val n = 5
for (i : 1..n) {
println(i)
}
Указывать типы не надо. Всё выводится из контекста.
2. Паттерн-матчинг
В языке полноценного паттерн-матчинга нету. Есть лишь улучшенный switch:
switch (x) {
case 1: println("x == 1")
case String: println("String")
default: println("Unknown")
}
В принципе неплохо, но до нормального паттерн-матчинга не дотягивает. Полбалла.
3. null
Никаких средств языка для борьбы с null нет.
4. Точки с запятой
Не нужны. Кроме того, ненужны пустые скобки. Зачёт.
5. return
Ключевое слово return не нужно. Всё является выражением:
def int factorial(int n) { if (n == 0) 1 else n * factorial(n - 1) }
6. Value Object
Чтобы объявить Value Object, нужно заюзать аннотацию @Data, которая делает поля final, создаёт конструктор из полей, геттеры, hashCode, equals и toString:
@Data
class
Point {
int
x
int
y
}
val
p = new Point(1, 2)
val
p2 =
new
Point(1, 2)
println(p == p2)
// true
Зачёт.
7. Mutability
Из средств борьбы с изменяемостью:
- Ключевое слово val.
- Аннотация @Data.
- Аргументы функций final.
- Литералы коллекций возвращают неизменяемые коллекции.
В остальном все проблемы Java унаследованы. Персисентных коллекций нету. Полбалла.
8. Примитивные типы
Всё как в Java. Примитивные типы не могут быть подставлены в качестве аргументов дженериков (List<int> нельзя написать). Слив.
9. Variance
Так же как в Java (usage site variance). Wildcard'ы и прочая дрянь. Незачёт.
10. Хвостовая рекурсия
Отсутствует.
Kotlin
1. Вывод типов
Имеется:
val n = 5
for (i in 1..n) {
println(i)
}
2. Паттерн-матчинг
Так же как в Xtend есть улучшенный switch:
when (x) {
1 -> println("x == 1")
is String -> println("String")
else -> println("Unknown")
}
3. null
В Kotlin заложена полноценная борьба с null. Все типы по умолчанию являются не-nullable. Например val x: String = null не скомпилируется (нужно объявить тип String?, чтобы заработало). Зачёт.
4. Точки с запятой
Не нужны.
5. return
Необязательно:
fun factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1)
6. Value Object
Присутствует:
data class Point(val x: Int, val y: Int)
val p = Point(1, 2)
val p2 = Point(1, 2)
println(p == p2) // true
7. Mutability
Есть val. Коллекции разделены на изменяемые и неизменяемые, а не как в Java - всё изменяемое. Изменяемые отнаследованы от неизменяемых. Неизменяемые коллекции не являются персистентными, т.е. в неизменяемый List не добавишь элемент за O(1). Полбалла кароч.
8. Примитивные типы
Есть Any, от которого отнаследовано всё, в том числе Int, Long, Double и т.д. Соответственно дженерики работают на всех классах, а не только на ссылочных. Зачёт.
9. Variance
Mixed variance. Т.е. и usage site variance есть, и declaration site variance. В общем, круто.
10. Хвостовая рекурсия
Есть. Чтобы включить оптимизацию хвостовой рекурсии, нужно использовать ключевое слово tailRecursive:
fun foldLeft<A, B>(z: B, array: Array<A>, f: (A, B) -> B): B {
[tailRecursive] fun go(i: Int, z: B) =
if (i == array.size()) z
else go(i + 1, f(array.get(i), z))
return go(0, z)
}
Ceylon
1. Вывод типов
Имеется:
value n = 5;
for (i in 1..n) {
print(i);
}
2. Паттерн-матчинг
Тоже только улучшенный switch:
switch (x)
case (1) { print("x == 1"); }
case (is String) { print("String"); }
else { print("Unknown"); }
3. null
Как в Kotlin, есть не-nullable и nullable типы. Следующий код не скомпилируется:
String s = null;
Кроме того, null проверяется в рантайме (вставляются проверки, и вылетает NPE, если значение оказалось null, но тип значения не-nullable).
4. Точки с запятой
Нужны. Fail.
5. return
Ключевое слово return необязательно:
Integer factorial(Integer n) => (n == 0) then 1 else n * factorial(n - 1);
6. Value Object
В документации Ceylon я не нашёл ничего касательно создания data-классов.
7. Mutability
Есть value, которое объявляет неизменяемую переменную:
value s = "String";
Поля классов по умолчанию неизменяемые. Как и в Kotlin, есть изменяемые, и неизменяемые коллекции. Первые отнаследованы от вторых. Персистентности нет.
8. Примитивные типы
Как и в Kotlin, понятия примитивных типов нету. Есть только nullable и не-nullable. Все типы отнаследованы от Anything (nullable и не-nullable).
9. Variance
Declaration site variance и usage site variance.
10. Хвостовая рекурсия
Нету.
Scala
1. Вывод типов
Присутствует:
val n = 5
for (i <- 1 to n) {
println(i)
}
2. Паттерн-матчинг
Да!
x match {
case 1 => println("x == 1")
case s: String => println("String")
case (a, b): String => println(s"Pair: $a and $b")
case _ => println("Unknown")
}
3. null
В Scala нету синтаксиса, запрещающего значению быть null. Все пользовательские типы (если они не AnyVal) являются nullable.
4. Точки с запятой
Не нужны.
5. return
Не нужно:
def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1)
6. Value Object
Есть case-классы.
7. Mutability
В Scala присутствует честная иммутабельность из коробки:
- Переменные - val,
- Поля классов неизменяемы, если не написать var.
- Персистентные коллекции (List, Vector, HashMap ...)
8. Примитивные типы
Все типы отнаследованы от Any. Все ссылочные типы отнаследованы от AnyRef, который в свою очередь отнаследован от Any. Также можно вводить свои value-типы, отнаследов их от AnyVal.
9. Variance
Declaration site variance и usage site variance. Хотя usage site variance не нужна. Во всех вышеперечисленных языках её добавили только ради interop'а с Java.
10. Хвостовая рекурсия
Есть.
Выводы
Итого:
- Первое/второе место - Scala, Kotlin (9 баллов)
- Третье место - Ceylon (6 баллов)
- Четвёртое место - Xtend (5 баллов)
Победили Scala (что ожидаемо) и Kotlin. Вообще Kotlin подаёт большие надежды. Когда допилят, можно смело пользоваться.
В Ceylon'е не вижу смысла, да и синтаксис у него странный.
Xtend - только если нужен 100% interop с Java, т.к. транслируется не в байткод, а в Java-код. Также в Xtend есть макросы, что может позволить решать некоторые задачи значительно короче и безопаснее, например
интернационализация.