Println (or Log) in Kotlin - Standard / Alternative Methods - Part 1 of 2
Println? Part 1
Situation - Printing Results to the Console
Imagine you need to run a function that returns a String result. We can think about the following:
- Does the function just return a String or does it do something else that affects something else in the program?
- Do we need to display that result in the console?
- Do we need to store that result for use somewhere else?
For these examples, we will focus on numbers one and two being true.
As a general rule, we want our code to be as easy to read and understand as possible. Keep that in mind for the following.
Example Function
This function may help you understand the type of situation we're dealing with.
private fun testXAndReturnSummary()
: String {
/**
* [processData] alters program
* state and returns enum
* which we convert to String
* using [getTextDescription]
*/
return getTextDescription(
processData()
)
}
Three Standard Println Approaches
- Approach 1 - Store the result and print it
Perhaps you would get the String then print it.
val resultSummary =
testXAndReturnSummary()
This approach gets a 4/10. It works, but it is quite verbose. We don't need to store the resultSummary so this is definitely not great.
- Approach 2 - Print the return of the function directly
println(testXAndReturnSummary())
- Approach 3 - Also...
Perhaps you are more experienced with Kotlin and do it like this:
testXAndReturnSummary()
.also { println(it) }
This is great. This is a perfect 10/10; the idiomatic way to print a String being returned from a function. This makes it clear you want the function to do something and you also want to print the result.
Alternative Approach
- Alternative 1
testXAndReturnSummary().println()
But as you may know or notice, this doesn't exist (yet).
How would you add this?
Click to reveal
fun String.println(): String {
println(this)
return this
}
Is this a good solution? It's okay, I guess. I feel it lands somewhere between Approach 2 and 3 so I'll give it a 6.5/10. It's definitely convenient.
- Alternative 2
testXAndReturnSummary().alsoPrintln()
I think we could score this solution a 9/10. In personal projects, especially for testing things, I would recommend adding this String extension locally and deleting it later when the project becomes more mature.
Alternative 3
- Another way to deal with this is to wrap the printing in another function. Now we just simply call the wrapper function:
fun testSomethingAndPrintResult() =
testXAndReturnSummary()
.also { println(it) }
What Else?
Number Formatting
fun Double.alsoPrintln(
decimalPlaces: Int = 2)
: Double {
println("%.${decimalPlaces}f"
.format(this))
return this
}
fun Float.alsoPrintln(
decimalPlaces: Int = 2
): Float {
println("%.${decimalPlaces}f"
.format(this))
return this
}
fun Float.toString(
decimalPlaces: Int = 2
): String {
return "%.${decimalPlaces}f"
.format(this)
}
Click to reveal
fun Double.toRoundedString(
decimalPlaces: Int = 2
): String = "%.${decimalPlaces}f"
.format(this)
Okay, so it is one line on my IDE but I had to add line breaks to make it show up nicely on this blog.
Notice the naming change; there are two very specific reasons for these. I wanted to start the function with 'to' so it shows up in autocomplete along the same pattern as toString.
Secondly, toRoundedString makes it obvious you are limiting the decimal places without needing to read the name of the input variable.
I actually think this improved floating-point (real) number extension is very useful so I will rate that a 10/10.
Other Values
fun <T> T.alsoPrintln(): T {
println(this)
return this
}
fun <T> T.alsoPrintln(
start: String = "",
end: String = ""
): T {
println(start + this + end)
return this
}
We added start and end Strings to the alsoPrintln function, but at this point it's not really making things nicer or easier to read.
In fact, I would rate these solutions 2.5/10 (.5 bonus points for being creative with start and end).
In Action
Let's look at some of these in action:
fun templateAndNumberPrintTest() {
13123.alsoPrintln()
false.alsoPrintln()
123.313.alsoPrintln(
decimalPlaces = 3
) // to three places
1233.3434f.alsoPrintln(
decimalPlaces = 1
)
1233.3434f.toRoundedString(
decimalPlaces = 2
)
val widthCm = 1232.213123213
println("The object was ${
widthCm.toRoundedString(
decimalPlaces = 3
)
} wide")
val result = true
result.alsoPrintln(
start = "The result was ",
end = "."
)
println("The result was " +
"${result}.")
}
Looking at this, the primitive values being directly followed by the alsoPrintln is quite amusing, but I doubt it has many real practical applications.
The Double and Float decimalPlaces modifier with alsoPrintln suffers the same fate, but the toString with decimalPlaces looks very practical.
The result value being printed using the start and end version really looks quite bad. The standard println function following that is shorter, more idiomatic, and easier to read.
Call Chaining
As we're looking at examples, we can take a quick look at call chaining:
fun callChainExample() {
val upperName =
"With our extension"
.alsoPrintln()
.uppercase()
.alsoPrintln()
}
Here you can see how convenient the return on the alsoPrintln() is. Of course, we can do this with quite easily with the standard language features:
fun callChainExample2() {
val upperName =
"With standard language features"
.also { println(it) }
.uppercase()
.also { println(it) }
}
Logging
Click to reveal
fun String.alsoLogV(
tag: String
): String {
Log.v(tag, this)
return this
}
fun String.alsoLogV(
tag: String
): String {
Log.v(tag, this)
return this
}
val stringResult =
"A very interesting result."
stringResult.alsoLogV(
tag = "P_Test"
)
val stringResult =
"A very interesting result."
stringResult.alsoLogV(
tag = "P_Test"
)
Interesting. Any More?
Part 1 Conclusion
We have mainly focused on extension functions, call chaining, and naming, but we also touched upon templates. In part two, we will go into a little more depth.
I'll leave you with one final note - these are not the final versions of these functions. The final versions will be presented in part two.
Links
- Part 2
- Raw Kotlin
If you are interested in raw Kotlin (which I used for everything in this post), you may also want to check out these posts:
- Faster Backend Development: Running 'Raw' Kotlin in Android Studio - Set up your Android Studio to run Kotlin without Android
- Plain Kotlin Android Style Logging - Logging with Log so you can easily move code back and forth into and Android environment. I used this for the Log.v function.
Comments
Post a Comment