Kotin Koan

I was trying to solve the null safety question on kotlin koans (Kotlin Playground: Edit, Run, Share Kotlin Code Online). I wrote the following code fun

sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
		
    message?.let{
        m-> {
            client?.let{ c ->
                if(c.personalInfo != null && c.personalInfo.email != null ){
                    mailer.sendMessage(c.personalInfo.email,m)
                }
                
            }
        }
    }
}

And I get the following error
Fail: everythingIsOk: The function ‘sendMessage’ should be invoked expected: but was:

Passed: noClient

Passed: noMessage

Passed: noPersonalInfo

Passed: noEmail What does it mean ?

Welcome to the Kotlin community. :vulcan_salute:

Now to your question: It means that your code does not call sendMessage even when all parameters are ok.

I think, the reason for that is that the number of braces (curly brackets) is wrong. let computes the given operation and returns its result. I guess in your version with the additional braces, the result is a full function of sending message instead of just sending message - basically the difference between doSomething() and { doSomething() }.
FunFact: That means, if you add “?.invoke()” after the last closing brace, it actually passes all tests.

However, I am not an expert on let, but the point of the exercise is to use Kotlin operations to simplify the given Java code and your solution is actually way more complicated than the original: it is not really shorter than the Java version and it is nested in three levels - which invites easy-to-make-but-difficult-to-see-mistakes like “too many braces”.

I guess the idea is using safe calls in chains like if (client?.personalInfo?.email != null). With that you can reduce it to a simple two-line solution with only one level.

1 Like

Yup thnx for the input I didn’t use idiomatic kotlin . The issue was caused by the curly braces adding a run after m → solved everything .

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
		
    message?.let{
        m-> run{
            client?.let{ c ->
                if(c.personalInfo != null && c.personalInfo.email != null ){
                    mailer.sendMessage(c.personalInfo.email,m)
                }
                
            }
        }
    }
}

class Client(val personalInfo: PersonalInfo?)
class PersonalInfo(val email: String?)
interface Mailer {
    fun sendMessage(email: String, message: String)
}

Re. the braces: one potential source of confusion in Kotlin is that, unlike most other languages, braces are used for two different things in Kotlin:

  • Group statements into a block. This is what they mean in C, Java, and a host of other languages. It provides a scope for local variables. And it delimits the body of a class, interface, or function, and a way to execute multiple statements in a for or while loop, a try block, or an if branch.

In Kotlin, braces define a block only in the specific cases above. In all other cases they:

  • Define a lambda. This is an anonymous function or closure, which is not run immediately but can be stored and/or passed as a parameter. In many other languages, you need to provide an arrow (-> or =>) to define a lambda, but Kotlin lets you omit that if the lambda takes no parameters, or takes one (which can be referred to as it), and so the braces are required.

What makes this confusing is that, although braces define a block only in specific cases, those cases are extremely common (and work just like many other languages), and so it’s easy to miss the fact that in Kotlin, arbitrary braces define a lambda. You can’t use them to define a block just anywhere; instead, you have to use e.g. run{ … } (in which the braces define a lambda).


In the question, this applies to the line m -> {, which defines a lambda. That lambda will become the result of the overall message?.let{ expression — but since the lambda is never called, none of the statements inside it will get executed.

If you remove that brace (and the corresponding close brace), then you might find it works as intended. (If there were multiple statements inside, you could instead add run before the open brace.)

1 Like

Thanks so much for giving such a in depth reply and to clear my doubt . The following works

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
		
    message?.let{
        m-> run{
            client?.let{ c ->
                if(c.personalInfo != null && c.personalInfo.email != null ){
                    mailer.sendMessage(c.personalInfo.email,m)
                }
                
            }
        }
    }
}

class Client(val personalInfo: PersonalInfo?)
class PersonalInfo(val email: String?)
interface Mailer {
    fun sendMessage(email: String, message: String)
}

Just removing the additional braces would work, too:

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    message?.let { m ->
        client?.let { c ->
            if (c.personalInfo != null && c.personalInfo.email != null) {
                mailer.sendMessage(c.personalInfo.email,m)
            }
        }
    }
}

or keeping the braces and calling invoke on the result (the solution above is better, just mention it for fun):

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    message?.let { m -> {
            client?.let { c ->
                if (c.personalInfo != null && c.personalInfo.email != null) {
                    mailer.sendMessage(c.personalInfo.email,m)
                }
            }
    	}
    }?.invoke()
}

or using optional chaining:

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    if (message != null && client?.personalInfo?.email != null) {
        mailer.sendMessage(client.personalInfo.email, message)
    }
}

or using a generic function that adds the optional chaining idea to function parameters:

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    ifNotNull(mailer::sendMessage, client?.personalInfo?.email, message)
}

fun <S, T, U> ifNotNull(action: (S, T) -> U, arg1: S?, arg2: T?): U? {
    if (arg1 == null || arg2 == null) {
        return null
    }
    return action(arg1, arg2)
}

Kotlin is fun. :vulcan_salute: