How to get class of Any? variable in Kotlin? - reflection

I want my equals to compare class too and I wrote
override fun equals(other: Any?): Boolean {
return this::class == other::class && ...
}
unfortunately it swears
Expression in a class literal has a nullable type 'Any?', use !! to make the type non-nullable
But I want to compare with nulls too. What about glorious "null safety"? They forgot it for reflection? I didn't find ?:: operator or something.

Think about this. The class is actually not different between a String and a String?, it's only the type that differs. You cannot invoke that operator on nullable types since it could mean you invoke it on null which would lead to a NullPointerException:
val x: String? = null
x!!::class //throws NPE
With the help of the scope function let you can ensure it isn't null and use the class literal syntax:
return other?.let { this::class == other::class } ?: false
The Elvis operator ?: is used to handle the null case by making the expression false (not equal).

Related

Weird behavior with Maps(A nullable expression can't be used as a condition)

Map<String,bool> map= { "key1":true, "key2":false };
/*
* Flags following compilation error:
* A nullable expression can't be used as a condition.
* Try checking that the value isn't 'null' before using it as a condition.
*/
if(map["key1"]) {
//do sth
}
/*So I try checking value isn't null as specified in error
*Still flags same compilation error
*/
if(map!=null && map["key1"]) {
//do sth
}
//This works
if(map["key1"] == true) {
//do sth
}
}
Based on the following snippet, may I know why both the 1st and 2nd if blocks fail but not the 3rd?
You misunderstood the error message.
A nullable expression can't be used as a condition.
means that you can't do:
bool? condition;
if (condition) {
...
}
Map<K, V>'s operator[] returns a V?. It returns a nullable type as a way of indicating failure when the key isn't found, and you need to check that the returned value isn't null, not that map itself is not null. For example:
if (map["key"] ?? false) {
...
}
Your third approach (which checks == true) works because it will perform a null == true equality check if the lookup returns null. However, you should prefer using ?? false since it conveys the intent better, and equality checks against true or false are usually a code smell.
The [] operator on Map can return null which makes it nullable which is explained in details here: https://dart.dev/null-safety/understanding-null-safety#the-map-index-operator-is-nullable
So your first example is invalid since null is not a bool. So you cannot directly use the value from the [] operator for a Map.
Your second example is invalid for the same reason since map["key1"] is bool?.
Third example works since null == true is always false. So it is fully valid to make a comparison which involves something which can be null.

Reflection struct field.Set with a Flag pointer value

I have a bunch of flags parsed, and I'm then trying to assign those values to fields in a struct, but I'm struggling to get a parsed flag value set into the struct because I can't type assert it or cast it.
Here is a snippet of the code I have. It's not important to worry too much about the IterFields function, basically the third argument is called for each field in the struct...
Note: there are comments in the code below which highlight the error(s).
flag.Parse()
IterFields(st, v, func(field reflect.Value, sf reflect.StructField) {
flag.VisitAll(func(f *flag.Flag) {
if f.Name == strings.ToLower(sf.Name) || f.Name == sf.Tag.Get("short") {
fmt.Printf("%+v, %T\n", f.Value, f.Value)
// PRINTS: true, *flag.boolValue
if v, ok := f.Value.(bool); ok {
fmt.Println("ok")
} else {
fmt.Println("not ok")
}
// ERROR: impossible type assertion: bool does not implement flag.Value (missing Set method)
field.Set(reflect.ValueOf(f.Value))
// PANIC: value of type *flag.boolValue is not assignable to type bool
}
})
})
f.Value is an interface type flag.Value abstracting all kinds of flag values. As your code indicates, it's not of type bool but some non-exported *flag.boolValue. You shouldn't be concerned about its dynamic type.
You may use the Value.String() method to get its value as a string, which will be either "false" or "true" for bool types, you may use simple comparison to obtain a bool from it like f.Value.String() == "true".
But a better approach would be: all flag.Value values originating from the flag package also implement flag.Getter which also has a Get() method that will directly return a bool value in case of a bool flag (wrapped in interface{} of course). Just use that:
field.Set(reflect.ValueOf(f.Value.(flag.Getter).Get()))
The above works for fields of any type (given that the flag's value type is assignable to the field's type).
For bool fields only, alternatively you may also use:
field.SetBool(f.Value.(flag.Getter).Get().(bool))

Incrementing the mutable map value results in a nullable receiver error

I am new to Kotlin and tried to google it, but I don't get it.
Example here:
https://try.kotlinlang.org/#/UserProjects/q4c23aofcl7lb155oc307cnc5i/sgjm2olo277atiubhu2nn0ikb8
Code:
fun main(args: Array<String>) {
val foo = mutableMapOf('A' to 0, 'C' to 0, 'G' to 0, 'T' to 0)
foo['A'] = foo['A'] + 1
println("$foo['A']")
}
I don't get it; why does the indexing operator return the nullable type? The map in the example is defined as Map<Char, Int>, not Map<Char, Int?>.
I can override it via non-null assertion, so this works:
foo['A'] = foo['A']!!.plus(1)
Is there a cleaner way?
You can use the index operator with arbitrary chars, even those that are not part of the map, as in not an existing key. There are two obvious solutions to this, either throw an exception or return null. As you can see in the documentation, the standard library returns null in the operator fun get, which the index operator translates to:
/**
* Returns the value corresponding to the given [key], or `null` if such a key is not present in the map.
*/
public operator fun get(key: K): V?
The alternative is getValue which is described like this:
Returns the value for the given [key] or throws an exception if there is no such key in the map.
Used like this: val v: Int = foo.getValue('A')

Manipulate struct field when using interface

The title might be misleading, but to the point...
I have a single interface Expression:
type Expression interface {
String() // skiped in implementation below
}
The interface is implemented by multiple structs, some of which implements same interface as a field value:
type IdentExpression struct {
value string
}
type UnaryExpression struct {
token string
value Expression
}
func (a *UnaryExpression) Simplify() {
var finalValue Expression
switch a.value.(type) {
case UnaryExpression:
tmp := a.value.(UnaryExpression)
switch tmp.value.(type) {
case UnaryExpression:
tmp = tmp.value.(UnaryExpression)
finalValue = tmp.value
}
}
a.value = finalValue
}
Given expression -(-(-(1))), UnaryExpression.Simplify() will simplify the expression to -(1). (play)
I would like to extend the interface with Simplify() method:
type Expression interface {
Simplify()
String() string
}
// ...
func (a IdentExpression) Simplify() {} // do nothing
Resulting code does not work (play):
main.go:29: impossible type switch case: a.value (type Expression) cannot have dynamic type UnaryExpression (missing Simplify method)
main.go:30: impossible type assertion:
UnaryExpression does not implement Expression (Simplify method has pointer receiver)
main.go:59: cannot use UnaryExpression literal (type UnaryExpression) as type Expression in field value:
UnaryExpression does not implement Expression (Simplify method has pointer receiver)
main.go:60: cannot use UnaryExpression literal (type UnaryExpression) as type Expression in field value:
UnaryExpression does not implement Expression (Simplify method has pointer receiver)
I have found this answer, which looks similar however I do not know how to apply it in my case.
The key here is that you are using a pointer receiver in your definition of Simplify() with respect to UnaryExpression:
func (a *UnaryExpression) Simplify()
The other methods you are implementing don't use a pointer receiver:
// One example
func (a IdentExpression) Simplify() {}
Typically, in Go, it is considered best practice to have all methods on the same type use the same type of receiver (i.e. if one method uses a pointer receiver, they all should. Likewise, if one method uses a non-pointer receiver, they all should for that particular type).
In this case, the code will compile if you remove the pointer receiver from the Simplify method of UnaryExpression. Hope this helps!
Edit: Here is a more comprehensive answer that explains exactly why this error happens, it's really a good read.

Is there a way to filter out null Any? values in Kotlin Map?

I'm trying to think of a function that would allow a Map<String, Any?> object to be treated as Map<String,Any> through type inference through applying a single function.
I am pretty new to the transformation functions in Kotlin and have tried the various filter and filterValues filterNot on the map like so:
val input = mapOf(Pair("first",null))
val filtered: Map<String,Any> = input.filter { it.value!=null }
it also fails to compile with any of these
input.filterValues { it!=null }
input.filterNot { it.value==null }
input.filterNot { it.value is Nothing }
The closest I can seem to get is applying multiple steps or having an Unchecked cast warning. I would have thought that filtering the values to be !=null would suffice. My only other thought is that it's due to the generics?
The filter functions return a Map with the same generic types as the original map. To transform the type of the value, you need to map the values from Any? to Any, by doing a cast. The compiler can't know that the predicate you pass to filter() makes sure all the values of the filtered map are non-null, so it can't use type inference. So your best et is to use
val filtered: Map<String, Any> = map.filterValues { it != null }.mapValues { it -> it.value as Any }
or to define a function doing the filtering and the transformation in a single pass, and thus be able to use smart casts:
fun filterNotNullValues(map: Map<String, Any?>): Map<String, Any> {
val result = LinkedHashMap<String, Any>()
for ((key, value) in map) {
if (value != null) result[key] = value
}
return result
}
The compiler just doesn't perform type analysis deep enough to infer that, for example, input.filterValues { it != null } filters out null values from the map and thus the resulting map should have a not-null value type. Basically there can be arbitrary predicate with arbitrary meaning in terms of types and nullability.
There is no special case function for filtering null values out of a map in the stdlib (like there is .filterIsInstance<T>() for iterables). Therefore your easiest solution is to apply an unchecked cast thus telling the compiler that you are sure about the type safety not being violated:
#Suppress("UNCHECKED_CAST")
fun <K, V> Map<K, V?>.filterNotNullValues() = filterValues { it != null } as Map<K, V>
See also: another question with a similar problem about is-check.
This yields no warnings kotlin 1.5.30
listOfNotNull(
nullableString?.let { "key1" to it },
nullableString?.let { "key2" to it }
).toMap()

Resources