With this kotlin test written with Kotest, IntelliJ shows the warning "The expression is unused" and syntax coloration is not working.
Also, when running the tests, the test is not found.
class TalentMatchServiceSpec : StringSpec() {
init {
"add 3 to 2 should give 5"
{
// Given integers 2 and 3
val two = 2
val three = 3
// When adding both numbers
val sum = two + three
// Then the result is 5
sum shouldBe 5
}
}
}
#Thomas Martin's answer is correct, but you asked why it makes a difference.
The SpringSpec in Kotest relies on some Kotlin DSL features to work. Let's demonstrate by building the same function from scratch.
We would start by using Kotlin's extension functions. This allows us to add functions to classes we don't control.
So,
fun String.test(test: () -> Unit)
So then we can call this test function on any string:
"this is a test".test({ 1 + 2 shouldBe 3 })
Secondly, Kotlin allows any final lambda arg to be brought outside of the parens. So foo(arg, arg2, arg3, lambda) can become foo(arg, arg2, arg3) lambda. This means we can write our test as:
"this is a test".test { 1 + 2 shouldBe 3 }
Next, we can mark functions as infix and then we do not need to use the dot to invoke them. So our test function becomes:
infix fun String.test(test: () -> Unit)
And our test now looks like:
"this is a test" test { 1 + 2 shouldBe 3 }
Finally, any function named invoke and marked as an operator function can be invoked without the function name. So operator fun invoke(a: String, b: String) inside a class Foo can be invoked as both the regular Foo.invoke(a,b) or just Foo(a, b).
So putting all this together, our final test function looks like:
operator fun String.invoke(test: () -> Unit)
And our test ends up as:
"this is a test" { 1 + 2 shouldBe 3 }
Or more likely,
"this is a test" {
1 + 2 shouldBe 3
}
If the braces are moved to the next line, as in your original question, it just looks like a String followed by an unrelated lambda block. Two different expressions.
Just put the opening curly bracket after "add 3 to 2 should give 5", like this :
class TalentMatchServiceSpec : StringSpec() {
init {
"add 3 to 2 should give 5" {
// Given integers 2 and 3
val two = 2
val three = 3
// When adding both numbers
val sum = two + three
// Then the result is 5
sum shouldBe 5
}
}
}
Related
To preface, I'm not a professional, nor a student. I have been self teaching Python and JavaScript for very basic quality of life improvements and I'm working my way through the Google Developers "Kotlin Fundamentals" for something different. However I've reached trailing lambdas and the "repeat function" and gotten a bit confused.
Before using the repeat function, the tutorial code was functioning as expected:
main() {
val treatFunction = trickOrTreat(false) {"$it quarters"}
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
if(isTrick) {
return trick
} else {
if (extraTreat != null) {
println(extraTreat(5))
}
return treat
}
}
val trick: () -> Unit = {
println("No Treats!")
}
val treat: () -> Unit = {
println("Have a treat!")
}
Which returned:
5 quarters
Have a treat!
No Treats!
But it then explains the "repeat" function and guides you to nest the treatFunction() inside a repeat function:
main() {
val treatFunction = trickOrTreat(false) {"$it quarters"}
val trickFunction = trickOrTreat(true, null)
repeat(4) {
treatFunction()
}
trickFunction()
}
Which returned:
5 quarters
Have a treat!
Have a treat!
Have a treat!
Have a treat!
No Treats!
This is the output that I was supposed to expect, but the guide doesn't explain - and I do not understand - why the trailing lambda "{$it quarters}" for the value "treatFunction" is not expressed in each iteration of the repeat function. e.g:
5 quarters
Have a treat!
5 quarters
Have a treat!
5 quarters
Have a treat!
5 quarters
Have a treat!
No Treats!
Any advice would be much appreciated!
No matter how many times you call repeat because treatFunction is already initialized by this moment and it's actually a reference to treat object. println(extraTreat(5)) is invoked only during this initialization. When you run treatFunction() it is actually dereferenced to treat() that in turn doesn't contain println(extraTreat(5)) logic.
The code here works:
fun main(){
val pizza = random()
print(pizza.num)
}
class random{
val num = 5
}
But the code here does not work
fun main(){
val pizza = random()
print(pizza.num)
}
class random{
val num = 5
num = 7
}
The only difference is that in the last line of code I reassign the variable num. The only thing I did was change this variable from 5 to 7.
Why is this causing errors?
Note This is the online IDE I was using: https://developer.android.com/training/kotlinplayground
2 things:
Firstly, you can't reassign vals. you need to change that to var
Secondly, you can't do assignments directly in a class body, only declarations.
However, you could put it in an init block like this to get the desired result:
class random{
var num = 5
init {
num = 7
}
}
you might want to read the documentation about kotlin classes here
How does the Kotlin compiler decide whether an expression enclosed in { } is a block or a lambda?
Consider this:
val a: Int
if (cond)
a = 1
else
a = 2
can be written more succinctly as:
val a =
if (cond)
1
else
2
Similarly, one might think that this:
val a: () -> Int
if (cond)
a = { 1 }
else
a = { 2 }
should be able to be written more succinctly like this:
val a =
if (cond)
{ 1 }
else
{ 2 }
But this is not the same: a is now an Int, and not of type () -> Int, because now the { 1 } is no longer a lambda. What are the rules that say whether something is a lambda or a block?
I didn't look into the Kotlin lexer, but I guess there are few possible places in the code where the compiler expects either a single expression or a block of code. That includes the code immediately following most of control flow statements like: if, else, while, when (one of its cases), etc. If you put { in one of these places, it will be interpreted as a start of the block of code related to this control flow statement and not as a lambda.
This is as simple as that. Note that even if you hint the compiler about the type, it still won't work:
// compile error
val a: () -> Int = if (cond)
{ 1 }
else
{ 2 }
It will be interpreted more like this:
// compile error
val a: () -> Int = if (cond) {
1
} else {
2
}
{ after if condition is always interpreted as a start of block of code. You need to put double {, } in cases like this:
// works fine
val a: () -> Int = if (cond) {
{ 1 }
} else {
{ 2 }
}
To put it very succinctly and easy to remember, the first opening brace after an if/when/else/for is always assumed to be the opening of a block. Use double braces if you want a lambda in there.
This is specified in the Kotlin Language Specification, section 1.2: Syntax Grammar:
The grammar defines a block as statements between { and }. In most cases (such as function bodies) the syntax is different between a block and a lambda. Where it is the same is in the usage of controlStructureBody - these are the places where the block has a value, or where you could put a non-block expression in its place. If you search the whole spec document for "controlStructureBody", you'll find it's used in the following places:
For statement
While statement
Do-while statement
If expression
When expression
When entry
In every other place where a value is required, a '{' signifies the start of a lambda.
From the Kotlin Fundamentals course, we have this code:
#BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight?) {
item?.let {
setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
}
In other languages I would simplify this by using the sleepQuality integer to look up the matching element, in Typescript for example:
setImageResource(R.drawable[`ic_sleep_${item.sleepQuality}`] ?? R.drawable.ic_sleep_active)
To start trying this out even my first step doesn't compile:
0 -> R.drawable["ic_sleep_0"] // doesn't compile
Is this kind of operation possible in Kotlin?
Edit/Update
There's a few good responses here.
It looks like for this specific use case, I can look up resources by string, similar to what I'm trying:
val resId = context.resources.getIdentifier("ic_sleep_${item.sleepQuality}", "drawable", context.packageName)
However, this is not a general solution. The following does not work:
val x = item['sleepQuality']
As noted in some responses, this may be possible using reflection. How would this be done?
val resId = context.resources.getIdentifier("ic_sleep_${item.sleepQuality}", "drawable", context.packageName)
setImageResource(if (resId != 0) resId else R.drawable.ic_sleep_active)
Through reflection (based on Getting value of public static final field/property of a class in Java via reflection) :
val resId = try {
R.string::class.java.getField("ic_sleep_${item.sleepQuality}").getInt(null)
} catch (e: Exception) {
R.string.ic_sleep_active
}
setImageResource(resId)
Only using reflection. Kotlin statically typed programming language and does not support "Variable variables"
I'm trying to understand why let is needed. In the example below I have a class Test with a function giveMeFive:
public class Test() {
fun giveMeFive(): Int {
return 5
}
}
Given the following code:
var test: Test? = Test()
var x: Int? = test?.giveMeFive()
test = null
x = test?.giveMeFive()
x = test?.let {it.giveMeFive()}
x gets set to 5, then after test is set to null, calling either of the following statements return null for x. Given that calling a method on a null reference skips the call and sets x to null, why would I ever need to use let? Are some cases where just ?. won't work and let is required?
Further, if the function being called doesn't return anything, then ?. will skip the call and I don't need ?.let there either.
let()
fun <T, R> T.let(f: (T) -> R): R = f(this)
let() is a scoping function: use it whenever you want to define a variable for a specific scope of your code but not beyond. It’s extremely useful to keep your code nicely self-contained so that you don’t have variables “leaking out”: being accessible past the point where they should be.
DbConnection.getConnection().let { connection ->
}
// connection is no longer visible here
let() can also be used as an alternative to testing against null:
val map : Map<String, Config> = ...
val config = map[key]
// config is a "Config?"
config?.let {
// This whole block will not be executed if "config" is null.
// Additionally, "it" has now been cast to a "Config" (no
question mark)
}
You need to use let if you want to chain function calls that aren't defined on the type you are chaining from.
Let's say the definition of your function was this instead:
// Not defined inside the Test class
fun giveMeFive(t: Test) {
return 5
}
Now, if you have a nullable Test and want to call this function in a chain, you have to use let:
val x = test?.let { giveMeFive(it) }
The .let{} extension function in Kotlin:
Takes the object reference as the parameter on which .let{} is called.
Returns value of any non-primitive data-type which has been returned from with let{} function. By default, it returns undefined value of kotlin.Any class.
Declaration in package kotlin:
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
Simple practical demonstration to see how .let{} extension function works in Kotlin.
Sample Code 1:-
val builder = StringBuilder("Hello ")
println("Print 0: $builder")
val returnVal = builder.let { arg ->
arg.append("World")
println("Print 1: $arg")
"Done" // Returnning some string
}
println("Print 2: $builder")
println("Print 3: $returnVal")
Sample Code 1 Output:-
Print 0: Hello
Print 1: Hello World
Print 2: Hello World
Print 3: Done
In Sample Code 1:
We created the final object of type StringBuilder with initialization value "Hello ".
In builder.let{}, the builder object reference will be passed to arg.
Here, the output Print 2: Hello World and Print 3: Hello World means that the builder and arg, both are pointing to the same StringBuilder object-reference. That's why they both print the same String value.
In the last line of .let{} function block, we are returning "Done" String value which will be assigned to returnVal.
*Hence, we get Print 3: Done output from returnVal.
Sample Code 2:-
val builder = StringBuilder("Hello ")
println("Print 0: $builder")
val returnVal = builder.let { arg ->
arg.append("World")
println("Print 1: $arg")
arg.length
}
println("Print 2: $builder")
println("Print 3: $returnVal") // Now return val is int = length of string.
Sample Code 2 Output:-
Print 0: Hello
Print 1: Hello World
Print 2: Hello World
Print 3: 11
In Sample Code 2:
What's difference:
In the last line of .let{} function block, we are returning the Int value equals to the length of StringBuilder object arg whose value at this line of code is "Hello World". So length = 11 of type Int will be assigned to returnVal.
*Hence, we get Print 3: 11 as output from returnVal.
Also try these:-
.run() {}
.apply() {}
with(obj) {}
.also() {}
Happy Coding...
fun <T, R> T.let(f: (T) -> R): R = f(this)
.let block does not equal to in multiThreading
val x? = null
if(x == null) {
}
and
x?.let{
}
.let block is thread-safe