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

среда, 14 января 2015 г.

О важности базовых примитивов

Часто в спорах о языках программирования и технологиях оппоненты приводят со своей стороны аргументы вроде:

  • "X - не гавно, ты просто не умеешь этим пользоваться."
  • "Эта фича в Y есть, просто нужно её включить/заимпортировать."
  • "Язык Z этого не умеет, но есть библиотека W, которое делает то же самое."
В процессе спора дальше выясняется, что:
  • Правильно пользоваться то научиться можно, но это довольно сложно и нужно постоянно следить за тем, как бы случайно не наступить на грабли и не отстрелить себе ногу.
  • Подключение фичи идёт через жопу, включенное работает криво и неудобно.
  • Библиотека W оказывается дерьмовой поделкой, которая даже близко не умеет того, что есть в нормальных языках.
Дабы не быть голословным, вот примеры:
  • В Java почти всё стандартное API - блокирующее. "Но у нас же есть асинхронные каналы!" - возразят некоторые упоровшиеся по Java разработчики. Во-первых, оно появилось только в Java 1.7, во-вторых лично я не видел более худшего API, чем java.nio.channels. Вообще любая работа с сетью в Java - это боль и страдание. Ни один человек в здравом уме для работы с сетью не будет использовать стандартные примитивы Java, а возьмёт нормальную библиотеку вроде Netty.
  • В Java отсутствует синтаксис для value object'ов (2015 год на носу). "Но у нас же есть Lombok/AutoValue!". Ну что ж, я рад за вас, только AutoValue всё равно требует написания десятка строк на каждый класс. Да и смотрится оно по-уродски (напишите аналог data Maybe a = Nothing | Just a и убедитесь).
  • В Java всё по-умолчанию изменяемое. Поля классов, коллекции, локальные переменные. "Ну так поставь final, подключи Functional Java". Ну final я и так везде ставлю, а вот подключать библиотеки с другой парадигмой в проект не собираюсь. Просто коллеги не поймут. Будут материться, упрутся рогом, использовать фичи не будут. А будут по старинке клепать ArrayList'ы.
Вместо Java можете подставить C+, Go, C# и любой другой императивный язык. Там всё плохо практически в такой же степени (в C++ значительно хуже).

Так в чём причина плохого качества этих языков? Почему мы имеем в Java сотни тысяч готовых библиотек (среди которых есть очень много хороших, кстати), но продолжаем клепать неработоспособный и неподдерживаемый софт?

Дело в примитивах, которые есть из коробки. Если эти примитивы спроектированы дерьмово, то всё программирование на этом языке будет таким же. И не спасут вас никакие супер-пупер библиотеки. Вы можете написать супермодную библиотеку для работы с сетью, классную либу для реактивного программирования, сделать крутой препроцессор аннотаций для убирания boilerplate-кода, но все эти библиотеки будут жить в стороне, а базовые примитивы не изменятся. Люди будут продолжать наступать на те же грабли годами и десятилетиями, просто потому что примитивы, доступные из коробки, неправильны. ArrayList есть из коробки, а ImmutableList - нету, вот и будут они пихать этот ArrayList везде, даже там где по смыслу нужен неизменяемый список, а это процентов 80 случаев. По умолчанию все поля в классах изменяемы, а чтобы сделать их неизменяемыми, нужно написать final, а это долго и лень. Вот и будут большинство программистов херачить проекты с тоннами изменяемых классов. А потом вся команда уволится, и наймут студента, который этот проект будут поддерживать. Он в свою очередь допишет ещё пару тысяч классов с null'ами и synchronized-блоками, и тоже уволится. А следующий программист будет 90% времени отлавливать баги с NPE и стараться хоть как-то повысить производительность тормозящего приложения.

Да, я ни в коем случае не утверждаю, что на Java или Python нельзя написать хорошее и качественное приложение. Но чтобы это сделать, программист должен быть очень опытным и хорошо разбираться в архитектурных недостатках своей платформы.

Да, я считаю, что чтобы писать поистине качественные и при этом сложные приложения, не обязательно быть программистом с 10-летним стажем, который собаку съел на косяках своего языка программирования. Можно просто выбрать правильный язык с правильными базовыми примитивами. Если бы в Java всё по умолчанию было final и не было null, то мы бы сейчас имели совсем другую индустрию разработки.

В общем, изучайте новые языки и новые парадигмы. Смотрите на Kotlin, Rust, Swift, Haskell, Clojure, Scala. Только помните, что переход нужно осуществлять в комплексе. Не нужно использовать на Scala ArrayList'ы и Thread'ы. В Scala свои примитивы, которые требуют других подходов.