пятница, 28 марта 2014 г.

Создание веб-приложений работающих в браузере на языке Kotlin

На днях я начал знакомиться с новым языком программирования под JVM - Kotlin, созданным Питерской компанией JetBrains. На первый взгляд язык очень интересный. Мои общие впечатления о нем можно прочитать по этой ссылке, а тут я лишь скажу что во многом он напоминает Scala. Одной из интересных возможностей этого языка является возможность создания на нем front-end web-приложений. Иными словами, кроме компиляции в байт-код виртуальной Java машины, Kotlin может компилироваться в JavaScript и исполняться в браузере. Конечно, для компиляции в браузере доступно некоторое подмножество его возможностей, по той простой причине, что архитектура виртуальных машин Java и JavaScript имеет принципиальные различия. Тем не менее, этот язык программирования показался мне довольно симпатичным и местами очень похожим на Scala, так что после короткого знакомства с документацией, мне захотелось попробовать эту его возможность создания приложений Kotlin to JavaScript.

Естественно предполагается что мы будем использовать IDE JetBrains IntelliJ IDEA. Тут есть короткое введение и небольшой Tutorial по созданию Kotlin to JavaScript проекта, от себя я к этому хочу добавить только то, что если вы, как и я, новичок в IDEA, то можете не разбираться сейчас с механизмом артефактов, а просто скопировать вручную файл kotlin.js в структуру вашего web-каталога. В моем случае все это выглядит следующим образом:


Для начала я предлагаю просто посмотреть на исходные файлы проекта, а лучше скопировать их и попробовать все это запустить. Ниже я дам некоторые пояснения по поводу того, что же там на самом деле происходит. Сперва нам понадобиться файл index.html, это наша страничка, к которой мы подключаем JavaScript(Kotlin) код. Её код выглядит следующим образом:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Hello Kotlin!</title>
<style>
.background-blue {
background: #5D9CEC;
}
.background-green {
background: #8CC152;
}
</style>
</head>
<body>
<h1>Hello Kotlin!</h1><br/>
<div id="container"></div><br/>
<div id="kotlinCallContainer"></div><br/>
<input id="btn" type="button" value="Hide blue container" />
<input id="btnNativeCall" type="button" value="Call Native Object JavaScript Object" />
<input id="btnJsCall" type="button" value="Call Kotlin From JavaScript" onclick="updKotlinCallContainer()" />
<script src="js/lib/jquery-1.10.1.min.js"></script>
<script src="js/lib/kotlin.js"></script>
<script src="js/lib/nativetest.js"></script>
<script src="js/app/HelloKotlin2Js.js"></script>
<script>
function updKotlinCallContainer() {
Kotlin.modules.HelloKotlin2Js.hello.callFromJs("Here is call Kotlin from JavaScript ;)");
}
</script>
</body>
</html>

Colored with dumpz.org


В принципе, тут кроме скрипта ничего необычного нет, а смысл того что находиться в тэге script станет ясен позже. Обратите внимание что тут используется библиотека JQuery и подключена она из локальной файловой системы, а на через CDN. Ниже я привожу коротенький исходный код нашего js-файла(nativetest.js) в котором представляет собой простой объект с одним полем и одним единственным методом:
function NativeJsObject(_text) {
this.text = "[" + _text + "]";
var that = this;
this.showAlert = function() {
alert(that.text);
}
}

Colored with dumpz.org


И наконец перейдем к самому интересному, а именно, исходному коду Kotlin:
/**
* Created by OZKA on 26.03.2014.
*/
package hello
import js.jquery.*
native class NativeJsObject(_test: Any?) {
fun showAlert() : Unit = js.noImpl
}
native public fun JQuery.hide() : JQuery = js.noImpl
fun callFromJs(text: Any?) {
val container = jq("#kotlinCallContainer")
container.addClass("background-green")
jq("#kotlinCallContainer").text(text.toString())
}
fun main(args: Array<String>) {
js.dom.html.window.document.write("<h3>Hello Kotlin to JavaScript!!!</h3>")
val container = jq("#container")
container.text("Text added by Kotlin/JQuery!")
container.addClass("background-blue")
jq("#btn").click { container.hide() }
jq("#btnNativeCall").click {
val njso = NativeJsObject("Native JavaScript object call from Kotlin!")
njso.showAlert()
}
}

Colored with dumpz.org


Для начала следует обратить внимание на функцию main, так же как в C++, C#, Java и Scala, в Kotlin эта функция является точкой входа в приложение. В случае же трансляции Kotlin в JavaScript, управление функции main передается после полной загрузки документа в браузере. В нашем примере, первая строка просто пишет в документ и работает так же, как это работает в JavaScript. Далее, можно обратить внимание что мы подключили JQuery(import js.jquery.*) и присваиваем переменной container ссылку на JQuery объект, содержащий DOM элемент "container" из нашего index.html файла. Тем кто знаком с JQuery должно быть все понятно.

Следует отметить, что стандартная библиотека Kotlin содержит в своем составе(Proxy-обертки) для работы с JQuery, но количество реализованных функций в ней сильно ограничено, впрочем как мы выясним позже, это все так же решаемо. Но что бы понять в чем именно смысл, давайте для начала разберем класс NativeJsObject. Этот класс имеет модификатор native, который в Kotlin to JavaScript используется для создания proxy-класса к существующему JavaScript объекту. js.noImpl просто вызывает существующую реализацию метода, написанную на JavaScript, причем обратите внимание, что нам не пришлось писать никакого дополнительного кода для конструктора JavaScript объекта. Все готово для того, что бы использовать из Kotlin, написанную на JavaScript логику. Тут мы можем сделать вывод что для большинства случаев, мы, без особых проблем, сможем использовать готовые JavaScript библиотеки и компоненты, и свободно комбинировать их с кодом, написанным на Kotlin!

Как я уже упомянул выше, Kotlin to JavaScript содержит пакет JQuery, но в нем реализованы только основные proxy-функции для работы с этой библиотекой. Впрочем, если мы посмотрим на функцию JQuery.hide(), то мы видим что это вовсе не проблема и при желании мы можем добавить недостающие функции JQuery самостоятельно. В данном случае используется механизм Kotlin, который называется Extension functions. Этот механизм похож на методы-расширения из C#, а так же неявные преобразования(Implicit conversions) в Scala.

И последняя функция, callFromJs, создана для того, что бы продемонстрировать вызов функционала написанного на Kotlin непосредственно из JavaScript. Для этого разберем тело функции updKotlinCallContainer, описанной в нашем файле index.html. Тут просто нужно обратить внимание на то, по какому имени мы ссылаемся на созданную компилятором Kotlin в JavaScript функцию. Естественно, если вы по-другому назвали проект и пакет, у меня пакет называется hello, имена пакетов и модуля, который по умолчанию совпадает с именем проекта(HelloKotlin2Js), то строку Kotlin.modules.HelloKotlin2Js.hello.callFromJs вам следует описать в соответствии с вашими именами.

Ну и хочется подытожить, что в Kotlin to JavaScript мы имеем не только возможность создавать приложения исполняющиеся в веб-браузере, но так же у нас есть полноценная двухсторонняя интеграция с кодом, написанным на JavaScript. Конечно, поскольку язык молодой, то какие-то детали и подходы, с выходом новых версий, могут измениться. Конечно немного не хватает идей, реализованных в GWT, когда мы в одном проекте имеем возможность писать и клиентский, и серверный код, а так же великолепную поддержку обмена данным между браузером и веб-контейнером(GWT-RPC). Но учитывая что за созданием Kotlin стоит компания JetBrains, которая известна в ИТ-мире своими замечательными продуктами для разработчиков, а так же что язык продолжает развиваться, я думаю у этого языка есть шанс занять пускай и небольшую, но свою нишу. Так что, тем кто интересуется современными языками программирования, гибко совмещающими в себе функциональный и объектно-ориентированный подходы, я очень рекомендую познакомиться с Kotlin. Особенно интересным знакомство с Kotlin может показаться программистом, имеющим опыт написания на Scala ;)

четверг, 27 марта 2014 г.

С праздником

С днем рождения мой технический блог! 27 марта 2008 года я сделал первую запись, поздравляю тебя, друг мой, с шестилетием! Тогда я думал что ты превратишься в самый классный русскоязычный блог по разработке в системе MS Dynamics AX. Я тогда и сам не знал, что работать с аксаптой мне останется год с небольшим, а самой axapta тут будут посвящены всего несколько записей, которые, по большому счету, кроме меня никому здесь и не нужны.

Я не знал что самым популярным поисковым запросом окажется пример подключения из Java к базе данных MySQL, я не знал что на долгие годы я останусь вечным Junior'ом, мечты которого работать с Java натолкнуться на суровую реальность, а именно что никто не спешит звать меня даже на поддержку legasy, видя мой сильно не джуниорский возраст и отсутствие высшего образования. Тогда я не знал что моя влюбленность в GWT/ExtGWT/GAE так же мало кому интересна.

Я продолжал верить и мечтать о прекрасном, искать...На этом пути мне встретились такие замечательные родственники Java, как Scala и Clojure, но видя что пишут хорошие программисты про эти языки, одна мысль о том что мне до них, как "до той самой луны", убивала не то что желание что-то про них писать, а даже продвинуться дальше чем банальный HelloWorld.

Иногда я забывал про тебя, но продолжал искать, знакомился с Dart и JavaScript, учился задавать вопросы на StackOverflow и Google Groups, но так и не мог ничего сказать тебе про мои находки. Мне стыдно, друг мой, что я так редко что-то мог тебе сказать. Стыдно что всякий раз, когда меня посещала мысль чем-то поделиться с людьми с помощью тебя, руки опускались, потому что людям ты был все еще нужен для подключения из Java к MySQL. Прости меня, а сейчас просто задуем вместе свечи и пожелаем друг другу, что может этот Hello World, написанный на отечественном языке Kotlin, как раз и станет тем, что я искал все эти долгие 6 лет. С днем рождения! И знай, я про тебя помню!

понедельник, 24 июня 2013 г.

Vaadin 7 и Scala

Все-таки интерес взял свое я и решил посмотреть на возможности, которые предлагает веб-фреймворк Vaadin 7, причем с использованием Scala в качестве языка программирования. На сайте создателей имеется вики-страница Scala and Vaadin HOWTO. По ней достаточно легко сконфигурировать тестовый проект, но есть один нюанс. Листинг приведенный в качестве тестового кода для запуска вашего "Hello World" соответствует 6-ой версии фреймворка, а, я думаю, для тех кто только начинает знакомится с этим продуктом имеет смысл сразу начинать не с предыдущей, а с текущий версии. Так вот, что бы ваше тестовое приложение корректно скомпилировалось и запустилось, вместо кода приведенного на wiki страничке используйте следующий код:
package com.example.vaadintest
import com.vaadin.ui._
import com.vaadin.server.VaadinRequest
class ScalaApp extends UI {
def init(request : VaadinRequest): Unit = {
val view = new VerticalLayout();
view.addComponent(new Label("Hello Vaadin ;)"));
setContent(view);
}
}

Colored with dumpz.org

Только обратите внимание на имя тестового класса и имя пакета, они у меня не совпадают с предложенным в вики продукта.

Приятного знакомства ;)

четверг, 9 августа 2012 г.

Сериализация Map'a в XML и обратно в X++

static void Map2XML2MapTests(Args _args)
{ 
    XMLDocument     xmlDoc;
    XmlElement      xmlElement;
 
 
    XmlElement      newXmlElement;
    Map             map = new Map(Types::Integer, Types::String);
    Map             mapFromXml;
    MapEnumerator   me;
    ;
 
    map.insert(1, "A");
    map.insert(2, "B");
    map.insert(3, "C");
    map.insert(4, "D");
    map.insert(5, "X");
 
 
    /*xmlDoc = XMLDocument::newXml(map.xml());
    info(xmlDoc.xml());
 
    xmlElement = xmlDoc.getNamedElement("Map");
    info(xmlElement.xml());
    */
    //global::createMapFromXML(xmlElement);
    mapFromXml = Map::createFromXML(XMLDocument::newXml(map.xml()).getNamedElement("Map"));
 
    me = mapFromXml.getEnumerator();
    info("Show map...");
    while(me && me.moveNext())
    {
        info(strfmt("%1->%2", me.currentKey(), me.currentValue()));
    }
}

понедельник, 15 ноября 2010 г.

Небольшая задача на списки в Scala

На одном форуме студент обратился с просьбой помочь в реализации задания на Java. Ссылку я приводить не буду, так как по ряду причин его вопрос до сих пор не закрыт, но сама задача показалась мне неплохим поводом немного попрактиковаться со списками в Scala. Звучит она следующим образом: имеется таблица с номерами строк и столбцов, ячейки таблицы содержат числа. Требуется на вход программе подать шаблон, например: "1-2-3-2", данный шаблон нужно разобрать и превратить в набор координат: (1-2), (2-3), (3-2). Для каждой координаты прочитать число из таблицы и рассчитать их сумму. В результате у меня получился следующий код:

object Appl {

val table = Map(1->Map(1->0, 2->5, 3->6, 4->7),
2->Map(1->5, 2->0, 3->8, 4->9),
3->Map(1->6, 2->8, 3->0, 4->4),
4->Map(1->7, 2->9, 3->4, 4->0))

val separator = "-"

def lookupValue(table: Map[Int, Map[Int, Int]], x: Int, y: Int) : Int = table.get(y).get(x)

def compute(path: String) : Int = {

try {
val list : List[Int] = path.split(separator).toList map {cur => Integer.parseInt(cur)}

list.zip(list.tail) map {cur =>  lookupValue(table, cur._1, cur._2)} reduceRight {_ + _}
}
catch {
case _ => return 0;
}
}

def main(args: Array[String]) = new Form(table).run

}


Если посмотреть на полученный код, то работает он следующим образом: берется строка-шаблон, превращается в список чисел в строковом формате, далее список превращается в числовой формат. Объединив полученный список с самим собой, за исключением первого элементы мы получаем список из пар "координат". Проходя по этому списку, каждая пара координат заменяется на соответствующее ей значение из таблицы, все это снова записывается в список, который в результате содержит значения и в завершении мы просто суммируем полученный набор чисел.

Разберем происходящее в методе compute пошагово.

Взять входную строку - шаблон, превратить ее в массив строк на основе указанного разделителя и преобразовать полученный массив в список: ... path.split(separator).toList

Над каждым элементом списка который, после предыдущего шага, представляет собой список чисел в строковом формате, провести преобразование строки в число: ... map {cur => Integer.parseInt(cur)}

Объединить полученный список с самим собой, исключив первый элемент: list.zip(list.tail)

Результатом предыдущей операции будет список пар координат(кортежей?), каждую пару мы заменяем на ее значение в таблице значений: ... map {cur => lookupValue(table, cur._1, cur._2)}

В завершении, полученный список значений последовательно суммируем: ... reduceRight {_ + _}

Все это завернуто в try/catch на случай подачи на вход неверных данных, которые алгоритм не сможет превратить в нужную для расчета структуру. В данном случае подробности ошибок не разбираются и в случае их возникновения мы просто возвращаем 0.

Итого, на мой субъективный взгляд, получился компактный и симпатичный код, правда он может показаться слега непривычным тем, кто только осваивает язык, как и я :)

Рабочий пример целиком тут

четверг, 21 октября 2010 г.

Декорация функций в Clojure

Данная заметка родилась по мотивам статьи "Ловушки для Clojure". Недавно, читая на sql.ru очередное обсуждение языков программирования, я наткнулся на интересную дискуссию о декораторах функций и методов. Предыдущая заметка про Scala появилась в результате попытки найти подобную функциональность в этом замечательном языке. Судя по собственным экспериментам, штатными средствами этого добиться нельзя, а поиск в Google отсылает к каким-то очень уж нетривиальным подходам, которые мой мозг одинэсника прикладного программиста отказывается воспринимать. В свою очередь тот эффект который я искал в Scala можно успешно проделать в Clojure. Ниже будет небольшой пример демонстрации с краткими пояснениями.

Предположим что нам нужно рассчитать зарплату трех наших сотрудников, например, программистов. Зарплата каждого из них складывается из ставки(оклада) и бонусной части. Ниже приведен код, который это делает.

(def developer_rate 60000.0)
(def tax_factor 0.87)

(defn calc_salary [rate bonus]
(+ rate bonus))


(def mike_salary (calc_salary developer_rate 2500.0))
(def vasya_salary (calc_salary developer_rate 3000.0))
(def alex_salary (calc_salary developer_rate 0.0))


Теперь в наш код необходимо добавить удержание налога в виде 13%. В принципе мы могли бы сразу написать функцию расчета зарплаты следующим образом:

(defn calc_salary [rate bonus] 
(* (+ rate bonus) tax_factor))


Однако есть одна незадача. Допустим одному из наших программистов довелось работать в чудо-граде Сколково и вычитать с него налог нам не требуется :) Перепишем наш механизм расчета используя "хуки" библиотеки, ссылка на которую есть в начале заметки. Теперь наш код будет выглядеть следующим образом:

(def developer_rate 60000.0)
(def tax_factor 0.87)

(defn calc_salary [rate bonus]
(+ rate bonus))

;;(defn calc_salary [rate bonus]
;; (* (+ rate bonus) tax_factor))

(defn taxation [func rate bonus]
(* (func rate bonus) tax_factor))

(add-hook :around #'calc_salary :taxation taxation)

(def mike_salary (calc_salary developer_rate 2500.0))
(def vasya_salary (calc_salary developer_rate 3000.0))

(remove-hook :around #'calc_salary :taxation)

(def alex_salary (calc_salary developer_rate 0.0))


Функция add-hook добавила "перехватчик вызова функции" calc_salary, а перед расчетом нашего счастливого сотрудника из Сколково, мы удалили данный "хук" и не стали вычитать из него налоги. Подробное описание, опять же, смотреть по ссылке на статью в начале этого поста. А ниже еще пара возможностей добавлять функциям поведение на лету.

Напишем функцию которая печатает ставку программиста, а затем дополним эту функцию выводом еще пары значений, налогового коэффициента и суммы к выдачи после вычета налога:

(defn print_rate [rate]
(println (str "Rate: " rate)))

(defn print_tax_factor [dummy_rate]
(println (str "Tax factor: " tax_factor)))

(defn print_rate_excl_tax [rate]
(println (str "NET value: " (* rate tax_factor))))

(add-hook :before #'print_rate :print_tax_factor print_tax_factor)
(add-hook :after #'print_rate :print_rate_excl_tax print_rate_excl_tax)


Теперь мы можем вызвать в REPL например следующий код (print_rate 10000) и проверить результат.

В заключение. Естественно такие расчеты можно выполнить массой других способов, все что выше всего лишь простое пояснение возможности изменять реализацию функций в Clojure на лету. На всякий случай хочу сказать, что в Clojure я по пятибалльной шкале разбираюсь на уровне 0 с каким-то минимальным значением после запятой, в общем учусь, так что если сильно что напутал/наврал, приношу свои извинения. Код целиком на gitgist.

понедельник, 18 октября 2010 г.

Неявные преобразования типов в Scala

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

package implicitconversions


case class Human(val name: String, val salary: Double) {
def greeting = println("Hello, my name is " + name)
}


class Worker(val h : Human) {
def doJob     = println(h.name + " do his job")
def showSalary = println (h.name + " salary is " + h.salary)
}

class Pastime() {
def watchTV = println("Watching TV")
def walk = println("Walking...")
}


object JobEnviroment {

implicit def human2Worker(h: Human) = new Worker(h)

def in(h: Human) {
h.doJob
h.showSalary
}

}

object HomeEnviroment {

implicit def human2Pastime(h: Human) = new Pastime()

def in(h: Human) {
h.watchTV
h.walk
}
}

object DemoAppl {

def complexDemo = {
import HomeEnviroment._
import JobEnviroment._

val h = new Human("Pete", 40000)

h.greeting
h.doJob
h.showSalary
h.watchTV
h.walk
}

def errorDemo {

val h = new Human("Steve", 15000)

h.greeting
//Error -->

//h.doJob
//h.showSalary
//h.watchTV
//h.walk

// <--
}

def main(args: Array[String]) : Unit = {
println("Scala Implicit Conversions")

val h = new Human("Vasya", 25000)
h.greeting

JobEnviroment.in(h)
HomeEnviroment.in(h)

complexDemo

}
}


Как видно из примера выше, конструкция implicit def указывает Scala компилятору, что в данном случае класс Human приобретает новые методы: класса Worker в контексте объекта JobEnviroment и Pastime в HomeEnviroment. В методе complexDemo мы импортируем оба определения неявных преобразований и можем выполнить все дополнительные методы одновременно.

Данная возможность представляет собой аналоги расширяющих методов языка C#, а так же напоминает облегченную версию прототипов "JavaScript", впрочем думаю что злоупотреблять ее использованием наверное не стоит, так как это может сильно запутать код.