Kotlin avoid smart cast for null check - kotlin

So I'm trying to reduce this code and avoid the smart cast hint from IDE.
The idea is I have a nullable variable of type T and I want to either map it to R or I just get R from a supplier in case the variable is null.
I've tried different approaches and came up with this one. Still it gives me the smart cast hint.
fun <T, R> T?.func(mapper: (T) -> R, supplier: () -> R): R =
when(this) {
null -> supplier()
else -> mapper(this) // smart cast
}
But I don't like the need for wrapping one of the lambdas in parenthesis. For example.
fun foo(value: String?): Int =
value.func({ it.length + 20}) { 30 }
This may seem odd but the ideia in my context was to pass the variable as not nullable to a function that produced a R or call a function that generated a R.
fun bar(value: T?): R =
when(value) {
null -> func1()
else -> func2(value) // smart cast
}
Note: I've read this but its not the same.

Following should avoid the smart cast hint
fun <T, R> T?.func(mapper: (T) -> R, supplier: () -> R): R {
return this?.let { mapper(it) } ?: supplier()
}

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 any difference in null checking quality between `?.apply`, `?.run` and `?.let` in Kotlin?

I know the convention is to use ?.let for null checking mutable variables because ?.let will make sure that the variable we're checking doesn't change to null in the middle of the block that's being executed. Will the same hold true for ?.apply and ?.run?
One last thing, if the variable is immutable is it recommended to just use a simple if?
Is there any difference in null checking quality between ?.apply, ?.run and ?.let in Kotlin?
Yes, they're all essentially the same when it comes to null checking quality. In fact, if you open the code for the apply, let, with, also & run. They're 'nearly' identical, they mainly differ on how the block get's executed, what argument is passed to the block and what value is returned.
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
They're really just syntactic sugar, that said it'd be a good idea for you to follow some basic rules/conventions on when to use what. Take a look at article I & article II, they explain the difference between them in much greater detail than I can elaborate in this answer, along with basic conventions on when to use what.
if the variable is immutable is it recommended to just use a simple if?
Yes, in fact, if you make an if check on a val variable, then the compiler will automatically understand that the variable will never be null inside the if block.
val user: User? = null;
if (user != null) {
// user not null
val name = user.name // won't show any errors
}
var user: User? = null;
if (user != null) {
// user might be null
// Since the value can be changed at any point inside the if block (or from another thread).
val name = user.name // will show an error
}

Replace lamda in an extension function

This is an extension function:
fun <T, R> Collection<T>.fold(initial: R, combine: (acc: R, nextElement: T) -> R): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
Is it possible to replace the second parameter which is a function with a separate function. For example, something that would look similar to this:
fun <T, R> Collection<T>.fold(initial: R, someFun)
fun someFun (acc: R, nextElement: T) -> R): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
You can use two colons to pass reference to the function:
var collection = listOf<String>()
collection.fold(3, ::someFun)
fun <T, R> someFun(acc: R, nextElement: T): R {
var accumulator: R = acc
// ...
return accumulator
}
I'm not sure why do you need to extract a function this way. The desired code in question does not compile and to propose a working alternative it's required to know your actual intent.
For example if you don't want to spell a long function signature in the type of a parameter, perhaps because you have a lot of such functions taking that type of function parameter and you afraid of making a mistake in that signature, you can extract the functional type declaration into a type alias:
typealias Combiner<R, T> = (acc: R, nextElement: T) -> R
and then use that type alias in the function declaration:
fun <T, R> Collection<T>.fold(initial: R, combine: Combiner<R, T>): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}

Can functions be default parameter values?

Kotlin docs states that "functions are first-class". I'm trying to use a function as a default value of a function extension. However the compiler isn't having any of it:
fun <T> identity(x: T): T = x
fun <T, P> Channel<T>.dedupe(by: (T) -> P = ::identity): ReceiveChannel<T>
{
...
}
The error is Function invocation 'identity(...)' expected which kinda indicates Kotlin isn't really understanding what I want to do at all.
Is there a way?
I don't know why you get this error message, but the problem is type mismatch: the default value must make sense for any type parameters (subject to bounds). I.e. you need a (T) -> P, but ::identity can give you (T) -> T or (P) -> P.
Proof: if you change to
fun <T, P> identity(x: T): P = throw Exception()
fun <T, P> List<T>.dedupe(by: (T) -> P = ::identity): Unit {}
it compiles.
Answer (which came out in comments below):
If P is changed to Any?, we should be able to use ::identity because (T) -> T is a subtype of (T) -> Any?. Unfortunately, it doesn't work, but using a lambda instead of a function reference does:
fun <T> identity(x: T): T = x
fun <T> Channel<T>.dedupe(by: (T) -> Any? = { it }): ReceiveChannel<T>
{
...
}

Kotlin Platform Types and Generics

I am stuck at the last Kotlin Koans task 28 where I get these error messages when I try to call my partitionTo function:
Error:(25, 12) Kotlin: Type inference failed. Expected type mismatch: found: kotlin.Pair<kotlin.Collection<kotlin.String!>, kotlin.Collection<kotlin.String!>> required: kotlin.Pair<kotlin.List<kotlin.String>, kotlin.List<kotlin.String>>
Error:(30, 12) Kotlin: Type inference failed. Expected type mismatch: found: kotlin.Pair<kotlin.Collection<kotlin.Char!>, kotlin.Collection<kotlin.Char!>> required: kotlin.Pair<kotlin.Set<kotlin.Char>, kotlin.Set<kotlin.Char>>
I read that the exclamation mark at the end of a type marks a platform type. But then I would have expected the type java.lang.String! and not kotlin.String!. Do I have to enforce null checks somewhere? Maybe someone can help me with this last task. I am using IntelliJs Kotlin Plugin version 0.10.195.
This is my code:
fun List<String>.partitionWordsAndLines(): Pair<List<String>, List<String>> {
return partitionTo(ArrayList<String>(), ArrayList<String>()) { s -> !s.contains(" ") }
}
fun Set<Char>.partitionLettersAndOtherSymbols(): Pair<Set<Char>, Set<Char>> {
return partitionTo(HashSet<Char>(), HashSet<Char>()) { c -> c in 'a'..'z' || c in 'A'..'Z'}
}
inline fun <reified T> Collection<T>.partitionTo(first: MutableCollection<T>, second: MutableCollection<T>, predicate: (T) -> Boolean): Pair<Collection<T>, Collection<T>> {
for (element in this) {
if (predicate(element)) {
first.add(element)
} else {
second.add(element)
}
}
return Pair(first, second)
}
The problem is that you promised to return a pair of Lists:
fun List<String>.partitionWordsAndLines(): Pair<List<String>, List<String>> {
But in fact returned a pair of Collections:
inline fun <reified T> Collection<T>.partitionTo(...): Pair<Collection<T>, Collection<T>> {
A useful hint from the task:
The signature of the function 'toCollection()' from standard library may help you.
See it here: https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/generated/_Snapshots.kt#L207
P.S. Why did you want to use inline and reified on partitionTo?
Check you return types partitionWordsAndLines(): **Pair, List>**, extensions require List or Set where partitionTo returns Collection
Here is fixed version
inline fun <reified T, C: MutableCollection<T>> Collection<T>.partitionTo(first: C, second: C, predicate: (T) -> Boolean): Pair<C, C>