Показаны сообщения с ярлыком scala. Показать все сообщения
Показаны сообщения с ярлыком scala. Показать все сообщения

вторник, 28 апреля 2015 г.

Про RoboVM

На прошедшем хакатоне я решил попробовать себя в совершенно новой для себя области  разработка под iOS. Эта задача была для меня настоящим вызовом, так как до этого у меня не было ни опыта мобильной разработки, ни опыта разработки в Xcode. И вообще, я даже не являюсь пользователем Mac'а. С другой стороны, задачу сильно облегчило то, что писал я не на Obective-C под Xcode, а на Java+Scala под Eclipse, которые мне хорошо знакомы.

Для программирования под iOS на Java я использовал RoboVM  AOT-компилятор байт-кода в нативный код ARM для создания нативных приложений под iOS. До этого для программирования на Java я использовал исключительно Oracle HotSpot, так что использование AOT-компилятора также являлось для меня новым опытом.

Впечатления RoboVM оставил хорошие. Из того, что я понял  это полноценная Java 7, поддерживающая все её основные возможности кроме динамической загрузки классов, так как динамическая компиляции под iOS запрещена. Hibernate, скорее всего, не получится использовать :).

На сайте RoboVM лежит отличный тьюториал, в котором подробно расписаны шаги для создания своего первого проекта под iOS. Сначала я установил плагин RoboVM для Eclipse и создал RoboVM iOS Project:



После создания проекта я нажал кнопку Run As iOS Simulator (iPad). Первый запуск длился где-то минуты три, так как RoboVM сначала должен был спомпилировать всю стандартную библиотеку Java (robovm-rt.jar) и классы для разработки под iOS (robovm-objc.jar и robovm-cocoatouch.jar): в сумме это около трёх тысяч class-файлов. Стоит оговориться, что сидел я на довольно старом и тормозном Mac mini, поэтому на современном железе процесс занял бы намного меньше.

Немного больше пришлось повозиться с запуском приложения под настоящим iPad'ом. Загвоздка состояла в том, что для запуска под iPad / iPhone нужно быть членом программы iOS Developer, а для этого нужно заплатить корпорации Яблоко 100 долларов. К счастью, в нашей конторе уже был купленный аккаунт, и я просто вбил в Xcode его логин и пароль, после чего запуск из под Eclipse заработал.

Наконец, преодолев все вышеописанные технические шаги, я начал писать код. Процесс разработки оказался простым и приятным. Каждый следующий запуск приложения был уже не таким долгим как первый и длился где-то 20 секунд. В консоли Eclipse можно было смотреть логи System.out, а также stacktrace'ы исключений, но в них отсутствовали номера строк, так как я использовал бесплатную версию RoboVM. Также утверждается, что в платной версии можно использовать отладчик.

Немного смутило полное отсутствие javadoc'ов в классах для работы с iOS, но это компенсировалось нормальными именами классов и методов. Насколько я помню, я даже ни разу не лез в гугл, а просто искал нужные классы, смотрел список их методов и выбирал нужные. Хотя с другой стороны, для своего калькулятора я не использовал ничего кроме простейших контролов, поэтому не возьмусь утверждать, что так же легко будет разрабатывать более сложные приложения с нетривиальной логикой и навороченным дизайном.

Код калькулятора я выложил на github. Финальное приложение выглядит следующим образом (не судите строго):



Для полноты эксперимента я также решил использовать в проекте Scala. В частности, парсер и интерпретатор калькулятора написаны на Scala. Со Scala были мелкие проблемы, которые решились вынесением всего Scala-кода в отдельный проект и jar-файл. RoboVM пришлось компилировать также классы из стандартной библиотеки Scala (а это ещё 3000 классов), в итоге весь финальный бинарный файл распух до 35 MB (ipa-файл  8MB).

Вообще говоря, RoboVM вроде бы не компилирует все-все классы, которые лежат в Build Path. Вместо этого он берёт только те классы, на которые ссылается ваш код, а также все их транзитивные зависимости. Поэтому если вы в проекте где-то используете Class.forName(), то он может кинуть ClassNotFoundException. Этого можно избежать путём явного прописывания имён таких классов в файле robovm.xml. В частности, в моём случае при инициализации Scala-классов где-то происходила попытка загрузки классов, связанных с шифрованием, но их не было в рантайме, и вылетала такая ошибка:
java.lang.SecurityException: META-INF/TYPESAFE.SF has invalid digest for scala/Function2$mcIJI$sp$class.class in /Users/user/Library/Developer/CoreSimulator/Devices/0532E093-3660-4EB8-B8CB-AA5E09677E6F/data/Containers/Bundle/Application/533E98CB-A2E0-40F1-B7AE-DDB9C5838D6A/Calculator.app/lib/org.scala-lang.scala-library_2.11.6.v20150224-172222-092690e7bf.jar
Проблема решилась добавлением следующих классов в robovm.xml:
<forceLinkClasses>
  <pattern>com.android.org.conscrypt.OpenSSLProvider</pattern>
  <pattern>com.android.org.conscrypt.OpenSSLSignature</pattern>
  <pattern>com.android.org.conscrypt.OpenSSLMessageDigestJDK</pattern>
  <pattern>com.android.org.conscrypt.JSSEProvider</pattern>
  <pattern>com.android.org.conscrypt.OpenSSLRSAKeyFactory</pattern>
</forceLinkClasses>

В итоге, своим экспериментом с RoboVM, длившемся два дня, я остался доволен. В процессе работы компилятор вёл себя чрезвычайно стабильно, и я не встретил ни одного бага. Плагин для Eclipse также работал стабильно.

Это вовсе не значит, что я ручаюсь за RoboVM на 100% и рекомендую разрабатывать на нём под iOS. Решайте сами, использовать его или нет. Но как минимум, знайте, что появилась ещё одна альтернатива для написания iOS приложений, которая выглядит на мой взгляд довольно перспективно и привлекательно.

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

Диаграмма языков

Есть четыре парадигмы языков, которые часто путают и смешивают в кучу:
  • Императивная парадигма
  • Декларативная парадигма
  • Фунциональная парадигма
  • Объектно-ориентированная парадигма

Я решил нарисовать простую диаграмму, которая расставляет всё на свои места. Заранее предупреждаю, что это моё личное субъективное видение, которое не претендует на стопроцентную истину:


Таким образом, есть две прямо противоположные непересекающиеся парадигмы: императивная и декларативная. В императивных языках мы описываем решение задачи через последовательность инструкций, которые указывают машине что делать. Декларативная парадигма же не содержит никаких инструкций, а вместо них даёт некоторое описание, которое машина должна интерпретировать.
Пример чисто императивного языка  C, пример чисто декларативного языка  HTML.
Теперь самое интересное: объектно-ориентированная и функциональная парадигмы. Обе они не являются строгим подмножеством императивной и декларативной парадигм соответственно. Да, есть языки, которые объектно-ориентированны и при этом чисто императивны (Java, C++), и есть языки, которые функциональны и чисто декларативны (Agda).
Но в целом, ничто не запрещает функциональному языку быть императивным (Haskell), а объектно-ориентированному языку  декларативным (C# со своим LINQ).
А есть вообще языки, которые сразу поддерживают и императивную, и декларативную, и объектно-ориентированную, и функциональную парадигмы (Scala, Nemerle, OCaml).

понедельник, 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.

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

суббота, 27 сентября 2014 г.

Простая таблица языков программирования

Мне нравятся языки программирования, которые позволяют как можно больше ошибок отсеять на этапе компиляции. Статические языки в этом плане лучше динамических, а функциональные  лучше императивных (функциональный стиль более строгий). Таким образом, самые лучшие языки  это языки статические и функциональные:

Языки
Императивные
Функциональные
Динамические
JavaScript, Ruby, Python, PHP
Clojure, Erlang, Scheme
Статические
Java, C#, C++, Go
Scala, Haskell, OCaml

вторник, 15 октября 2013 г.

Мысли об ФП

ФП - это программирование без побочных эффектов. На этом определение ФП заканчивается. А лямбды, функции как объекты первого класса, композиция и прочие функциональные штуки - это всё второстепенно.

Вот вполне себе функциональный код на Java:

String reverseString(String str) {
    if (str.length() <= 1) {
        return str;
    } else {
        return
        str.charAt(str.length() - 1) +
        reverseString(str.substring(1, str.length() - 1)) +
        str.charAt(0);
    }
}

Побочных эффектов нету, соблюдается referential transparency.
Проблемы начинаются, когда код становится чуть сложнее, чем просто инвертирование строки. Попробуем написать функцию, которая инвертирует строки текста:

String reverseText(String text) {
    String[] lines = text.split("\n");
    String[] invertedLines = reverseLines(lines, 0, new String[0]);
    return Joiner.on("\n").join(invertedLines);
}

String[] reverseLines(String[] lines, int i, String[] invertedLines) {
    if (lines.length - i == 0) {
        return invertedLines;
    } else {
        return reverseLines(lines, i + 1,
            ObjectArrays.concat(invertedLines, reverseString(lines[i])));
    }
}

Справились, но пришлось добавить вспомогательный метод reverseLines. Естественным образом возникает мысль, что неплохо было бы его спрятать внутрь reverseText. И тогда можно не передавать lines в метод reverseLines, потому что она и так будет видна через замыкание. Таким образом, в функциональном языке мы должны уметь определять функции внутри функций.

Теперь надо написать функцию, которая не просто инвертирует строки текста, а, скажем инвертирует и ещё переводит их в верхний регистр. Очевидно, что функция будет отличаться от предыдущей одной лишь заменой reverseString на reverseStringAndToUpperCase. А дублировать код плохо. Тогда возникает мысль, а можно ли как-нибудь абстрагировать функцию reverseString от инвертирования? Назовём её transformTextа функцию reverseString  будем передавать как аргумент. Вот и возникло желание иметь функции как объекты первого класса.

Далее, наверное не сильно хочется писать функцию reverseStringAndToUpperCase? Ведь у нас уже есть reverseString и toUpperCase. Неплохо было бы уметь писать что-нибудь вроде reverseString.andThen(toUpperCase). Т.е. ещё возникает необходимость композиции функций в нашем языке.

Таким образом, писать без побочных эффектов - это трудная задача. А функциональные языки предоставляют инструменты, с помощью которых эту задачу можно значительно упростить.