I sometimes have scenarios where a function is using two variables of the same type, like two integers that represent different things. Scala seems to have a hard time with this.
def f(using evens: Seq[Int], odds: Seq[Int]) = {}
given evens: Seq[Int] = Seq(2,4,6,8)
given odds: Seq[Int] = Seq(1,3,5,7)
val y = f /*Ambiguous given instances:
both given instance evens in object Playground and given instance odds in object Playground
match type Seq[Int] of parameter evens of method f in object Playground */
I understand how it would have ambiguity if I did not name the givens, i.e
given Seq[Int] = Seq(2,4,6,8)
given Seq[Int] = Seq(1,3,5,7)
val y = f
There is true ambiguity here. But given you are able to name givens in the function definition, and then also name them in the given callsite, the compiler should have no trouble with this. But apparently the compiler makes no attempt to correlate the names in resolving ambiguity.
This seems like a significant limitation to implicits in the language, I have ran into this multiple times now.
what solutions are there to this? (without combining into a single parameter (Seq[Int], Seq[Int]), obviously) Would a simple type alias work, or would it need to be opaque?
Why does scala not pay attention to the names, and could this be fixed?
Yes this is an annoying limitation that I ran into many times. You simply cannot have two givens of the same type. (The names mean nothing, because anything can be named any name.) The instances get “summoned” by type, not name (since names can be anything anywhere). Implicit resolution is a complex matter and I guess that’s how it’s designed.
My understanding is that the design intention behind givens is mostly about typeclass instances, and anonymous givens are given vastly more attention than named givens. (I wish I could find the contributors discussions about this, can’t find the link, maybe this?, but I remember Odersky said that vast majority of the use case is anonymous givens and that’s what matters).
So in Scala you need to avoid this pattern (of needing multiple givens of the same type), it won’t work.
To disambiguate, you have to place them in different scopes, like put one inside an object. That’s what I do.
I’m not sure about the original reasons for that design, but I am very glad it is that way.
Implicit resolution as it is is quite complex already (the various places where the compiler looks for given instances, the priority rules in case more than one candidate is found…). If the names would be considered as well, there would need to be yet another set of rules to know and keep in mind. For example:
def foo()(using i: Int) = {
println(i)
}
given i: Int = 3
{
given Int = 4
foo()
}
What would you expect to be printed? And that is still a very simple example…
It’s as MartinHH said. Implicit resolution is quite complex…
There are languages that take implicits way further than Scala, such as Lean, with auto-implicit arguments, automatic implicit conversions, deep recursive implicit searches and more; and they have some complex mechanisms that give you a much greater degree of manual control over them, like manually setting priorities in instance searches, or setting default instances. But that’s further on the research side of things…
I mean, what is even the point of that code, it is not much shorter than just passing the parameters explicitly. If you tell me you need to call f multiple times it still doesn’t make much sense, it would imply a weird design, and still could be abstracted in other ways.
You should never use implicits for simple types like this.
This is not their intended use case.
I agree with @BalmungSan that this seems like a place where you just plain shouldn’t use given/using – it seems inappropriate, based on what you’ve shown so far.
Setting that aside, though, and taking the question at face value: given/using is entirely about types. Names are largely irrelevant. You have two givens with exactly the same type, so from the compiler’s point of view they’re exactly the same thing.
If you really need to do this, you have to distinguish the types, not the names. You could do this with tag types if you really cared, something like this:
object tags {
opaque type Tagged[+V, +Tag] = Any
type @@[+V, +Tag] = V & Tagged[V, Tag]
def tag[Tag]: [V] => V => V @@ Tag =
[V] => (v: V) => v
}
import tags._
trait Evens
type EvenSeq[T] = Seq[T] @@ Evens
trait Odds
type OddSeq[T] = Seq[T] @@ Odds
def f(using evens: EvenSeq[Int], odds: OddSeq[Int]) = {}
given evens: EvenSeq[Int] = tag[Evens](Seq(2,4,6,8))
given odds: OddSeq[Int] = tag[Odds](Seq(1,3,5,7))
val y = f
It would help if you would explain why! Context is supposed to work by type, not by name.
If you want it to work by “name”, there are two options.
The first option is to simply wrap whatever it is in a case class. Case classes are incredibly easy to create:
case class Evens(seq: Seq[Int]) {}
case class Odds(seq: Seq[Int]) {}
given evens: Evens = Evens(Seq(2, 4, 8))
given odds: Odds = Odds(Seq(1, 5, 7))
def foo(using Evens, Odds) =
summon[Evens].seq ++ summon[Odds].seq
The other option is to use a lightweight type tagging library. I happen to have one in my library kse3, so I will use that as an example, but you can find it elsewhere. The basic idea in my case is that you can tag any type or value with a string constant by using \ "myname"; you then get the actual value by calling unlabel or ~ "myname".
I haven’t really used tagged types for context parameters; I instead use them when it is very very important to keep identity straight despite the underlying type being the same (so conjure isn’t provided by the library, but I’ll add it someday).
I’ve made runnable examples of both cases with a plausible reason for why you might want this: often you have a context where only one is in view, but sometimes there’s an inner context where you need both the outer one and a new one: Scastie - An interactive playground for Scala.
I mean, what is even the point of that code, it is not much shorter than just passing the parameters explicitly … This is not their intended use case.
This is not the real use-case. I picked this example because it was trivial to understand and demonstrate the mechanics of the problem. The real use case is a contextual function with many implicit parameters, and at least two of them are the same type.
I can imagine lots of reasonable DSL specs that could run into this problem. – Of course solvable if I merge the two into a single case class, but at the cost of DSL ergonomics.
That still suggests a misuse of contextual functions, to me. In general, having multiple contextual values of the same type in a single scope is pretty rare – indeed, it’s rarely even well-defined. Like, the only common example I know is the fact that Monoid has two valid implementations over numbers. And more rarely, I’ve wanted a “reverse” implementation of Ordering.
It still feels like you’re fighting the language in a critical way – contextual parameters are defined by type. That’s the point of contextual parameters.
Are these two values truly, absolutely, the exact same type, with no way to distinguish them via type tagging? If not, they really shouldn’t be used this way, but I’d recommend stepping back and thinking about it, because the usages you’re describing sound like type tags are the tool I’d reach for here. Both of the known examples I’ve listed above could be distinguished that way; it just generally makes no sense because any given algorithm generally wants one at a time.
(Can you provide any more info about your use case? If you were able to say any more, it might help us provide more concrete suggestions.)
An easy example is a declarative construction of a tree in which you want a method on a node to implicitly have access to it’s parent, it’s child, and it’s self. This would mean a contextual function in which all 3 parameters are the exact same type Node.
It could even be useful for some rare use cases to give the method implicit access to the root and tail nodes, making it 5 parameters of the same type…
Hmm – still seems like something I would address using tag types. For example, I would make the accessor for the parent return something like Node @@ Parent (probably aliased as ParentNode), and have that as the type for the implicit parameter. Ditto for child, root, etc.
Remember: types and classes are very different. These are all the same runtime class, but the compiler can have far richer information than that in the type system. Tag types tend to be the right tool for the situation where the runtime class is ambiguous, and we want to inject more context for the compiler to make use of.
I’d recommend exploring this, and seeing where you can go with it. Yes, it’s a smidgeon wordier than hypothetically just using names – but it’s more robust and actually works, which something name-based isn’t ever likely to, because that violates some of the assumptions of Scala’s design.
Right, so those three should be typed differently so the compiler enforces that you have the right one, not just guesses that well maybe since you used a consistent naming convention probably this is the thing you want (but if you use some other name, oh well, just use it anyway)?
You can use the tagging method I showed, or the one jducoeur showed, or something else.
But you really need some sort of tagging solution. If you want it to “just work” untagged when there’s only one thing in scope, you can have a given that automatically untags things.
You can have tagged things be subclasses of untagged things, so you have access to the methods on the original:
opaque type Tagged[+A, T] <: A = A
But you really need to get the type system involved here; it won’t work the way you want, and if it did work the way you wanted it would be extremely fragile.
I like the idea of disambiguating by the value name. (I apologize in advance that if I like an idea, it will never be adopted.)
Everything is named now, from tuple elements to method args to whatever.
I just read some code that took an implicit Context and also an explicit newContext: Context.
Worth adding that having two parameters of the same type is a lintable offense. In an explicit application, you must name both or risk switching them by position. In Scala 2, a span is start, point, end, but in Scala 3, it is start, end, point. That is not to complain about the choice in Scala 3 (natch) but merely to point out the peril.
The beauty of infix 42 + 27 is that I don’t care about the order. So perhaps if you want two parameters of the same type, the method must be infix and also annotated commutative.
Otherwise, they must be implicit and distinguished by name. If they are supplied explicitly (using x, y) then explicit names are required.