понедельник, 26 января 2015 г.

Ликбез по Either

Есть два типа Either:
Симметричный Either реализован полностью зеркально и не отдаёт предпочтения ни левому, ни правому параметру. Это означает, что методы map и flatMap не имеют смысла, т.к. непонятно, над каким параметром производить действие: над левым или над правым. Поэтому вводятся дополнительные сущности LeftProjection и RightProjection, и чтобы можно было писать for-comprehension, нужно сначала позвать методы either.left() или either.right().
Scalaz и Haskell используют другой подход. Так как в 99% случаев Either используются для возвращения из функций либо значений, либо ошибок, то можно договориться, что мы значение всегда кладём в Right и на нём же реализуем методы map и flatMap. Таким образом, необходимость в проекциях отпадает, и мы спокойно пишем for-comprehension:
val either1: String \/ Int = \/-(3)
val either2: String \/ Int = \/-(4)
 
val either3 = for {
  val1 <- either1
  val2 <- either2
} yield val1 + val2
 
println(either3) // Печатает \/-(7)

Мне больше нравится смещённый вправо Either, так как код становится чище и лаконичнее. А если возникнет необходимость сделать for-comprehension с левой частью, можно использовать метод swap.

суббота, 24 января 2015 г.

OOP is dead?

Хайп вокруг ООП за последние несколько лет сильно стух:
  • Новые языки программирования отказываются от наследования (Go, Rust) и добавляют всё больше и больше функциональных вещей. Лямбды есть уже во всех мейнстримовых языках.
  • Люди всё чаще используют динамические и скриптовые языки (JavaScript заметно вырвался вперёд по популярности, хотя несколько лет назад был наравне с Java).
  • Исчезают статьи про паттерны проектирования и прочий оопшный мусор. А статьи про монады растут в геометрической прогрессии.
  • Программисты стали чаще отказываться от тяжелых фреймворков и контейнеров в пользу более лёгких. Сейчас уже трудно встретить человека, который с энтузиазмом бы рассказывал про какой-нибудь GlassFish или Spring.
  • Не совладая с экспоненциально растущей сложностью своих проектов, люди стали винить даже процесс разработки и начали искать решения в гибких методологиях вроде Scrum или XP. Вместо того чтобы развиваться и изучать компьютерную науку, люди занимаются самообманом. Agile не решит проблему ООП: граф состояний объектов так и будет расти экспоненциально.
Мне кажется, эра ООП постепенно подходит к концу. Целая индустрия фреймворков построена исключительно для того, чтобы решать проблемы, существующие только в мире ООП. Столько мозгов и денег пропадает впустую, решая несуществующие проблемы!

Мы должно положить этому конец. Чем скорее ООП сдохнет, тем лучше.

воскресенье, 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 есть макросы, что может позволить решать некоторые задачи значительно короче и безопаснее, например интернационализация.