Description: Following up on the previous article, today we are going to talk about some of the funky things in Kotlin 1.3. So, today we are going to start the second article. I have read some articles about the new features of Kotlin 1.3, which are basically translated from the official Blog of Kotlin Blog. I’m not going to do that today, but since today’s topic is fashion let’s do something interesting. Just like JetBrains developer day evangelist Hali didn’t use powerpoint at all when talking about new features in Kotlin1.3, just pick up the code. Let’s take a look at today’s outline:

Coroutine, Kotlin/Native 1.0Beta

1. Basic introduction of Coroutine

Coroutines involve a lot of content, which can not be explained clearly in one sentence. Due to the length of the article, I will not expand it here, and there will be a special series of articles to discuss it in depth. This is just a basic introduction.

  • Coroutines positive

The most important thing about Kotlin 1.3 is the conversion of Coroutine. Coroutine has been in the official library for a long time, but the API is always Experimental. If you have played with Coroutine before, there is still a little change in the API compared to the official version of Coroutine. Coroutines are essentially lightweight threads that can execute multiple coroutines in one thread.

  • Coroutines role

The best thing about coroutines for developers is that they solve the problem of callback hell by writing asynchronous tasks as synchronous calls at the code level. It does not block the current thread. For Android developers, RxJava is a natural solution to the multi-request callback problem. RxJava does alleviate the callback hell problem to some extent, but it still doesn’t get rid of the fact that callbacks are used. So coroutines will take you to a whole new way to solve this problem. In Android development, Kotlin’s Coroutine can basically replace RxJava. And at this conference, the head of Google China’s technical team gave a speech about the use of Corouine in Android. It also says that the Retrofit library will soon support the suspend function in coroutines. In addition, Jake Wharton wrote Retrofit to support adapter Coroutine retrofit2-Kotlin-Coroutines-Adapter. For new Android projects using Kotlin, try Corouine instead of RxJava. You might find it really cool. If you don’t believe me, take a look at an example of coroutines in Android (pseudocode):

suspend fun updateData(showId: Long) = coroutineScope {
    val localCacheDeferred = async {// Start an asynchronous task to process local time-consuming IO
        localDataStore.getShow(showId)
    }
    val remoteDataDeferred1 = async {// Start an asynchronous task network request 1
        remoteSource1.getShow(showId)
    }
    val remoteDataDeferred2 = async {// Initiate an asynchronous task network request 2
        remoteSource2.getShow(showId)
    }

    val localResult = localCacheDeferred.await()// Calling the await function suspends waiting until the result of executing the task is returned. There is no need to pass in a callback to wait for the result to return.
    val remoteResult1 = remoteDataDeferred1.await()
    val remoteResult2 = remoteDataDeferred2.await()
    val mergedResult = handleMerged(localResult, remoteResult1, remoteResult2)
    localDataStore.saveShow(mergedResult)
}
Copy the code

You can see that there are three asynchronous tasks involved, but you just need to use the await function to suspend and wait for the result to return. No callback is needed at all. You can see that the whole code looks synchronous and actually contains three asynchronous tasks. You can see that coroutines are actually quite fun to use.

  • 3. Solve other examples of callback hell

It’s not just Kotlin who has examples of this, but if you know anything about JavaScript, JavaScript works like this, Older VERSIONS of JS typically perform asynchronous tasks using Promise or Ajax, and an asynchronous execution can always be followed by a callback. Callback nesting (callback hell) occurs when a slightly more complex page scenario is involved if you’re familiar with the new syntax features of ES6. New async and await functions have been added to Es6 syntax, which is also to write asynchronous tasks as synchronous calls, solving the problem of callback hell in JS.

  • Coroutines in other languages

Coroutines are not just for Kotlin, they were there long before they came out, things like coroutines in Python and fibers in Window programming, which are actually similar to coroutines. Remember some time ago I read a foreign article that Java is also working on a coroutine like lightweight thread framework project (Loom), the specific is still in the experimental stage. If you are interested in Oracle, you can read about it.

1.0 Beta 2, Kotlin/Native

I won’t cover Kotlin/Native in detail here. If you are interested, check out my previous JetBrains Developer Daily News (1) Kotlin/Native blog post, which has a lot of details about Kotlin/Native.

Experimental Contract

See the interesting title of the contract, which is really interesting and a very useful grammar point. Kotlin version 1.3 has an interesting syntactic feature Contract Contract, but cotract is still Experimental. There may be changes in the API later. It is recommended to play with it in advance, but do not introduce it into production environment.

Why do we need a Contract

We all know that there is a nice feature in Kotlin called smart cast. I wonder if you have encountered such a situation when using Kotlin development. Sometimes the intelligent derivation can be correctly identified, but sometimes it fails. I don’t know if you’ve studied this problem in depth, but here’s an example of how to reproduce that scenario. Let’s take a look:

  • Case a
// Define a class in Java that generates a token function that may return NULL
package com.mikyou.news;

import org.jetbrains.annotations.Nullable;

public class TokenGenerator {
    public @Nullable String generateToken(String type) {
        return type.isEmpty() ? null : "2s4dfhj8aeddfduvcqopdflfgjfgfgj"; }}Copy the code
// Call this function in Kotlin and generate a nullable String receiver
import com.mikyou.news.TokenGenerator

fun main(args: Array<String>) {
    val token: String? = TokenGenerator().generateToken("kotlin")
    if(token ! =null && token.isNotBlank()) {// Call short
        println("token length is ${token.length}")// token.length compiles correctly and a smart cast is performed}}Copy the code

Hover your mouse over the token in token.length and the compiler will tell you that it has been processed by Smart Cast

  • In case 2, you suddenly want to put the nulling-handling check in a checkTokenIsValid function, which makes perfect sense in terms of code responsibility and readability. But if you do, something amazing happens.
fun main(args: Array<String>) {
    val token: String? = TokenGenerator().generateToken("kotlin")
    if (checkTokenIsValid(token)) {// The null processing is left to the function to handle, according to the function return value to determine
        println("token length is ${token.length}")// Compiler exception: the token is nullable and needs to be nulled. This is not very depressed}}fun checkTokenIsValid(token: String?).: Boolean{
    returntoken ! =null && token.isNotBlank()
}
Copy the code

You will find a token. The length that error prompts you to empty processing, as shown in the figure below, is a face of meng force, in fact you have done to empty processing in function. But the compiler says that token is nullable as far as it knows in scope, so it must be nullable. At this point you will feel where the intelligence?

Encounter the above scene, I believe that a lot of people are not using!! To sort it out.

/ / use!!!!! To solve the problem
fun main(args: Array<String>) {
    val token: String? = TokenGenerator().generateToken("kotlin")
    if (checkTokenIsValid(token)) {// The null processing is left to the function to handle, according to the function return value to determine
        println("token length is ${token!! .length}")// Compile properly: use!! Force the compiler to be told that null is not present}}fun checkTokenIsValid(token: String?).: Boolean{
    returntoken ! =null && token.isNotBlank()
}
Copy the code
  • Case 3: Use the official built-in judgment function isNullOrBlank

Use Kotlin’s built-in functions and you’ll be even more confused…

fun main(args: Array<String>) {
    val token: String? = TokenGenerator().generateToken("kotlin")
    if(! token.isNullOrBlank()) {// The null processing is left to the function to handle, according to the function return value to determine
        println("token length is ${token.length}")// Compile properly: use isNullOrBlank to take the inverse operation, here intelligent derivation of normal recognition}}Copy the code

The three cases are all defined as a function. Why can’t our own function be recognized by intelligent derivation, while Kotlin’s built-in function can? What’s the black magic in the built-in function? So we seem to see the essence of the problem, which is to compare the isNullOrBlank function implementation. Open isNullOrBlank function source:

@kotlin.internal.InlineOnly
public inline funCharSequence? .isNullOrBlank(a): Boolean {
    contract {// Here is a contract
        returns(false) implies (this@isNullOrBlank! =null)}return this= =null || this.isBlank()// This is a normal call
}
Copy the code

By checking the isNullOrBlank function source code, it seems that I found a new thing contract, yes, this is the main character contract we are going to analyze today

  • Case 4: Using a custom contract to make checkTokenIsValid function have dark magic recognized by the compiler’s intelligent inference
@ExperimentalContracts // Since the Contract API is Experimental, you need to use the annotation declaration for ExperimentalContracts
fun main(args: Array<String>) {
    val token: String? = TokenGenerator().generateToken("kotlin")
    if (checkTokenIsValid(token)) {// The null processing is left to the function to handle, according to the function return value to determine
        println("token length is ${token.length}")// Compile well: use custom contract implementation, here intelligent derivation is normal recognition}}@ExperimentalContracts // Since the Contract API is Experimental, you need to use the annotation declaration for ExperimentalContracts
fun checkTokenIsValid(token: String?).: Boolean{
    contract {
        returns(true) implies (token ! =null)}returntoken ! =null && token.isNotBlank()
}
Copy the code

So if you look at all of these examples and you see that the contract is amazing, it seems like it has a way of talking to the compiler, telling it this is a smart cast, don’t tell me to nullize. Now let’s open the mystery of Contract, please go on.

2. Basic introduction to Contract

  • The basic definition

A Contract in Kotlin is a way of notifying the compiler of a function’s behavior. As mentioned above, it looks like it can tell the compiler what its behavior is at this point.

  • The basic format
/ / pseudo code
fun someThing(a){
    contract{
       ...//get some effect}}Copy the code

Call the function someThing and have an effect

Is not a face meng force, good abstract ah, do not panic to an example to explain:

@ExperimentalContracts // Since the Contract API is Experimental, you need to use the annotation declaration for ExperimentalContracts
fun checkTokenIsValid(token: String?).: Boolean{
    contract {
        returns(true) implies (token ! =null)}returntoken ! =null && token.isNotBlank()
}
// A call to checkTokenIsValid produces the effect that if the return value is true, that means token! = null. Inform the compiler of this contract behavior, and the compiler will know that the next time you encounter this situation, your token will be non-null and will be smart cast. Note: The compiler will recognize it next time, so when you change the contract, you will find that smart Cast does not take effect immediately, but deletes it and calls it again.
Copy the code

3, Kotlin source code Contract application

Although Contract contracts are currently Experimental, they were used extensively in versions of the standard library prior to Kotlin. The isNullOrBlank function seen in the above example uses contracts, which you can easily find in the 1.2 Kotlin source code. So here are a few common examples.

  • CharSequence class extension isNullOrBlank(), isNullOrEmpty()
@kotlin.internal.InlineOnly
public inline funCharSequence? .isNullOrBlank(a): Boolean {

    contract {
        returns(false) implies (this@isNullOrBlank! =null)}return this= =null || this.isBlank()
}
Copy the code

The contract tells the compiler that the effect of calling isNullOrBlank() extension is that if the function returns false, the current CharSequence instance is not empty. So one thing we can see is that when you call isNullOrBlank(), the smart cast only works if it’s inverted, so try that yourself.

  • RequireNotNull function
@kotlin.internal.InlineOnly
public inline fun <T : Any> requireNotNull(value: T? , lazyMessage: ()-> Any): T { contract { returns() implies (value ! =null)}if (value == null) {
        val message = lazyMessage()
        throw IllegalArgumentException(message.toString())
    } else {
        return value
    }
}
Copy the code

The effect of calling requireNotNull is that if the function returns normally and no exceptions are thrown, it means that value is not null

  • Common library functions run,also,with,apply, and let
// Use the apply function as an example
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T. () -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
Copy the code

Returns: callsInPlace: invocationKind. EXACTLY_ONCE: callsInPlace: invocationKind. EXACTLY_ONCE: callsInPlace: invocationKind. EXACTLY_ONCE This contract representation tells the compiler that the effect of calling apply is to specify that the block LAMba expression argument is invoked at the appropriate place. The appropriate place is that block lambda expressions can only be called when the apply function is called. When the apply function is called, the block expression cannot be executed. Invocationkind.exactly_once specifies that a block lambda expression can only be called once, and that the outer function must be an inline function.

4, The principle behind Contract (Contract source code analysis)

See the wide use of Contract contracts in source code above, and see that the previous example represents three different types of contracts. At this time, do you have a strong interest in the source code of Contract? Next, we will briefly analyze the source code of the Contract. Many people get a headache when it comes to analyzing source code, because you probably haven’t found a good way to analyze source code.

The following gives a personal analysis of the source code habit (has been used so far, the effect feels good):

Rule of source code analysis: To analyze a source code problem, first identify the problem domain, and then list all the participating roles or noun concepts in the problem domain, the role each role or noun plays, the relationship between roles, how they communicate, and how they establish relationships.

So, let’s use this principle to uncover the mystery of Contract step by step, so that you can have a deeper and comprehensive understanding of the API of Contract.

  • Step 1: Identify the problem area (that is, what you need to study)

Review and understand the principles behind the Contract and its workflow

  • The second step is to identify the participating roles in the problem domain (that is, the API classes in the Contract) by giving all the classes and interfaces in a contracts package

  • The third step is to clarify their responsibilities.
// To indicate the effect of a function being called
public interface Effect 
// Inherits the Effect interface, which is used to indicate that the Effect of certain conditions is true after observing another Effect after a function call.
public interface ConditionalEffect : Effect 

// Inherits the Effect interface, which represents the result of a function call (this is usually the most common Effect).
public interface SimpleEffect : Effect {
    public infix fun implies(booleanExpression: Boolean): ConditionalEffect // Infix indicates that the implies function is an infix function, so it should be called just like an infix expression
}
// Inherits the SimpleEffect interface, which is used to indicate when a function normally returns a given return value
public interface Returns : SimpleEffect
// Inherits the SimpleEffect interface, which is used to indicate when a function normally returns a non-empty value
public interface ReturnsNotNull : SimpleEffect
// Inherits the Effect interface, which is used to express the Effect of calling a functional argument (lambda expression argument), and the functional argument (lambda expression argument) can only be called during the call of its function. After the call, the functional argument (lambda expression argument) cannot be executed.
public interface CallsInPlace : Effect
Copy the code

Then focus on the contractBuilder. kt file, which is actually a Contract Contract, a DSL Builder and a Contract function exposed to the outermost.

// The ContractBuilder interface aggregates different effects to return functions on the corresponding interface object
public interface ContractBuilder {
    @ContractsDsl public fun returns(a): Returns

    @ContractsDsl public fun returns(value: Any?).: Returns
    
    @ContractsDsl public fun returnsNotNull(a): ReturnsNotNull

    @ContractsDsl public fun <R> callsInPlace(lambda: Function<R>, kind: InvocationKind = InvocationKind.UNKNOWN): CallsInPlace
}

// Enumerates the number of times lambda expression arguments to callsInPlace are called
public enum class InvocationKind {
    // Can only be called once at most (cannot be called or can only be called once)
    @ContractsDsl AT_MOST_ONCE,
    // Be called at least once (only once or more)
    @ContractsDsl AT_LEAST_ONCE,
    // Can only be called once
    @ContractsDsl EXACTLY_ONCE,
    // Cannot determine how many times is called
    @ContractsDsl UNKNOWN
}
Copy the code
  • Step 4: Clarify the relationship between effects.

  • Step 5, analyze an example to simulate the Contract workflow
fun checkTokenIsValid(token: String?).: Boolean{
    contract {// The first thing we're actually doing here is calling the contract function and passing in a Lambad expression with a ContractBuilder return value.
        returns(true) implies (token ! =null)
        The returns(value) function is actually equivalent to this.returns(true);
        The RETURNS (value) function returns a RETURNS interface object. The RETURNS interface inherits the SimpleEffect interface (with the implies intermediate function), Therefore, call the implies function directly with the Returns interface object fix
    }
    returntoken ! =null && token.isNotBlank()
}
Copy the code

In fact, after analysis, it is found that the contract actually describes the behavior of the function, including the return value of the function and the execution rules of lambda expression parameters in the function inside the function. Informing the compiler of these behavior constraints can save the compiler’s intelligent analysis time, which is equivalent to the developer helping the compiler to do some intelligent inference things faster and more efficiently.

5. Customize Contract

In fact, custom was given earlier in the above example, Contract is still experimental in kotlin version 1.3, so it cannot be used directly. Looking at the source code of various contract use examples, I believe that a custom contract should be very simple.

// Here is a smart cast example of the instanceOf type
data class Message(val msg: String)
 
@ExperimentalContracts // Add Experimental annotation
fun handleMessage(message: Any?). {
    if (isInstanceOf(message)) {
        println(message.msg) 
    }
}
 
@ExperimentalContracts
fun isInstanceOf(message: Any?).: Boolean {
    contract { 
        returns(true) implies (message is Message)
    }
    return message is Message
}

Copy the code

A contract is actually a protocol between the developer and the compiler, and the developer passes some special effects to the compiler through the contract. And these effects are determined by the behavior of the function call. On the other hand, it also shows that the developer must know enough about the business scenario to use the contract, because this is equivalent to the compiler trusting the developer to handle some operations. The more flexible the developer is in using the space, the greater the risk is, so do not abuse it.

6. Restrictions on the use of Contract

While the Kotlin contract looks great, the current syntax is currently unstable, and the API may change in the future. But with the above analysis, you’ll be able to make sense of future changes quickly.

  • We can only use contracts inside top-level functions, i.e. we cannot use them on members and class functions.
  • Contract call declarations must be the first statement in the body of a function
  • As I said above, the compiler trusts the contract unconditionally. This means that the programmer is responsible for writing the right and reasonable contracts.

Although Contract is still in the experimental stage, we have seen extensive use of contracts in the stalib library source code for a long time, so it is not expected that the API will change much in subsequent releases, so it is worth further analysis at this point.

Three, endnotes

Due to the in-depth analysis of the Contract, it will take too much space in the article. Other new features will be introduced and analyzed in the next article, so stay tuned.

Welcome to Kotlin’s series of articles:

Original series:

  • Kotlin/Native: JetBrains Developer’s Day (Part 1
  • How to overcome the difficulties of generic typing in Kotlin
  • How to overcome the difficulties of Generic typing in Kotlin (Part 2)
  • How to overcome the difficulties of Generic typing in Kotlin (Part 1)
  • Kotlin’s trick of Reified Type Parameter (Part 2)
  • Everything you need to know about the Kotlin property broker
  • Source code parsing for Kotlin Sequences
  • Complete analysis of Sets and functional apis in Kotlin – Part 1
  • Complete parsing of lambdas compiled into bytecode in Kotlin syntax
  • On the complete resolution of Lambda expressions in Kotlin’s Grammar
  • On extension functions in Kotlin’s Grammar
  • A brief introduction to Kotlin’s Grammar article on top-level functions, infix calls, and destruct declarations
  • How to Make functions call Better
  • On variables and Constants in Kotlin’s Grammar
  • Elementary Grammar in Kotlin’s Grammar Essay

Translation series:

  • Kotlin’s trick of Reified Type Parameter
  • When should type parameter constraints be used in Kotlin generics?
  • An easy way to remember Kotlin’s parameters and arguments
  • Should Kotlin define functions or attributes?
  • How to remove all of them from your Kotlin code! (Non-empty assertion)
  • Master Kotlin’s standard library functions: run, with, let, also, and apply
  • All you need to know about Kotlin type aliases
  • Should Sequences or Lists be used in Kotlin?
  • Kotlin’s turtle (List) rabbit (Sequence) race
  • The Effective Kotlin series considers using static factory methods instead of constructors
  • The Effective Kotlin series considers using a builder when encountering multiple constructor parameters

Actual combat series:

  • Use Kotlin to compress images with ImageSlimming.
  • Use Kotlin to create a picture compression plugin.
  • Use Kotlin to compress images.
  • Simple application of custom View picture fillet in Kotlin practice article

Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~