воскресенье, 18 января 2015 г.

Сравнение Xtend, Kotlin, Ceylon и Scala

Самые главные проблемы Java:
  1. Нету нормального вывода типов.
  2. Нету паттерн-матчинга.
  3. null
  4. Точки с запятой.
  5. Обязательное ключевое слово return.
  6. Нету синтаксиса для Value Object.
  7. Всё по умолчанию mutable.
  8. Примитивные типы оторваны от ссылочных.
  9. Usage site variance.
  10. Отсутствие хвостовой рекурсии.
Посмотрим, как решают вышеописанные проблемы языки-убийцы 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) {
  -> 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 == 01 else n * factorial(n - 1)

6. Value Object


Присутствует:
data class Point(val x: Int, val y: Int)

val p = Point(12)
val p2 = Point(12)
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 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 <- 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 == 01 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 есть макросы, что может позволить решать некоторые задачи значительно короче и безопаснее, например интернационализация.