Kotlin extension function - compiler cannot infer that nullable is not null - kotlin

Let's say I have a simple class Foo with a nullable String?
data class Foo(
val bar: String?
)
and I create a simple function capitalize
fun captitalize(foo: Foo) = when {
foo.bar != null -> runCatching { foo.bar.capitalize() }
else -> ""
}
which works fine, because the compiler infers that foo.bar cannot be null eventhough it's type is nullable. But then I decide to write the same function as an extension of Foo
fun Foo.captitalize2() = when {
bar != null -> runCatching { bar.capitalize() }
else -> ""
}
and all of a sudden the compiler is no longer able to infer that bar is not null, and IntelliJ tells me that "only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable reciever of type String?"
Can anyone explain why?

I think it's because in the first case you are calling this function:
public inline fun <R> runCatching(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (e: Throwable) {
Result.failure(e)
}
}
but in the second case you are calling function with receiver:
public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
return try {
Result.success(block())
} catch (e: Throwable) {
Result.failure(e)
}
}
For me, it looks like an issue in the Kotlin compiler because if you inline code of this function by yourself it will work fine:
fun Foo.captitalize2() = when {
bar != null -> try {
Result.success(bar.capitalize())
} catch (e: Throwable) {
Result.failure<String>(e)
}
else -> ""
}
btw, if I were you I would like to write my capitalize2 function like this :)
fun Foo.captitalize2() = bar?.capitalize() ?: ""

So, finally I found an alternative approach that allows us to use runCatching without having the problem you shows.
As in my comment to the answer of #Andrei Tanana, in your code type parameters of fun <T, R> T.runCatching(block: () -> R) : Result<R> are inferred as <Foo, String> and the compiler can't use the information that this.bar is not null.
If you rewrite the capitalize2 function as follows
fun Foo.capitalize2(): Serializable = when {
bar != null -> bar.runCatching { capitalize() }
else -> ""
}
T is inferred as String (thanks of the bar != null case of the when expression) and the compiler does not complain about this.capitalize() invocation in the block passed to runCatching.
I hope this can help you, both as an approach than allows you to solve the problem and as explanation of the problem itself.

Related

Kotlin - Infer type for one of two generic parameters

I am trying to create a function that has two generic types: one reified, and another derived from the context of its usage (since it is an extension function):
inline fun <reified E, A> Either<Throwable, A>.bypassLeft(transformation: Throwable.() -> A): Either<Throwable, A> =
when (this) {
is Either.Left -> when (value) {
is E -> value.transformation().right()
else -> this
}
else -> this
}
The idea would be to call the function just mentioning the reified type, something like:
a.bypassLeft<NoResultException> { "" }
In which "a" is an object of type Either<Throwable,String>
But the compiler is not letting me go away with it, and requires me to specify both generic types, instead of deriving the second one form the object calling the function.
It seemed quite a reasonable thing to be possible, but maybe I am wrong...
Is this possible to achieve? If so, what am I doing wrong?
It's not currently possible with a function to ascribe a single type argument and leave the other inferred. You can achieve what you want if you type the lambda arguments by changing your implementation to not use a receiver type.
I threw in there an additional impl that shows how type args can also be partially applied with a class or other surrounding scope.
import arrow.core.Either
import arrow.core.right
inline fun <reified E : Throwable, A> Either<Throwable, A>.bypassLeft(
transformation: (E) -> A //changed to regular arg not receiver
): Either<Throwable, A> =
when (this) {
is Either.Left -> when (val v = value) { //name locally for smart cast
is E -> transformation(v).right()
else -> this
}
else -> this
}
class Catch<A>(val f: () -> A) { //alternative impl with partial type app
inline fun <reified E : Throwable> recover(
recover: (E) -> A
): Either<Throwable, A> =
Either.catch(f).fold(
{
if (it is E) Either.Right(recover(it))
else Either.Left(it)
},
{
Either.Right(it)
}
)
}
suspend fun main() {
val x: Either<Throwable, Int> = Either.Left(StackOverflowError())
val recovered = x.bypassLeft {
s: StackOverflowError -> //here infers E
0 // here infers A
}
println(recovered) // Either.Right(0)
val notRecovered: Either<Throwable, Int> =
Catch {
throw NumberFormatException()
1
}.recover<StackOverflowError> { 0 }
println(notRecovered) // Either.Left(java.lang.NumberFormatException)
}
This is possible as of Kotlin v1.7.0 with the underscore operator.
The underscore operator _ can be used for type arguments. Use it to automatically infer a type of the argument when other types are explicitly specified:
interface Foo<T>
fun <T, F : Foo<T>> bar() {}
fun baz() {
bar<_, Foo<String>>() // T = String is inferred
}
In your example, it would be possible like this:
a.bypassLeft<NoResultException, _> { "" }

Is there a way to distinguish between a function argument's default value having been passed explicitly or implicitly in Kotlin?

Assuming a kotlin function like this:
fun f(p1: T1? = null, p2: T2? = null, ..., pN: TN? = null) {
// ...
}
Can the above function's implementation distinguish between the following two calls, where the first one passed p1 = null implicitly, and the second one passed it explicitly?
f() // Implicit
f(null) // Explicit
f(p1 = null) // Explicit
Note: There could be arbitrary numbers of parameters
No, it cannot distinguish between those cases.
You could distinguish between them if you added a distinct overload, however.
Although I'd rather not use that approach in production, you could do something like I've done in the following snippet:
object Default {
val defaultMapping = mutableMapOf<KClass<*>, Any?>()
inline fun <reified T> get(): T? =
T::class.let {
defaultMapping[it] ?: it.java.constructors.getOrNull(0)?.let { c ->
try {
// NOTE: for now only parameterles constructor will work
c.newInstance()
} catch (e: Exception) {
e.printStackTrace()
null
}.also { v ->
defaultMapping[it] = v
}
} ?: run {
defaultMapping[it] = null
null
}
} as? T
inline fun <reified T> T.isDefault(): Boolean = defaultMapping[T::class] == this
}
inline fun <reified T> foo(bar: T? = Default.get()) {
if (bar?.isDefault() == true) println("bar: default is in use")
else println("bar: $bar")
}
fun main() {
foo<Any>()
foo(Default.get<Any>())
foo<Any>(null)
foo<Any>(bar = null)
foo(Any())
val a = Any()
foo(a)
foo(bar = a)
}
Note, that I have not polished the code in any way. Some parts are leftovers from several attempts (e.g. the part about the constructors.getOrNull(0)) and I don't intend to improve that.
Also: This simple approach only works with default constructors (see it.newInstance()) on the JVM. So that's no multi-platform solution in any way.
The result is something like
bar: default is in use
bar: default is in use
bar: null
bar: null
bar: java.lang.Object#41906a77
bar: java.lang.Object#4b9af9a9
bar: java.lang.Object#4b9af9a9
Again: Keep in mind, this is very simplistic, don't use that in production!

How can you call different versions of similar extension methods with kotlin

I have the following functions to simulate the ternary operator for kotlin
fun Boolean.then(action: () -> Unit): Boolean {
if (this)
action.invoke()
return this
}
fun Boolean.otherwise(action: () -> Unit) {
if (!this)
action.invoke()
}
fun <T> Boolean.then(func: () -> T): T? {
if (this)
return func.invoke()
return null
}
fun <T> T?.otherwise(action: () -> T): T {
return this ?: action.invoke()
}
they are supposed to be used like this :
(check).then { doHello() }.otherwise { doWorld() }
val answer = (check).then { "hello" }.otherwise { "world" }
however when I try to assign a value using the above operators like this:
val visibility: Int = (show).then { View.VISIBLE }.alt { View.GONE }
I get an error saying that the required reply was Int but it actually got Unit which means that it called the first version of the methods instead of the second
Other than renaming the methods (when I changed the first two to thenDo and otherwiseDo it worked), can I write the above code in some way so that the compiler will know to call the second version?
I don't think you need both overloads. If you remove the ones that return Unit, then both your lines of code work:
(check).then { doHello() }.otherwise { doWorld() }
val answer = (check).then { "hello" }.otherwise { "world" }
That's because the first line, where the lambdas return Unit, e.g. doHello(), can still use the generic versions of then and otherwise, as they are still considered functions with a return value, namely Unit.
Although I agree with some the comments above: do you really need this? Why not just use if, which is an expression which returns a value (like the ternary operator). See discussion here for more info.

Conditional side-effects and optional types in Kotlin

I am trying to perform a simple side-effect in Kotlin:
fun handle(request: Request) {
repository.findByUID(request.userId)?.let {
if (someCondition) return
service.run(...)
}
}
As you can see, the side-effect should be performed when the repository returns a non-null value and when someCondition is satisfied.
Is there any Kotlin-way of doing this rather than using if{}-return constructs?
In Java 8, it could be achieved by:
optional
.filter(...)
.ifPresent(...)
Update:
Kotlin 1.1 has a method called takeIf:
/**
* Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
*/
#kotlin.internal.InlineOnly
#SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
You can use it this way:
repository.findByUID(request.userId)?.takeIf { someCondition }?.let { service -> }
Kotlin doesn't contain such method in the stdlib.
However, You can define it:
inline fun <K : Any> K.ifPresent(condition: K.() -> Boolean): K? = if (condition()) this else null
Using this method your example can be rewritten as:
fun handle(request: Request) {
repository.findByUID(request.userId)?.ifPresent { someCondition }?.let {
service.run(...)
}
}
Another option may be to use the built in extensions for list (but there is an overhead of using lists):
listOf(repository.findByUID(userId)).filter { someCondition }.forEach { service.run(...) }
Kotlin's nullable types are very similar to Java's Optional (which is very similar to Guava's Optional).
In Kotlin 1.1 you can use takeIf which "is like filter for a single value" (takeIf() and also() - What's New in Kotlin 1.1 - Kotlin Programming Language):
repository.findByUID(request.userId).takeIf { !someCondition }?.let { service.run(...) }
Kotlin 1.0 does not define map, flatMap, filter/takeIf, etc. for nullable types but you can easily define your own function. e.g.:
inline fun <T> filter(value: T?, predicate: (T) -> Boolean): T? {
return if (value != null && predicate(value)) value else null
}
Example usage:
filter(repository.findByUID(request.userId)) { !someCondition }?.let { service.run(...) }
I would go without extra libs nor extension functions with this construct:
?.let { if (someCondition) null else it }
After aplying this construct on the code sample from the original question, it would look like:
fun handle(request: Request) {
repository.findByUID(request.userId)
?.let { if (someCondition) null else it }
?.let {
service.run {
/* ... */
}
}
}
Or at least it looks OK, compiles and have same types in my codebase after defining Request, repository, findByUid etc. :-)

How do I create an enum from a string in Kotlin?

I have an enum with some instances Foo and Bar. If I have a string "Foo", how can I instantiate a Foo enum from that? In C# it would be Enum.Parse(...), is there an equivalent in Kotlin?
Currently, the best I have found is to create a factory that switches on all possible strings, but that is error prone and performs poorly for large enumerations.
Kotlin enum classes have "static" function valueOf to get enum entry by string(like Java enums). Additionally they have "static" function values to get all enum entries. Example:
enum class MyEnum {
Foo, Bar, Baz
}
fun main(args : Array<String>) {
println(MyEnum.valueOf("Foo") == MyEnum.Foo)
println(MyEnum.valueOf("Bar") == MyEnum.Bar)
println(MyEnum.values().toList())
}
As bashor suggested, use MyEnum.valueOf() but please have in mind that it throws an exception if value can't be found. I recommend using:
enum class MyEnum {
Foo, Bar, Baz
}
try {
myVar = MyEnum.valueOf("Qux")
} catch(e: IllegalArgumentException) {
Log.d(TAG, "INVALID MyEnum value: 'Qux' | $e")
}
Would do it like
enum class MyEnum {
Foo, Bar, Baz
}
val value = MyEnum.values().firstOrNull {it.name == "Foo"} // results to MyEnum.Foo
Reusable Exception Safe Solution
The default solution in Kotlin will throw an exception. If you want a reliable solution that works statically for all enums, try this!
Now just call valueOf<MyEnum>("value"). If the type is invalid, you'll get null and have to handle it, instead of an exception.
inline fun <reified T : Enum<T>> valueOf(type: String): T? {
return try {
java.lang.Enum.valueOf(T::class.java, type)
} catch (e: Exception) {
null
}
}
Alternatively, you can set a default value, calling valueOf<MyEnum>("value", MyEnum.FALLBACK), and avoid a null response. You can extend your specific enum to have the default be automatic
inline fun <reified T : Enum<T>> valueOf(type: String, default: T): T {
return try {
java.lang.Enum.valueOf(T::class.java, type)
} catch (e: Exception) {
default
}
}
Or if you want both, make the second:
inline fun <reified T : Enum<T>> valueOf(type: String, default: T): T = valueOf<T>(type) ?: default
If you want to create an enum value from one of its parameters, instead of the name, this funny code does a pretty decent job:
inline fun <reified T : Enum<T>, V> ((T) -> V).find(value: V): T? {
return enumValues<T>().firstOrNull { this(it) == value }
}
This can be used like this:
enum class Algorithms(val string: String) {
Sha1("SHA-1"),
Sha256("SHA-256"),
}
fun main() = println(
Algorithms::string.find("SHA-256")
?: throw IllegalArgumentException("Bad algorithm string: SHA-256")
)
This will print Sha256
I think the best solution is enumValueOf<T>(String):
enum class MyEnum { Foo, Bar }
#Test
fun test() {
assertEquals(MyEnum.Foo, enumValueOf<MyEnum>("Foo"))
}