4

I am trying to get a class, which combines list, set and map in Kotlin. I wished to write isScalar function, which should return true if object contains only one element and wrote

import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap
import it.unimi.dsi.fastutil.objects.ReferenceArrayList
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet

class Args {

    var list : ReferenceArrayList<M>? = null

    var set : ReferenceOpenHashSet<M>? = null

    var map : Reference2ReferenceOpenHashMap<M, M>? = null

    fun isEmpty() : Boolean {
        return list === null && set === null && map === null
    }

    fun isScalar() : Boolean {
        if(list !== null && list.size == 1) {
            return true
        }
    }

}

Unfortunately it gave me error in comparison

list !== null && list.size == 1

saying

Smart cast to 'ReferenceArrayList<M>' is impossible, because 'list' is a mutable property that could have been changed by this time

As far as I understood, this is related with multithreaded assumption. In Java I would make function synchronized if would expect multithreding. Also, I would be able to disregard this at all, if I am not writing thread-safe.

How should I write in Kotlin?

I saw this solution https://stackoverflow.com/a/44596284/258483 but it expects MT, which I don't want to. How to avoid smart casting if it can't do it?

UPDATE

The question is how to do this in the same "procedural" form. How not to use smart casting?

UPDATE 2

Summarizing, as far as I understood, it is not possible/reasonable to explicitly compare variable with null in Kotlin at all. Because once you compare it, next time yous hould compare it with null again implicitly with such operations like .? and you can't avoid this.

Dims
  • 47,675
  • 117
  • 331
  • 600
  • The solution you saw doesn't "expect MT", it explains why Kotlin's static analysis works as it does. `How to avoid smart casting if it can't do it?`---by not requesting it. Your `list.size == 1` requests a smart cast which Kotlin refuses. – Marko Topolnik Jan 28 '18 at 19:08
  • Making a method `synchronized` wouldn't be nearly enough to make it impossible to update an instance variable from another thread. In fact, there is nothing you can do to stop it. – Marko Topolnik Jan 28 '18 at 19:20
  • @MarkoTopolnik so how not to request smart casting? – Dims Jan 28 '18 at 19:27
  • You need help with the syntax? `list!!.size` will avoid a smart cast. So will `list?.size`. – Marko Topolnik Jan 28 '18 at 19:30
  • @MarkoTopolnik incredible, I am very confused – Dims Jan 28 '18 at 19:32
  • 1
    It's hard to find where exactly you need help. Your `list` is a nullable type, you can't just dereference it. – Marko Topolnik Jan 28 '18 at 19:34
  • I already ensured it is not `null`, then I can dereference it. – Dims Jan 28 '18 at 19:42
  • You can't ensure an instance variable is not `null`. The next time you load it, it may have become `null`. There is nothing you can do to make that _theoretically impossible_. – Marko Topolnik Jan 28 '18 at 19:43
  • What's the difference with other values? Suppose I checked variable is equal to `2`. Next line I also can't be sure it is still `2` because somebody could change it. Hence, no programming is possible at all! – Dims Jan 28 '18 at 21:03
  • 1
    All you have to do is load an instance variable into a local variable. – Marko Topolnik Jan 28 '18 at 21:20

3 Answers3

6

If you take advantage of the fact that null cannot equal 1 (or anything else, really), you can make this check very concise:

fun isScalar() : Boolean =
    list?.size == 1

When a null-safe call to list.size returns null, we get false because 1 != null. Otherwise, a comparison of whatever value size returns is made, and that works as you would expect.

By using the null safe operator (?.) you are avoiding a smart cast entirely. Kotlin gives us smart casts to make code cleaner, and this is one of the ways it protects us from misuses of that feature. Kotlin isn't going to protect us from everything (division by zero, the example you use in comments, for example). Your code is getting caught up in a legitimate case of where smart casting can go wrong, so Kotlin jumps in to help.

However, if you are absolutely sure there are no other threads working, then yes, this check is "wrong". You wouldn't need the warning in that case. Judging by this thread on kotlinlang.org, you aren't the only one!

Todd
  • 30,472
  • 11
  • 81
  • 89
  • Yes, I understand that there is `.?` operator, but I want to understand, how to decompose – Dims Jan 28 '18 at 19:29
  • What do you mean by "how to decompose"? I'm not following you. There's no way to turn off the check that another thread could come in and change the meaning of `list`, so you have to work around it by not requesting a safe cast. – Todd Jan 28 '18 at 19:30
  • No. There is no way to turn that off. However the code from both answers (me and @zsmb13) are both idomatic Kotlin. – Todd Jan 28 '18 at 19:33
  • What is the purpose of message saying "smart cast is impossible" with simulatneously having smart cast is unavoidable? – Dims Jan 28 '18 at 19:35
  • suppose another code `if( a != 0 ) { b = c / a }` and a compiler which swears that it may cause division by zero because some thread can change `a` to zero between check and execution! this destroys all programming logic at all! – Dims Jan 28 '18 at 19:36
  • Because you aren't smartcasting by using `?.let` or `?.size`, you are doing nullsafe traversals. No smart cast happens there. – Todd Jan 28 '18 at 19:37
  • `a` isn't being cast in that case, it's always (presumably) an `Int`. So the compiler isn't looking to protect you from a common source of errors in that case. It is trying to protect you when you smart cast (which again, this isn't). – Todd Jan 28 '18 at 19:38
  • Division by zero is relatively common source of errors. Why not compiler protects me from it with special version of division? Why if protects me from `null` even against my will? – Dims Jan 28 '18 at 19:39
  • 1
    You should address these questions at the Kotlin creators. They made certain design choices that they saw as useful. For me, null-safety is great. For you, it isn't. This is no place for either arguing over it or asking Kotlin users to answer in the name of its creators. – Marko Topolnik Jan 28 '18 at 20:40
  • I need explanation. Null safety may be good, but neither service is good, if it is a must. Anyway, your answer looks best for me, because you used implicit checking for `null` inside `.?` only. – Dims Jan 28 '18 at 21:09
  • 1
    Null safety is no different from any other static analysis you care to name. They all follow strict formal rules and can never be _complete_, i.e., have no false positives and no false negatives. That is a _theoretical impossibility_. Furthermore, there is no statically typed language out there that allows you to disable any formal rule at any time There are always some rules that are "a must" and the designers have a very hard time finding the right balance between safe strictness and dangerous laxness. – Marko Topolnik Jan 29 '18 at 07:40
4

You can perform the null check, and if it succeeds, access a read-only copy of your variable with let:

fun isScalar() : Boolean {
    return list?.let { it.size == 1 } ?: false
}
  • If list is null, the entire let expression will evaluate to null, and the right side of the Elvis operator (false) will be returned.
  • If list is not null, then the let function is called, and result of the it.size == 1 expression is returned - it refers to the object that let was called on (list in this case). Since it's used with a safe call, this it will have a non-nullable type and size can be called on it.
zsmb13
  • 85,752
  • 11
  • 221
  • 226
2

I had the same problem in the given lines

 sliderView.setSliderAdapter(adapter!!)
 sliderView.setIndicatorAnimation(IndicatorAnimationType.WORM)

Finally, error resolved by adding !!

 sliderView!!.setSliderAdapter(adapter!!)
 sliderView!!.setIndicatorAnimation(IndicatorAnimationType.WORM)
anwar alam
  • 582
  • 5
  • 19
  • You should always try to avoid (!! not-null assertions) as it can lead to app crashes and weird behavior. Only use it when you are sure that the given value will never be null otherwise it will throw a NPE – Donki Nov 11 '21 at 22:11