Why is the key type parameter of a Kotlin Map invariant? - kotlin

The Map interface in Kotlin (using V1.6.21) has a signature of
interface Map<K, out V>
Why is K invariant instead of covariant (out K)?
The documentation of type parameter K says:
The map is invariant in its key type, as it can accept key as a parameter (of containsKey for example) and return it in keys set.
However, interface Set is covariant in the element type, so the the last part ("return it in keys set") is not applicable, at least not immediately.
Further, the type parameter K is used only at occurrences where the map state is not modified, for lookup purposes (methods containsKey, get, getOrDefault). At these places, isn't it safe to use #UnsafeVariance? After all, that same technique was employed to Map's value type parameter V, for example in containsValue, to allow making V covariant.

My guess would be that using a Map<KSubtype, V> as a Map<KSupertype, V> (where KSubtype : KSupertype) does not really make a lot of sense because the former, by construction, cannot contain entries with keys other than KSubtype.
So a proper implementation should return null from all calls to get(kSupertype) as well as return false from those to containsKey(kSupertype).
In the case of Set<out E> it's only the contains function that needs unsafe variance, and Map would also require unsafe variance on get. This might have been too much of a peculiarity to support, compared to the value of supporting the use case.

Related

What are nullable rules when calling Java from Kotlin

Why does Kotlin in one case infer type returned from Java to be nullable and in another case it is can be either, nullable or non-nullable?
I've checked both HashMap.get and JsonNode.get and I could not identify any #NotNull-like annotations neither in calsses nor anywhere in inheritance chain. What makes Kotlin treating those 2 calls differently?
I have read documentation https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types but it explanation use "Platform Types" without explaining what those are and it does not explain differences in behavior anyway.
import com.fasterxml.jackson.databind.JsonNode
private fun docType(node: JsonNode, map: java.util.HashMap<String,String>) {
val x: JsonNode = node.get("doc_type") // DOES compile and can throw NPE at runtime
val y: JsonNode? = node.get("doc_type") // DOES compile and Kotlin's type system will force you to check for null
val z: String = map.get("a") // ERROR: Type mismatch: inferred type is String? but String was expected
}
Kotlin provides seamless interoperability with Java, without compromising its own null-safety... almost. One exception is that Kotlin assumes that all types that are defined in Java are not-null.
To understand, let's look at JsonNode.get()
Platform types
public JsonNode get(String fieldName) { return null; }
Note that JsonNode is defined in Java, and is a therefore 'platform type' - and Kotlin does not 'translate' it to JsonNode?, even though that would be technically correct (because in Java all types are nullable).
When calling Java from Kotlin, for convenience it's assumed that the platform type is non-nullable. If this wasn't the case, you would always have to check that any instance of any platform type is not null.
So, to answer your question about what a 'platform type' is, it's a term that means
some type that is defined in an external target language,
you can't mention it explicitly in Kotlin code (but there's probably a synonymous Kotlin equivalent),
and we're going to assume that it's non-nullable for convenience.
Also the notation is <type>!, for example String! - which we can take to mean String or String?
Nullability annotations
The closest Java equivalent of Kotlin's nullable ? symbol are nullability annotations, which the Kotlin compiler can parse and take into account. However, none are used on JsonNode methods. And so Kotlin will quite happily assume that node.get("") will return JsonNode, not JsonNode?.
As you noted, there are none defined for HashMap.get(...).
So how does Kotlin know that map.get("a") returns a nullable type?
Type inference
Type inference can't help. The (Java) method signature
public V get(Object key) {
//...
}
indicates that a HashMap<String, String> should return String, not String?. Something else must be going on...
Mapped types
For most Java types, Kotlin will just use the definition as provided. But for some, Kotlin decides to treat them specially, and completely replace the Java definition with its own version.
You can see the list of mapped types in the docs. And while HashMap isn't in there, Map is. And so, when we're writing Kotlin code, HashMap doesn't inherit from java.util.Map - because it's mapped to kotlin.collections.Map
Aside: in fact if you try and use java.util.Map you'll get a warning
So if we look at the code for the get function that kotlin.collections.Map defines, we can see that it returns a nullable value type
/**
* 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?
And so the Kotlin compiler can look at HashMap.get(...) and deduce that, because it's implementing kotlin.collections.Map.get(...), the returned value must be a nullable value, which in our case is String?.
Workaround: External annotations
For whatever reason, Jackson doesn't use the nullability annotations that would solve this problem. Fortunately IntelliJ provides a workaround that, while not as strict, will provide helpful warnings: external annotations.
Once I follow the instructions...
Alt+Enter → 'Annotate method...'
Select 'Nullable' annotation
Save annotations.xml
Now node.get("") will show an warning.
This annotation isn't visible to the Kotlin compiler, so it can only be a warning - not a compilation error.
java.util.HashMap.get implements the interface method java.util.Map.get. Kotlin maps some Java types to its own types internally. The full table of these mappings is available on the website. In our particular case, we see that java.util.Map gets mapped internally to kotlin.collections.Map, whose get function looks like
abstract operator fun get(key: K): V?
So as far as Kotlin is concerned, java.util.Map is just a funny name for kotlin.collections.Map, and all of the methods on java.util.Map actually have the signatures of the corresponding ones from kotlin.collections.Map (which are basically the same except with correct null annotations).
So while the first two node.get calls are Java calls and return platform types, the third one (as far as Kotlin is concerned) is actually calling a method Kotlin understands: namely, get from its own Map type. And that type has an explicit nullability annotation already available, so Kotlin can confidently say that that value can be null and needs to be checked.

how to read kotlin type annotations

I'm coming to kotlin after working in mostly dynamically typed languages for years, so I get a lot of what I'm seeing, but I'm still tripping up a bit over reading some of the type annotations.
Most of them make sense (I've written some C++ and typescript so I'm not wholey familiar with more strictly type languages). so stuff like annotating the parameters and return types for functions, variable declaration, stuff like that makes sense.
What I'm having trouble with is the more complex annotations like looking at this explanation of the fold method when talking about higher order functions:
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
}
I get that:
the Collection refers to an arbitrary collection with elements that are of type T
the fold method call takes an value of type R named initial as the first argument and a callable function labeled combine as the second argument
the callable function will be called for each element of the collection with an accumulator of type R labeled acc and the next element of the collection of type T (since it's a collection of Ts) labeled nextElement
The callable function will return a type R in the end
The fold method will return a type R in the end
And I can use it like this:
val greetings = listOf("hey", "hi", "yo", "what's up")
val personalized = greetings.fold("", { carry, current -> "${carry}\n$current, Chris." })
println(personalized)
That all makes sense, but what does the <T, R> between the fun and the Collection mean? What is that part called? (It's hard to search for an explanation when you don't know what the thing you're looking for is called :P)
And more importantly, is there a section of the documentation that specifically talks about how to read these annotations or what each are called? I've been looking through the docs and searching in general for an explanation of how to read the type annotations and I can't find anything.
It feels like a silly question, but to the uninitiated it's kind of daunting and the docs are written as if you already understand that part of the language.
As Alexey already said, these names between angled brackets after the fun keyword are called "type parameters". They are used to declare generic functions.
the Collection refers to an arbitrary collection with elements that are of type T
Here you can see that Collection and T play different roles: Collection is a well-known defined type that you are referencing, while T is just a name that you arbitrarily choose for the definition of this function.
We want the compiler to check that Collection is a type that is defined and imported, and if you make a typo there will be a compile error.
On the other hand, we don't want that for T and R, so it is necessary to mention them in a special syntactic place so that the compiler knows you're just making up arbitrary names for the sake of the function definition.
It is nice to draw a parallel between the type parameters and the method arguments. The method arguments are also arbitrary names that you define in the signature and use in the function body, as opposed to class members like properties, which you can access without declaring them as arguments.
Just like the values of the arguments are passed when you call a method, and can be different for each different invocation, the "values" of the type parameters are also given at the call site, and can be different for each invocation (they are often inferred, though, so you don't see them).
Note that the "value" of a type parameter is a type (e.g. String), not a value in the usual sense like the string "abc". You can actually specify these types explicitly on the call site if you want:
listOf(1, 2, 3).fold<Int, Int>(42) { acc, e -> acc + e }
The syntax on the call site is similar to the declaration site, it uses <>, except that it's written after the function name.
In general, these types are easily inferred by the compiler using the argument types or the return type in the context of the call site, that's why it's often unnecessary to explicitly specify them.
Difference with generics at the class level
It may seem weird that the methods in the interface List don't need to declare such type parameters, despite the fact that they use generic types:
interface MutableList<T> {
fun add(element: T): Boolean {
//....
}
}
This is because T is already "well-defined" when using it for the method declaration: it was already defined as a type parameter for the List interface itself. The mechanism is the same, but the difference is the scope of the definition: class-level type parameters are defined by the instance of the class (you can create a List<Int> or a List<String>, and this is chosen when you create your instance), while function type parameters are defined by each call to the function.
You can even combine both:
interface List<T> {
fun <R> map(transform: (T) -> R): List<R> {
//...
}
}
Here T will be determined by the list instance on which you call map, but R can be different for each call to map even on the same list instance.
<T, R> are the type parameters. Since you are familiar with C++, it's like
template <typename T, typename R>
It just happens to be placed after the fun keyword in Kotlin (and after the type name when declaring a generic class/interface/type alias) instead of before the function definition.

Kotlin non nullable map allows remove null

Why this code can be compiled and executed without erros?
val map = HashMap<Int, Long>()
val key :Int? = null
map.remove(key)
In MutableMap remove declared as accepting only non nullable key, so it shouldn't even compile. Is it a Kotlin type inference bug or am I missing something?
public fun remove(key: K): V?
Your code is perfectly fine as remove() allows nullable arguments - your map contents definition got nothing to it. When remove() is invoked, it would try to find matching requested key in the map and as it's not there (it's completely irrelevant why it's not there - it's valid case for key to be not present) nothing will happen. Where compiler will complain is on any attempt to put such key into your map. Then map definition kicks in and since it's known that nullable keys not allowed, such code won't even compile as this is clearly buggy code.
In this case, map.remove(key) doesn't not calls
public fun remove(key: K): V?
It calls an extension remove function:
public inline fun <#OnlyInputTypes K, V> MutableMap<out K, V>.remove(key: K): V? =
#Suppress("UNCHECKED_CAST") (this as MutableMap<K, V>).remove(key)
This function documentation says that it allows to overcome type-safety restriction of remove that requires to pass a key of type K.
It allows overcoming type-safety restriction because the key of the entry you are removing does not have to be the same type as the object that you pass into remove(key); the specification of the method only requires that they be equal. This follows from how the equals() method takes in an Any as a parameter, not just the same type as the object.
Although it may be commonly true that many classes have equals() defined so that its objects can only be equal to objects of its own class, there are many places where this is not the case. For example, the specification for List.equals() says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So, for example, according to the specification of the method, it is possible to have a MutableMap<ArrayList<Something>, Something> and call remove(key) with a LinkedList as an argument, and it should retrieve the key which is a list with the same contents. This would not be possible if this extension remove(key) didn't exist.[1]
Kotlin could warn or refuse to compile (would be good), but it doesn't (for now).
The reason for it being not as bad as it looks from a first glance is that you cannot put an Int? into a MutableMap<Int, Long> because
val map = HashMap<Int, Long>()
val key :Int? = null
map.put(key, 1) // <--- WON'T COMPILE [Type mismatch: inferred type was Int? but Int was expected]
map.remove(key)
Nevertheless, I think you are right by wondering about that method being compiled.
Eventually asking this question helped to find another question with explanation. In short, what actually happens is call of the extension function which have it's own type inference.

Can Functor instance be declared with additional type restriction for function

I'm working on porting GHC/Arr.hs into Frege.
Array is defined:
data Array i e = Array{u,l::i,n::Int,elems::(JArray e)}
There is function:
amap :: (Ix i, ArrayElem e) => (a -> b) -> Array i a -> Array i b
Now, I don't know how to define Functor instance for it, because
instance (Ix i) => Functor (Array i) where
fmap = amap
But compiler complains that inferred type is more constrained that expected, what seems true. Can I make Array an functor with restrction for functions ArrayElem -> ArrayElem?
No, this is not possible.
If you base Array on JArray and want a functor instance, you must not use any functions that arise the ArrayElem (or any other additional) context.
Another way to say this is that you cannot base Array on type safe java arrays, but must deal with java arrays of type Object[]. Because, as you have without doubt noted, the ArrayElem type class is just a trick to be able to provide the correct java type on creation of a java array. This is, of course, important for interfacing with Java and for performance reasons.
Note that there is another problem with type safe java arrays. Let's say we want to make an array of Double (but the same argument holds for any other element type). AFAIK, Haskell mandates that Arrays elements must be lazy. Hence we really cannot use the java type double[] (to which JArray Double would be the Frege counterpart) to model it. Because, if we would do this, every array element would have to be evaluated as soon as it is set.
For this reason, I suggest you use some general custom array element type, like
data AElem a = AE () a
mkAE = A ()
unAE (AE _ x) = x
derive ArrayElement AElem
and change your definition:
data Array i e = Array{u,l::i,n::Int,elems::(JArray (AElem e))}
Now, your functor instance can be written, because the ArrayElem constraint does not arise, because when you access the elems array, the compiler knows that you have AElem elements and can and will supply the correct instance.
In addition, construction of AElems and usage of AElems as actual array elements does not impose strictness on the actual value.
Needless to say, the user of the Array module should not (need to) know about those implementation details, that is, the AElem type.

Is every method returning `this` a monad?

Is every method on a class which returns this a monad?
I'm going to say a very cautious "possibly". A lot of this is contingent on your definitions.
It's worth noting that I'm taking the definition of monad from the category theory construct, not the functional programming construct.
If you think of a method A of class C that maps a C instance to another C instance (i.e. it returns this), then this would appear that C.A() is a functor from the category consisting of C instantiations to itself. Therefore it's an endofunctor, at least. It would appear that this construction obeys the basic identity and associativity properties that we expect, but further inspection would be required to say for sure.
Anyway, I wouldn't stake my life on it, and I'm not certain this is a very helpful way about thinking of such constructions, but it does seem a reasonable assumption on first inspection, at least.
I have limited understanding of monads. I can't tell if that meets the formal definition of a monad (I don't think so, but I don't know for sure), but return this; alone doesn't allow any of the cool things monads allow (fluid interfaces are nice, but not monads imho and nowhere as useful as even simple monads like the option type monad).
This snippet from wikipedia seems to say "no":
Formally, a monad is constructed by defining two operations (bind and return) and a type constructor M [... further restrictions we don't need here]
Edit: Moreover, a monad is a type and not an operation (e.g. method) - the question should rather read "Is a class a monad if all of its methods return this?"</nitpick >
Probably not, at least not in any of the usual ways.
Monads in programming are typically defined over a category of types with functions as arrows. In that case, a method returning this is an arrow from the class to itself--this is an endomorphism with the usual monoid of function composition, but is not a functor.
Note that functors involving function types are certainly possible, but a functor F(A) => (A -> A) doesn't really work because the type appears in both covariant and contravariant position, that is, given a function A -> B you can send A -> A to A -> B, or you can send B -> B to A -> B, but you can't get a B -> B from A -> A or vice versa.
However, there is one way to view instances as having monadic structure. Consider that instance methods effectively have this as an implicit argument. So for some class C, its methods are functions from C to whatever other type. This corresponds roughly to the covariant function functor above. Note that I'm not describing any particular class here, but the entire concept of classes and instances! So, for this mapping from C to instance methods of C:
If we have an instance method returning some type A and a function with type A -> B, we can trivially define a method returning something of type B: that's the rest of the functor definition, a.k.a. 'fmap` in Haskell.
If we have some value of type A, we can add a trivial instance method that just returns that value: that's the monad's "unit" operation, a.k.a. return in Haskell.
If we have an instance method returning a value of type A, and another instance method taking an argument of type A and returning a value of type B, we can define a method that simply returns a value of type B by combining them. That's the monadic bind, a.k.a. (>>=) in Haskell.
Haskell calls the monad of "functions that all take a first argument of some fixed type" the Reader Monad, and the do notation for it lets you write code where that first argument is implicitly available--rather like the way that this is implicitly available inside instance methods.
The difference here is that with class instances, the monadic structure is... sort of at the level of the syntax, not something you can use directly in a program, at least not in most languages.
In my opinion, No.
There are at least two issues I see with it.
A monad is often a glue between two functions. In this case methodA returns a type on which the next methodB is invoked, (and of course methodA and methodB both belonging to the same type).
A monad is supposed to allow type transformations. So if functionA returns TypeX and functionB expects TypeY, the monad needs to provide a bind operation which can convert a Monad(TypeX) into a Monad(TypeY). The monad then goes on to take the return value of the first function, wrap it as a Monad(TypeX), transform it to Monad(TypeY) from which TypeY would get extracted and fed into functionB.
A method which returns this is actually an implementation of Fluent Interface. And while many have argued it to be a monadic as well, I would only say that while it helps resolve problems similar to what monads could otherwise solve, and while the solution would seem similar to how a monadic solution might work (instead of the "." operator, the bind method of the monad has to be invoked without any explicit do block), it is not a monad. In other words it may walk like a monad and talk like a monad, but it is not a monad.
Slight Correction to point 2: The monad needs to provide mechanisms to a) convert TypeX into Monad(TypeX), transform from Monad(TypeX) to Monad(TypeY) and a coercion from Monad(TypeY) to TypeY