Destructuring assignments, not just declarations

I know that is a old feature request, but I want to make my point here, with a good example of usage.

Destructuring assignment would be very useful because it is very common one needs to use destructuring for functions call inside a inner scope (curly brackets). In Kotlin it’s annoying, and sometimes it’s best join variables in a class in the caller with no logic than use multiple return in functions.

I’m writing a kind of compiler. There is a routine called getToken, the return is a token (using sealead class) and the new cursor position. Cursor is global inside the compiler and the token has a local usage.

Unfortunately the cleanest solution is to create a class with the cursor and the token, just to force the return, because destructuring within a local scope, would oblige to reassign the returns to other variables, which would be very artificial.

What I’ve made schematically:

First I’ve declared a sealed class and a artifical class (TokEt) that will be the return type of my function.

sealed class Token() {
    class Variaveis(...) : Token()
    class Numeros(...): Token()
    class Parenteses(...) : Token()
    class Funcoes(.... ) : Token()
    class Nada() : Token()
}

class TokEt(val tok: Token, val pos: Int)

Using the classes above defined for writing my function and the main code:

getToken(s: String, p: Int): TokEt {
    // code for get token t and update cursor pNew
    return TokEt(t, pNew)
}

fun buildTree(s: String) {    // Scanning the string s
    var p: Int = 0
    while (p <= s.length - 1) {
       var tk = getToken(s, p)   
       p = tk.pos
       when (tk.tok) {   // Processing token for build a tree
            is Token.Variaveis -> { ... }
            is Token.Numeros -> {... }
            is Token.Parenteses -> {...}
            is Token.Funcoes -> {...}
            is Token.Nada -> { }
        }
    }
}

My wish

getToken(s: String, p: Int): Pair<Token,Int>{
    // code for get token t and update cursor pNew
    return Pair(t, pNew)
}

fun buildTree(s: String) {    // Scanning the string s
    var p: Int = 0
    var pos = 0  
   var tok = Token.Nada()
    while (p <= s.length - 1) {
            //  Illegal. Notice that I can't declare p and tok here!
        (tok p)  = getToken(s, p)     
      //=========================
        when (tok) 
         ...  
      }
    }
}

Notice that allow one to use directly the returned token and updated cursor position as variables is a very nice syntactic sugar: no need to use superfluous property dot access and annoying reassignments (p = tk.pos). In my adopted solution I prefer to define a dummy class in the main function than using real multiple return, because it would be very weird return the values and soon below I should realocate for variables with wider scope,

I am not going to address destructuring assignments, but show you ways that Kotlin can give you better ways to think about the problem that would eliminate the need for it in this case. You are coding in a very imperative style and it is not Kotlin’s job to let you write better spaghetti code.

A quick tip to start with that I use in my versions. s.length - 1 can be replaced with s.lastIndex.

I am assuming here that p is not used in the when code (p is only used in iterating through the string) and that your getToken is a pure function in that it uses only s and p and nothing that happens in the when affects the operation of getToken.

I am also going to use a function argument for passing in the handling code (the when block).

The first thing to know is that Kotlin has nested functions and a nested function can access the variables of the surrounding scope. That would eliminate the need to pass p in and out of getToken:

fun buildTree(s: String, handler: (Token) -> Unit) {    // Scanning the string s
    var p: Int = 0

    fun getToken(s: String): Token {
        // code for get token t and update cursor pNew
        val t = Token.Variaveis(s[p])
        p = p + 1
        return t
    }

    while (p <= s.lastIndex) {
        handler(getToken(s)) 
    }
}

Which would be called like this:

buildTree(s) { when(it) { ... } }

That eliminates the need for destructuring but still not great code. One thing that can be done to make imperative code less imperative is to use tail recursion instead of loops:

tailrec fun buildTree(s: String, p: Int = 0, handler: (Token) -> Unit)
{
    if (p <= s.lastIndex)
    {
        // code for get token t and update cursor pNew

        handler(t)
        
        buildTree(s, pNew, handler)
    }
}

But in reality we want to separate the iteration from the handling and allow higher level functions like map and filter, which we can do using a coroutine sequence builder:

fun tokenize(s: String) = sequence {
    var p = 0
    while (p <= s.lastIndex) {
        // code for get token t and update cursor pNew    
        yield(t)
        p = pNew
    }
}

Which can be used like this:

tokenize(s)
     .forEach{ when(it) { ... } }
4 Likes

Should have also mentioned that the extension functions.like with, run, and apply also are appropriate here. Based on the first version with TokEt instead of pair:

fun buildTree(s: String) {    // Scanning the string s
    var p: Int = 0
    while (p <= s.length - 1) {
       with(getToken(s, p))
       {   
           p = pos
           when (tok) {  ...  }
       }
    }
}
1 Like

I’ve learned Kotlin just one month ago. So I still have many gaps. I know that there are nesting function in Kotlin, unfortunately getToken will be used in other constructs like expression evaluators. With is a really nice feature in Kotiln, remember me a little be with statement in Pascal. I confess that for me, an old school programmer very familiar with functional programming, it’s a little bit unreadable for me some nice features from Kotlin.