why ```readLine().toBoolean() returns a Boolean ty...
# getting-started
h
why
Copy code
readLine().toBoolean() returns a Boolean type
and
Copy code
readLine().toInt() throws a compile type error
r
fun readLine(): String?
returns the type
String?
- i.e. either
null
or a
String
. The extension function
fun String?.toBoolean(): Boolean
is defined with a receiver of type
String?
, so it works. The extension function
fun String.toInt(): Int
is defined with a receiver of type
String
, so it will not work. You need to do
readLine()?.toInt()
, which will return type
Int?
.
There was a discussion of this the other day - it's a bit inconsistent of the stdlib to have different receiver types. I think it was a mistake to define
toBoolean
on
String?
- I think it should have been on
String
.
💯 2
k
Interesting that
String.toInt()
throws an exception if the string is not an Int, but
toBoolean()
just returns
false
if you give it any old rubbish.
r
They were sticking with the semantics of the Java implementation:
Copy code
public static boolean parseBoolean(String s) {
  return "true".equalsIgnoreCase(s);
}
Anything other than
"true"
, including
null
, translates to
false
.
Again, I'd argue that a stricter parsing (as found in
toInt
) is better because you find out faster that "TRUE"
"yes"
(for instance) evaluates to
false
.
👍🏻 1
j
Mmh, the test ignores case, so
"TRUE".toBoolean()
actually does evaluate to
true
https://pl.kotl.in/Xcw95xfyR
r
😳 d'oh, and I actually posted the code with the
equalsIgnoreCase
call...
😆 1
My preference would be:
Copy code
fun String.toBoolean(): Result<Boolean> = when {
    this == "true" -> Result.success(true)
    this == "false" -> Result.success(false)
    else -> Result.failure(IllegalArgumentException("Cannot parse $this to boolean"))
}

fun String.toBooleanOrNull(): Boolean = this.toBoolean().getOrNull()
though that's breaking convention too - but personally I prefer the compiler to rap me over the knuckles about parsing failure.
j
Why use result when you can just throw the exception?
r
I prefer the compiler to rap me over the knuckles about parsing failure
j
I didn't get it the first time 😅 still don't
r
Throwing an exception, this compiles but fails at runtime:
Copy code
val unvalidatedInput = "not parseable"
if (unvalidatedInput.toBoolean()) {
  // do something
}
Returning a
Result<Boolean>
it does not compile, forcing me to think about how to handle unvalidated input before it hits production. Which might just be making a conscious decision to use
unvalidatedInput.toBooleanOrNull() ?: false
- but it's switched me from not thinking about it to thinking about it. (I should have written a test of course, but even then the unthinking thing is to check it with the parseable values, it's easy to forget to write a test with unparseable input. I'd love to be able to claim I'm immune to these sorts of mistakes but bitter experience shows I'm not.)
e
as mentioned the other day, I would generally prefer
.toBooleanStrict()
and
.toBooleanStrictOrNull()
unless you specifically want the legacy
"true"::equals
behavior. that makes the following pairs act analogously:
Copy code
readLine()?.toBooleanStrict()
readLine()?.toInt()

readLine()?.toBooleanStrictOrNull()
readLine()?.toIntOrNull()
1
a
Result
-returning type is not good - it doesn't indicate that the only possible failure is
IllegalArgumentException
r
True, though neither does
fun toBooleanStrict(): Boolean
- the type signature leaves the user to inspect the documentation to find out it fails at all, let alone what it fails with, though the name is an indicator. I like an
Either
type specialised to represent the a success or failure, I even rolled my own (had to call it an
Outcome
as
Result
clashed): https://github.com/Mahoney-playground/goos/blob/1a7207345f98f83caa4f3c5ad03de96f726e1d10/gradle/build-plugins/extract-plugin/src/either/Outcome.kt But for reasons I don't fully understand that seems an unpopular approach in the Kotlin community.
j
Virtually every function can throw an exception or error, though. Railway programming is just not the default Kotlin approach. Using an either type at that level is just making assumptions on the lower levels. If you call any function in yours, your specific either type might be lying too, because the called functions might be throwing exceptions too. You would need the whole tree to follow the same railway approach for it to be really fool proof. The stdlib approach is to use exceptions for exceptional cases that don't necessarily need to be handled, that's why a lot of Kotlin code usually follows this. That said, it doesn't prevent you from using libs like Arrow that may align more with your vision.
r
This would require a longer response, and it's all been said many times already (personally I'd prefer to work on a stack with much less availability and reliance on exceptions than the JVM), but I'm particularly keen on the "railway" approach for in memory transformations from less specific to more specific types. Firstly because I think it's very unlikely that there will be another exception thrown in an in memory transformation like
fun String.toBoolean(): Outcome<IllegalArgumentException, Boolean>
, and secondly because I think they are a major source of bugs - it's just too easy to forget to validate your inputs.