Youssef Shoaib [MOD]
05/22/2022, 1:53 PM// Implementation really doesn't matter, neither do the specific collections.
context(Map<K, V>, Set<V>)
fun <K, V> K.checkInclusion(): Boolean = this@Map[this] in this@Set // Check if our key K has a value which is a member of a set of accepted values
context(Set<Int>, Set<String>, Map<List<String>, String>, Map<List<Int>, Int>) // The last receiver is not needed to reproduce
fun reproducer(){
listOf("hello", "world").checkInclusion() // Error: Multiple arguments applicable for context receiver
}
However, if I just let IntelliJ insert explicit type arguments based on what it has inferred, everything compiles successfully:
listOf("hello", "world").checkInclusion<List<String>, String>()
IntelliJ even greys out the type arguments, saying that they're redundant. Is this a bug? Should I file this?elizarov
06/22/2022, 8:02 PMYoussef Shoaib [MOD]
06/22/2022, 8:41 PMShow
from Haskell, but just renamed to `Display`:
interface Display<in F, in A> {
context(SingleDisplay<A>) fun display(f: F): String
}
typealias SingleDisplay<T> = Display<T, T>
// ---------------------- This implementation doesn't really matter for the error
object IntDisplay: SingleDisplay<Int> {
context(SingleDisplay<Int>) override fun display(f: Int): String {
return "Display Int $f"
}
}
object StringDisplay: SingleDisplay<String> {
context(SingleDisplay<String>) override fun display(f: String): String {
return "Display String $f"
}
}
class ListDisplay<E>: Display<List<E>, E>{
context(SingleDisplay<E>) override fun display(f: List<E>): String {
return f.joinToString(prefix = "Display List [", postfix = "]", separator = ", ") { display(it) }
}
}
// ----------------------
context(Display<F, A>, SingleDisplay<A>)
@Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS") // False warning I think
fun <F, A> F.display(): String = display(this)
context(SingleDisplay<Int>, SingleDisplay<String>, Display<List<String>, String>
fun reproducer(){
listOf("hello", "world").display() // Error: Multiple arguments applicable for context receiver
}
I get the same error here. If I, however, make the type parameters for Display
not in
, the code compiles successfully. I think this probably is a legitimate use-case, don't you think? It is maybe pushing Kotlin's type system a bit to its limits, but I'd say that since specifying the type args fixes the issue, then I'd expect it to just work without explicitness.elizarov
06/23/2022, 7:06 AMinterface Display<in F, in A> {
context(SingleDisplay<A>) fun display(f: F): String
}
The above display
function is already declared as a member of Display
. Why would it also need a context of Display
being already declared in the context of Display
? Anyway, it looks like we simply forgot to forbit this kind of declaration to prevent people from running into those complications in the first place https://youtrack.jetbrains.com/issue/KT-52919Youssef Shoaib [MOD]
06/23/2022, 7:14 AMcontext(SingleDisplay<A>)
is for a child display. For instance, ListDisplay therefore requires, in its context, a way to display its elements. A ListDisplay<String>
requires a SingleDisplay<String>
in its context if you would like to call display on it. Obviously, for the trivial case of `IntDisplay`or StringDisplay
, no child display is required, but for a `ListDisplay<E>`or a ResultDisplay<E>
or an OptionalDisplay<E>
, a way to display E is required, which here is modelled by SingleDisplay<E>
(which is just a type alias for Display<E, E>
The implementation of ListDisplay
should show pretty well why a child display is required. Specifically, it's used in the lambda passed to `joinToString`so that we can safely display the inner elements based on the arbitrary implementation provided by the userYoussef Shoaib [MOD]
06/23/2022, 7:15 AMSingleDisplay
be a completely separate interface, and we'd still run into the same issue.
I don't have a compiler in front of me right now, but SingleDisplay could be defined as this:
//Instead of typealias SingleDisplay<A> = Display<A, A>
interface SingleDisplay<in A> {
fun display(a: A): String
}
// And remove the context(SingleDisplay) from IntDisplay and StringDisplay, but keep it in ListDisplay
And it would still give the same error, because in reality the error comes from a ambiguity of whether SingleDisplay<String>
or SingleDisplay<Int>
is the right context for the call inside reproducerYoussef Shoaib [MOD]
06/23/2022, 7:18 AMelizarov
06/23/2022, 7:23 AMcontext
clauses from the SingleDisplay.display
function:
https://gist.github.com/elizarov/1d92bff4723ea8d7bd138e46e46fc772elizarov
06/23/2022, 7:24 AMSingleDisplay<T>
to SingleDisplay<in T>
brings the error backYoussef Shoaib [MOD]
06/23/2022, 7:29 AMin
variance is not necessarily crucial for this example, but I can imagine that for a more complex inheritance chain (e.g. if we were displaying a sealed class hierarchy) that the variance would be convenient.
I'm still convinced though that context receivers resolution should just "figure this out" and not consider it ambiguous even though I can use a few inelegant hacks to work around it.elizarov
06/23/2022, 9:01 AMelizarov
06/23/2022, 10:09 AMDisplay
type parameters are underconstrained. There is no relation between a collection and its element type, so there is no way for compiler to pick the correct context.Youssef Shoaib [MOD]
06/24/2022, 7:57 PMMap Set
example and the Display
example the issue is due to carelessly using functions that have UnsafeVariance
(in the case of Set
, Set.contains
has unsafe variance, and in the case of Display
, I got no error for this but implicitly the SingleDisplay<A>
context receiver is actually using the wrong variance, but I guess the checkers for that in the compiler aren't implemented yet).
In fact, since in most Display implementations I would be unpacking items from some sort of container (List, Set, Result, etc.) and then passing them to the SingleDisplay<A>
, A
actually needs to be invariant because display
takes in
As (as objects inside F
) and also `out`puts `A`s into the outside world (by calling SingleDisplay<A>.display
). Making it invariant does seem to fix the issue, and in fact I think the safest option here is to make everything invariant and then carefully see if variance can be added without breaking inference.
In fact, it looks like just making A
invariant still lets code like this compile:
context(SingleDisplay<CharSequence>, Display<List<CharSequence>, CharSequence>)
fun foo() {
listOf("hello", "world").display()
}
Thanks, by the way, for taking the time to look at this issue. It's quite helpful and reassuring that even the lead language designer takes time to look through and respond to design questions so frequently! I really appreciate your help and time!elizarov
06/27/2022, 9:37 AM