Spec-Zone.ru › Kotlin 1.6

Что нового в Kotlin 1.1

Дата выхода: 15 февраля 2016 года

Содержание

  • Корутины

  • Другие языковые возможности

  • Стандартная библиотека

  • JVM-бекенд

  • JavaScript-бекенд

JavaScript

Начиная с Kotlin 1.1, целевая платформа JavaScript больше не считается экспериментальной. Поддерживаются все языковые возможности, и есть множество новых инструментов для интеграции с средой разработки фронтенда. Более подробный список изменений см. ниже.

Корутины (экспериментальная функция)

Ключевым новым свойством в Kotlin 1.1 являются корутины, обеспечивающие поддержку async/await, yield, и аналогичных паттернов программирования. Главное свойство дизайна Kotlin заключается в том, что реализация выполнения корутин является частью библиотек, а не языка, поэтому вы не привязаны к какому-либо конкретному парадигме программирования или библиотеке для конкурентного программирования.

Корутина — это, по сути, лёгкий поток, который может быть приостановлен и возобновлён позднее. Корутины поддерживаются с помощью функций приостановки: вызов такой функции может потенциально приостановить корутину, и для запуска новой корутины мы обычно используем анонимные функции приостановки (т. е. лямбда-функции приостановки).

Давайте посмотрим на async/await, которая реализована во внешней библиотеке kotlinx.coroutines:

// runs the code in the background thread pool
fun asyncOverlay() = async(CommonPool) {
    // start two async operations
    val original = asyncLoadImage("original")
    val overlay = asyncLoadImage("overlay")
    // and then apply overlay to both results
    applyOverlay(original.await(), overlay.await())
}

// launches new coroutine in UI context
launch(UI) {
    // wait for async overlay to complete
    val image = asyncOverlay().await()
    // and then show it in UI
    showImage(image)
}

Здесь, async { ... } запускает корутину, и когда мы используем await(), выполнение корутины приостанавливается, пока выполняется ожидаемая операция, и возобновляется (возможно, в другом потоке) после завершения ожидаемой операции.

Стандартная библиотека использует корутины для поддержки ленивых генерируемых последовательностей с функциями yield и yieldAll. В такой последовательности блок кода, возвращающий элементы последовательности, приостанавливается после получения каждого элемента и возобновляется при запросе следующего элемента. Вот пример:

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val seq = buildSequence {
      for (i in 1..5) {
          // yield a square of i
          yield(i * i)
      }
      // yield a range
      yieldAll(26..28)
    }

    // print the sequence
    println(seq.toList())
}

Запустите код выше, чтобы увидеть результат. Не стесняйтесь редактировать его и снова запускать!

Дополнительную информацию можно найти в документации по корутинам и учебнике.

Обратите внимание, что корутины в настоящее время считаются экспериментальной функцией, что означает, что команда Kotlin не гарантирует обратной совместимости этой функции после финального выпуска 1.1.

Другие возможности языка

Псевдонимы типов

Псевдоним типа позволяет определить альтернативное имя для существующего типа. Это наиболее полезно для общих типов, таких как коллекции, а также для типов функций. Вот пример:

//sampleStart
typealias OscarWinners = Map<String, String>

fun countLaLaLand(oscarWinners: OscarWinners) =
        oscarWinners.count { it.value.contains("La La Land") }

// Note that the type names (initial and the type alias) are interchangeable:
fun checkLaLaLandIsTheBestMovie(oscarWinners: Map<String, String>) =
        oscarWinners["Best picture"] == "La La Land"
//sampleEnd

fun oscarWinners(): OscarWinners {
    return mapOf(
            "Best song" to "City of Stars (La La Land)",
            "Best actress" to "Emma Stone (La La Land)",
            "Best picture" to "Moonlight" /* ... */)
}

fun main(args: Array<String>) {
    val oscarWinners = oscarWinners()

    val laLaLandAwards = countLaLaLand(oscarWinners)
    println("LaLaLandAwards = $laLaLandAwards (in our small example), but actually it's 6.")

    val laLaLandIsTheBestMovie = checkLaLaLandIsTheBestMovie(oscarWinners)
    println("LaLaLandIsTheBestMovie = $laLaLandIsTheBestMovie")
}

См. документацию по псевдонимам типов и KEEP для получения более подробной информации.

Связанные вызываемые ссылки

Теперь можно использовать оператор :: для получения ссылки на член, указывающей на метод или свойство конкретного экземпляра объекта. Ранее это можно было выразить только с помощью лямбды. Вот пример:

//sampleStart
val numberRegex = "\\d+".toRegex()
val numbers = listOf("abc", "123", "456").filter(numberRegex::matches)
//sampleEnd

fun main(args: Array<String>) {
    println("Result is $numbers")
}

Прочитайте документацию и KEEP для получения более подробной информации.

Запечатанные и данные классы

Kotlin 1.1 удаляет некоторые ограничения на запечатанные и данные классы, которые были в Kotlin 1.0. Теперь вы можете определять подклассы верхнего уровня запечатанного класса на верхнем уровне в одном файле, а не только как вложенные классы запечатанного класса. Данные классы теперь могут расширять другие классы. Это можно использовать для определения иерархии классов выражений красиво и чисто:

//sampleStart
sealed class Expr

data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun eval(expr: Expr): Double = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}
val e = eval(Sum(Const(1.0), Const(2.0)))
//sampleEnd

fun main(args: Array<String>) {
    println("e is $e") // 3.0
}

Прочитайте документацию по запечатанным классам или KEEPs для запечатанного класса и данного класса для получения более подробной информации.

Деструктуризация в лямбдах

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

fun main(args: Array<String>) {
//sampleStart
    val map = mapOf(1 to "one", 2 to "two")
    // before
    println(map.mapValues { entry ->
      val (key, value) = entry
      "$key -> $value!"
    })
    // now
    println(map.mapValues { (key, value) -> "$key -> $value!" })
//sampleEnd
}

Прочитайте документацию по деструктурирующим объявлениям и KEEP для получения более подробной информации.

Подчеркивания для неиспользуемых параметров

Для лямбды с несколькими параметрами можно использовать символ _ для замены имен параметров, которые не используются:

fun main(args: Array<String>) {
    val map = mapOf(1 to "one", 2 to "two")

//sampleStart
    map.forEach { _, value -> println("$value!") }
//sampleEnd
}

Это также работает в деструктурирующих объявлениях:

data class Result(val value: Any, val status: String)

fun getResult() = Result(42, "ok").also { println("getResult() returns $it") }

fun main(args: Array<String>) {
//sampleStart
    val (_, status) = getResult()
//sampleEnd
    println("status is '$status'")
}

Прочитайте KEEP для получения более подробной информации.

Подчеркивания в числовых литералах

Так же, как и в Java 8, Kotlin теперь позволяет использовать подчеркивания в числовых литералах для разделения групп цифр:

//sampleStart
val oneMillion = 1_000_000
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
//sampleEnd

fun main(args: Array<String>) {
    println(oneMillion)
    println(hexBytes.toString(16))
    println(bytes.toString(2))
}

Прочитайте KEEP для получения более подробной информации.

Укороченный синтаксис для свойств

Для свойств с определенным как выражение телом, тип свойства теперь можно опустить:

//sampleStart
    data class Person(val name: String, val age: Int) {
    val isAdult get() = age >= 20 // Property type inferred to be 'Boolean'
}
//sampleEnd
fun main(args: Array<String>) {
    val akari = Person("Akari", 26)
    println("$akari.isAdult = ${akari.isAdult}")
}

Встраиваемые аксессоры свойств

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

//sampleStart
public val <T> List<T>.lastIndex: Int
    inline get() = this.size - 1
//sampleEnd

fun main(args: Array<String>) {
    val list = listOf('a', 'b')
    // the getter will be inlined
    println("Last index of $list is ${list.lastIndex}")
}

Также можно пометить само свойство как inline — тогда модификатор применяется к обоим аксессорам.

Прочитайте документацию по встраиваемым функциям и KEEP для получения более подробной информации.

Локальные делегированные свойства

Теперь вы можете использовать синтаксис делегированного свойства с локальными переменными. Одно из возможных применений — определение лениво вычисляемой локальной переменной:

import java.util.Random

fun needAnswer() = Random().nextBoolean()

fun main(args: Array<String>) {
//sampleStart
    val answer by lazy {
        println("Calculating the answer...")
        42
    }
    if (needAnswer()) {                     // returns the random value
        println("The answer is $answer.")   // answer is calculated at this point
    }
    else {
        println("Sometimes no answer is the answer...")
    }
//sampleEnd
}

Прочитайте KEEP для получения более подробной информации.

Перехват привязки делегированного свойства

Для делегированных свойств теперь возможно перехватить привязку делегата к свойству с помощью оператора provideDelegate. Например, если мы хотим проверить имя свойства перед привязкой, мы можем написать что-то вроде этого:

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        ... // property creation
    }

    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}

fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }

class MyUI {
    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

Метод provideDelegate будет вызываться для каждого свойства во время создания экземпляра MyUI, и он может выполнить необходимую валидацию сразу.

Прочитайте документацию по делегированным свойствам для получения более подробной информации.

Общие методы доступа к значениям перечисления

Теперь возможно перечислить значения класса перечисления общим способом.

//sampleStart
enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumValues<T>().joinToString { it.name })
}
//sampleEnd

fun main(args: Array<String>) {
    printAllValues<RGB>() // prints RED, GREEN, BLUE
}

Управление областью действия неявных получателей в DSL

Аннотация @DslMarker позволяет ограничить использование получателей из внешних областей действия в контексте DSL. Рассмотрим канонический пример HTML-билдера:

table {
    tr {
        td { + "Text" }
    }
}

В Kotlin 1.0 код в лямбде, переданной в td, имеет доступ к трем неявным получателям: к тому, который передан в table, к tr и к td. Это позволяет вызывать методы, которые не имеют смысла в контексте — например, вызывать tr внутри td и тем самым помещать тег <tr> в <td>.

В Kotlin 1.1 вы можете ограничить это, чтобы внутри лямбды, переданной в td, были доступны только методы, определенные для неявного получателя td. Это делается путем определения аннотации, помеченной мета-аннотацией @DslMarker, и применением ее к базовому классу классов тегов.

Прочитайте документацию по безопасным билдерам типов и KEEP для получения более подробной информации.

Оператор rem

Оператор mod теперь устарел, и вместо него используется rem. См. эту проблему для мотивации.

Стандартная библиотека

Преобразования строк в числа

Добавлены новые расширения для класса String, позволяющие преобразовывать его в число без исключений при некорректном формате: String.toIntOrNull(): Int?, String.toDoubleOrNull(): Double? и т. д.

val port = System.getenv("PORT")?.toIntOrNull() ?: 80

Также функции преобразования в целые числа, такие как Int.toString(), String.toInt(), String.toIntOrNull(), получили перегрузки с параметром radix, позволяющим указать основание системы счисления (от 2 до 36).

onEach()

onEach — это небольшое, но полезное расширение для коллекций и последовательностей, которое позволяет выполнить некоторое действие, возможно с побочными эффектами, над каждым элементом коллекции/последовательности в цепочке операций. Для итерируемых объектов оно ведет себя как forEach, но также возвращает экземпляр итерируемого объекта. Для последовательностей возвращается обертывающая последовательность, которая применяет заданное действие лениво по мере итерирования элементов.

inputDir.walk()
        .filter { it.isFile && it.name.endsWith(".txt") }
        .onEach { println("Moving $it to $outputDir") }
        .forEach { moveFile(it, File(outputDir, it.toRelativeString(inputDir))) }

also(), takeIf(), и takeUnless()

Эти три функции общего назначения применимы к любому получателю.

also подобна apply: она принимает получатель, выполняет над ним некоторое действие и возвращает этот получатель. Разница заключается в том, что в блоке внутри apply получатель доступен как this, а в блоке внутри also — как it (и вы можете дать ему другое имя, если хотите). Это бывает удобно, когда вы не хотите затенять this из внешней области видимости:

class Block {
    lateinit var content: String
}

//sampleStart
fun Block.copy() = Block().also {
    it.content = this.content
}
//sampleEnd

// using 'apply' instead
fun Block.copy1() = Block().apply {
    this.content = this@copy1.content
}

fun main(args: Array<String>) {
    val block = Block().apply { content = "content" }
    val copy = block.copy()
    println("Testing the content was copied:")
    println(block.content == copy.content)
}

takeIf подобна filter для одного значения. Она проверяет, удовлетворяет ли получатель предикату, и возвращает получатель, если удовлетворяет, или null, если не удовлетворяет. В сочетании с оператором эльвис (?:) и ранними возвратами это позволяет записывать конструкции, такие как:

val outDirFile = File(outputDir.path).takeIf { it.exists() } ?: return false
// do something with existing outDirFile
fun main(args: Array<String>) {
    val input = "Kotlin"
    val keyword = "in"

//sampleStart
    val index = input.indexOf(keyword).takeIf { it >= 0 } ?: error("keyword not found")
    // do something with index of keyword in input string, given that it's found
//sampleEnd
    
    println("'$keyword' was found in '$input'")
    println(input)
    println(" ".repeat(index) + "^")
}

takeUnless — это то же самое, что и takeIf, но она принимает инвертированный предикат. Она возвращает получатель, когда он не удовлетворяет предикату, и null в противном случае. Так, один из приведенных выше примеров можно переписать с использованием takeUnless следующим образом:

val index = input.indexOf(keyword).takeUnless { it < 0 } ?: error("keyword not found")

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

private fun testTakeUnless(string: String) {
//sampleStart
    val result = string.takeUnless(String::isEmpty)
//sampleEnd

    println("string = \"$string\"; result = \"$result\"")
}

fun main(args: Array<String>) {
    testTakeUnless("")
    testTakeUnless("abc")
}

groupingBy()

Этот API можно использовать для группирования коллекции по ключу и одновременного сворачивания каждой группы. Например, его можно использовать для подсчета количества слов, начинающихся с каждой буквы:

fun main(args: Array<String>) {
    val words = "one two three four five six seven eight nine ten".split(' ')
//sampleStart
    val frequencies = words.groupingBy { it.first() }.eachCount()
//sampleEnd
    println("Counting first letters: $frequencies.")

    // The alternative way that uses 'groupBy' and 'mapValues' creates an intermediate map, 
    // while 'groupingBy' way counts on the fly.
    val groupBy = words.groupBy { it.first() }.mapValues { (_, list) -> list.size }
    println("Comparing the result with using 'groupBy': ${groupBy == frequencies}.")
}

Map.toMap() и Map.toMutableMap()

Эти функции можно использовать для удобного копирования карт:

class ImmutablePropertyBag(map: Map<String, Any>) {
    private val mapCopy = map.toMap()
}

Map.minus(key)

Оператор plus предоставляет способ добавления пары(пар) ключ-значение в неизменяемую карту, создавая новую карту, однако не было простого способа сделать обратное: для удаления ключа из карты приходилось прибегать к менее прямым методам, таким как Map.filter() или Map.filterKeys(). Теперь оператор minus заполняет этот пробел. Доступно 4 перегрузки: для удаления одного ключа, коллекции ключей, последовательности ключей и массива ключей.

fun main(args: Array<String>) {
//sampleStart
    val map = mapOf("key" to 42)
    val emptyMap = map - "key"
//sampleEnd
    
    println("map: $map")
    println("emptyMap: $emptyMap")
}

minOf() и maxOf()

Эти функции можно использовать для поиска наименьшего и наибольшего из двух или трех заданных значений, где значения являются примитивными числами или Comparable объектами. Также есть перегрузка каждой функции, которая принимает дополнительный Comparator экземпляр, если вы хотите сравнивать объекты, которые сами по себе не сравнимы.

fun main(args: Array<String>) {
//sampleStart
    val list1 = listOf("a", "b")
    val list2 = listOf("x", "y", "z")
    val minSize = minOf(list1.size, list2.size)
    val longestList = maxOf(list1, list2, compareBy { it.size })
//sampleEnd
    
    println("minSize = $minSize")
    println("longestList = $longestList")
}

Функции создания списков, похожих на массивы

Аналогично конструктору Array, теперь есть функции, которые создают List и MutableList экземпляры и инициализируют каждый элемент, вызвав лямбда-выражение:

fun main(args: Array<String>) {
//sampleStart
    val squares = List(10) { index -> index * index }
    val mutable = MutableList(10) { 0 }
//sampleEnd

    println("squares: $squares")
    println("mutable: $mutable")
}

Map.getValue()

Это расширение для Map возвращает существующее значение, соответствующее заданному ключу, или выбрасывает исключение, упоминая, какой ключ не найден. Если карта была создана с использованием withDefault, эта функция вернет значение по умолчанию вместо выброса исключения.

fun main(args: Array<String>) {
//sampleStart    
    val map = mapOf("key" to 42)
    // returns non-nullable Int value 42
    val value: Int = map.getValue("key")

    val mapWithDefault = map.withDefault { k -> k.length }
    // returns 4
    val value2 = mapWithDefault.getValue("key2")

    // map.getValue("anotherKey") // <- this will throw NoSuchElementException
//sampleEnd
    
    println("value is $value")
    println("value2 is $value2")
}

Абстрактные коллекции

Эти абстрактные классы могут использоваться в качестве базовых классов при реализации классов коллекций Kotlin. Для реализации неизменяемых коллекций существуют AbstractCollection, AbstractList, AbstractSet и AbstractMap, а для изменяемых коллекций — AbstractMutableCollection, AbstractMutableList, AbstractMutableSet и AbstractMutableMap. На JVM эти абстрактные изменяемые коллекции унаследуют большую часть своей функциональности от абстрактных коллекций JDK.

Функции манипулирования массивами

Стандартная библиотека теперь предоставляет набор функций для элементных операций над массивами: сравнение (contentEquals и contentDeepEquals), вычисление хэш-кодов (contentHashCode и contentDeepHashCode) и преобразование в строку (contentToString и contentDeepToString). Они поддерживаются как для JVM (где они действуют как алиасы для соответствующих функций в java.util.Arrays), так и для JS (где реализация предоставлена в стандартной библиотеке Kotlin).

fun main(args: Array<String>) {
//sampleStart
    val array = arrayOf("a", "b", "c")
    println(array.toString())  // JVM implementation: type-and-hash gibberish
    println(array.contentToString())  // nicely formatted as list
//sampleEnd
}

JVM-бекенд

Поддержка байткода Java 8

Kotlin теперь имеет возможность генерировать байткод Java 8 (опция командной строки -jvm-target 1.8 или соответствующие опции в Ant/Maven/Gradle). Пока это не меняет семантику байткода (в частности, методы по умолчанию в интерфейсах и лямбда-выражения генерируются точно так же, как в Kotlin 1.0), но мы планируем в дальнейшем использовать это.

Поддержка стандартной библиотеки Java 8

Теперь существуют отдельные версии стандартной библиотеки, поддерживающие новые API JDK, добавленные в Java 7 и 8. Если вам нужен доступ к новым API, используйте артефакты Maven kotlin-stdlib-jre7 и kotlin-stdlib-jre8 вместо стандартного kotlin-stdlib. Эти артефакты представляют собой крошечные расширения на основе kotlin-stdlib, которые подключаются в ваш проект в качестве транзитивной зависимости.

Имена параметров в байткоде

Kotlin теперь поддерживает хранение имен параметров в байткоде. Это можно включить с помощью опции командной строки -java-parameters.

Константная инлайнизация

Компилятор теперь инлайнит значения const val свойств в места их использования.

Изменяемые переменные замыканий

Классы-обёртки, используемые для захвата изменяемых переменных замыканий в лямбда-выражениях, больше не имеют переменных volatile. Это изменение улучшает производительность, но может привести к новым проблемам гонок в некоторых редких сценариях использования. Если вы затронуты этим, вам необходимо обеспечить собственную синхронизацию для доступа к переменным.

Поддержка javax.script

Kotlin теперь интегрируется с API javax.script (JSR-223). API позволяет оценивать фрагменты кода во время выполнения:

val engine = ScriptEngineManager().getEngineByExtension("kts")!!
engine.eval("val x = 3")
println(engine.eval("x + 2"))  // Prints out 5

См. здесь для более крупного примера проекта, использующего API.

kotlin.reflect.full

Для подготовки к поддержке Java 9, функции и свойства расширения в библиотеке kotlin-reflect.jar были перемещены в пакет kotlin.reflect.full. Имена в старом пакете (kotlin.reflect) устарели и будут удалены в Kotlin 1.2. Обратите внимание, что базовые интерфейсы рефлексии (например, KClass) являются частью стандартной библиотеки Kotlin, а не kotlin-reflect, и не затрагиваются этим перемещением.

END_OF_DOCUMENT_MARKER

JavaScript бэкенд

Единая стандартная библиотека

Теперь значительно большая часть стандартной библиотеки Kotlin может использоваться из кода, скомпилированного в JavaScript. В частности, ключевые классы, такие как коллекции (ArrayList, HashMap и т. д.), исключения (IllegalArgumentException и т. д.) и некоторые другие (StringBuilder, Comparator) теперь определены в пакете kotlin. В JVM они являются псевдонимами типов соответствующих классов JDK, а в JS — классами, реализованными в стандартной библиотеке Kotlin.

Улучшенная генерация кода

JavaScript бэкенд теперь генерирует код, который легче поддаётся статической проверке, что более благоприятно для инструментов обработки JS-кода, таких как минификаторы, оптимизаторы, линтеры и т. д.

Модификатор external

Если вам нужно получить доступ к классу, реализованному в JavaScript, из Kotlin безопасным способом с использованием типов, вы можете написать объявление Kotlin с использованием модификатора external. (В Kotlin 1.0 вместо этого использовалась аннотация @native). В отличие от JVM-цели, JS-цель позволяет использовать модификатор external с классами и свойствами. Например, вот как вы можете объявить класс DOM Node:

external class Node {
    val firstChild: Node

    fun appendChild(child: Node): Node

    fun removeChild(child: Node): Node

    // etc
}

Улучшенная обработка импорта

Теперь вы можете более точно описать объявления, которые должны быть импортированы из JavaScript-модулей. Если вы добавите аннотацию @JsModule("<module-name>") к внешнему объявлению, оно будет правильно импортировано в систему модулей (CommonJS или AMD) во время компиляции. Например, в CommonJS объявление будет импортировано с помощью функции require(...). Кроме того, если вы хотите импортировать объявление как модуль или как глобальный JavaScript-объект, вы можете использовать аннотацию @JsNonModule.

Например, вот как можно импортировать JQuery в Kotlin-модуль:

external interface JQuery {
    fun toggle(duration: Int = definedExternally): JQuery
    fun click(handler: (Event) -> Unit): JQuery
}

@JsModule("jquery")
@JsNonModule
@JsName("$")
external fun jquery(selector: String): JQuery

В этом случае JQuery будет импортирован как модуль с именем jquery. В качестве альтернативы, он может быть использован как объект $-object, в зависимости от того, какую систему модулей конфигурирован использовать Kotlin-компилятор.

Вы можете использовать эти объявления в своём приложении следующим образом:

fun main(args: Array<String>) {
    jquery(".toggle-button").click {
        jquery(".toggle-panel").toggle(300)
    }
}
Последнее изменение: 07 апреля 2022
Что нового в Kotlin 1.2 Основные синтаксические конструкции

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew11.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API