Dotty: implicit(ly) versus given/summon

First, apologies for having hijacked the Dotty-announce thread.

I have no big stakes in this, but given that the given syntax has now - correct me if I’m wrong - mostly reverted to match the current implicit syntax, i.e. no infix keyword, mandatory parentheses: Is there any gain from renaming implicit to given at all?

Here are my arguments:

  • both given and implicit capture the same amount of intuitive concept
  • you have to explain the concept to newcomers in both cases
  • implicit has been around for very long time, it’s has proven to be an elegant way of writing things
  • implicit/implicitly is a very easy to grasp pair, whereas given/summon intuitively has very little to do with each other
  • I give it to given that it saves me some characters typing. Is that enough to have to rewrite a lot of things?

I really liked the infix syntax, here given was making indeed sense. But now, compare:

given {
  def (xs: List[T]) second[T] = xs.tail.head
}

versus

implicit {
  def (xs: List[T]) second[T] = xs.tail.head
}

and

def max[T](x: T, y: T)(given ord: Ord[T]): T =
  if (ord.compare(x, y) < 0) y else x

max(2, 3)(given IntOrd)

versus

def max[T](x: T, y: T)(implicit ord: Ord[T]): T =
  if (ord.compare(x, y) < 0) y else x

max(2, 3)(implicit IntOrd)

and

given Position = enclosingTree.position

versus

implicit Position = enclosingTree.position

I think the implicit variant is at least as clear. By the way, it’s still ImplicitFunctionN not GivenFunctionN, perhaps because the latter is really less obvious.

best, .h.h.

4 Likes

I completely agree. The dotty contextual abstractions motivation section lists problems in the Scala 2 syntax/semantics that were already addressed even if we keep the implicit keyword.

I also think that extension methods should use a soft extension keyword instead of given/implicit.

AFAIK, the only valid reason remaining for changing the keyword is the requirement to keep both syntax variations for migration. As specified in the dotty docs:

there is the problem how to migrate. We cannot change the rules in mid-flight. At some stage of language evolution we need to accommodate both the new and the old rules. With a syntax change, this is easy: Introduce the new syntax with new rules, support the old syntax for a while to facilitate cross compilation, deprecate and phase out the old syntax at some later time. Keeping the same syntax does not offer this path, and in fact does not seem to offer any viable path for evolution

Yet, I believe there should be a better way to resolve the migration issues without changing the keyword.

4 Likes

Hello and thanks for sharing thoughts.

Hoping I’m not derailing the topic, but I wanted to ask a (somehow related) question:

I see that since given is a symmetric keyword (works both for “I expect” and “I define”) it’s a good keyword.

I got a very different experience. When I first started learning implicit some time ago, I really struggled with: implicit val ExecutionContext being used for both:

  • “I’m expecting an ExecutionContext for this part of code to work properly” and
  • “I’m defining the ExecutionContext for everywhere it’s needed in the following lines of code”.

That symmetry really confused me. To be completely honest, my first thought was that:

  • implicit meant “I’m expecting” and
  • implicitly meant “I’m defining”.

I’m really curious to know if other beginners had the same first impression, so if it makes sense to take that fact into account, or not at all.

Regarding the suggestion, I don’t really have a strong opinion. given sounds totally ok to me for the “I’m expecting” part.
I’d have balanced it with something like:

let ec be:ExecutionContext = ???

or

let system.Dispatcher standFor ExecutionContext

which is obviously very verbose and not desirable, but just to show the idea.


As for the extension part, I agree 100%.
The dotty doc is very good on that part, making clear that some of the motivations behind the rework of implicits is to separate concerns, and not having implicit mean “some magic stuff”.
Both Groovy and Kotlin have embraced “extension method” as an official way to define this concept, so I guess people are getting used to the term.
Having extension as an official keyword makes it obvious to the code reader. Even better: it allows people to look for extension methods very simply in their IDE or by grep | wc in a big code base.
(it may be a quality gate check for some team for instance: if you’re adding extension methods everywhere, your code may be quite hard to for people using the same library, but in a standard way).
That’d be a really neat addition.


Finally, given is also used for type classes, which sounded a bit weird to me when I first read the dotty doc/motivation. Here’s what I went through:

  • So “implicit everywhere” is getting thrown away? And replaced by something more readable/searchable? Fantastic!
  • Typeclasses are coming? Awesome! I heard so much about it
  • Wait? The same keyword is used for both defining a typeclass and “I’m expecting an ExecutionContext” part?
    I instantly thought I’m missing something.

And to be completely fair I’m still really not sure if the fact that some function is expecting an ExecutionContext to work properly has something to do with type classes or not.

I hope this thoughts will be helpful in some way, and am really sorry if this is not the place for beginners or intermediate-levels to share thoughts, please tell me if it’s not, apologies.

Thanks for all the work you’re doing with designing Dotty, it looks really great.

2 Likes

Someone made a suggestion that I think is a good one: One should have two keywords to distinguish
when something is built from when something is used. The notation is quite confusing otherwise.
The suggestion was to use give for the creation and given when taking from the environement.

give intOrd: Ord[Int] {
  def compare(x: Int, y: Int) =
    if (x < y) -1 else if (x > y) +1 else 0
}
def (t: T) sort[T] (given: Ord[T])

It becomes especially helpful when one statment mixes both as

give [T](given opt: Option[T]): List[T] = opt.toList
7 Likes

The distinction, e.g. give and given, makes sense, and your example is compelling:

give [T](given opt: Option[T]): List[T] = opt.toList

And I agree that extension methods should probably have a different marker.

Perhaps one could even use findGiven as a method replacement for summon/implicitly. It’s clear, and while it’s longer, it’s no less typing than implicitly, plus if we use the convention with the companion object, such that Monad[A].zero == findGiven[Monad[A]].zero, it will need to be typed quite seldom.

A combination of given and obtain would be nice IMO.

obtain [T](given opt: Option[T]): List[T] = opt.toList
2 Likes

The problem is that implicit parameters play both roles at the same time: when you call the method, the implicit parameter is “built” (or rather, “resolved”), and in the body of the method, the implicit parameter can be used. So we have to use a single keyword. Unless you want something like this:

give [T](give given opt: Option[T]): List[T] = opt.toList
2 Likes

I was starting to read the thesis on Context Aware Programming that is linked to from this very good web site Coeffects: Context Aware Programming Languages and one use of this
the author Tomas Petricek’ mentions is the use of this for implicit parameters (e.g. page 16), where he actually refers to Haskell’s implicit parameters.

As I understand a function’s arguments are taken from the context, ie the x and y to the left of the sign.

x:int, y:int ⊢ x+y : int

The body uses the variables declared in the function argument list. The question is where the arguments are found. given tells the compiler to look in the context for something placed there that will fit the bill.

p17
“Implicit parameters are a particularly valuable example, because they clearly illustrate the ambiguity inherent in context-aware programs - the context demands of a function can be satisfied using the context available at declaration site or using the context avaiable at the call site.”

I am not sure if this is the same as Scala’s implicits, If it is, it could help better get a handle on what is ,going on.

I can understand that it’s seen as really obnoxious, but it doesn’t bother me that much tbh.
given give ExecutionContext explicitly states that the function expects an ExecutionContext, but just to use it for invoking other functions that expect it to be sent.

Sure it’s verbose, but really explicit. Having a simple given keyword meaning both given and give “hides this fact” somehow.

It’s also a way for the function definition to tell “it’s not my fault”, I’m needing an ExecutionContext, but that’s because a function I’m calling needs it.

But sure, it’s verbose (and verbose in the majority of use cases).

While this may be logically correct, the form

give [T](given opt: Option[T]): List[T] = opt.toList

is entirely clear and understandable. I read it like the plain declaration

give [T](given opt: Option[T]): List[T]

Do we? Maybe context bounds solve 99% of the give given cases?

1 Like

I agree with @julienrf. A given parameter both demands and provides a given instance, so we need a single keyword to express both. Why given and not implicit? The principal reason is that we need to distinguish new from old syntax since the method application rules have changed. implicit parameters can be passed using plain arguments whereas given parameters can only be passed using arguments of the form (given E). Besides, given is a better name than implicit precisely because it expresses
both “provide” and “require”. The previous syntax

implicit object ord extends Ord[Int] { ... }

is weird since there is nothing “implcit” about ord. It’s just as explicit as the next object. The new syntax

given ord: Ord[Int] { ... }

works much better in this regard.

Thank you Martin. I still think that

given [T](given peer: Ordering[T]): Ordering[List[T]] = ???

is “weird” (it was weird before when it was implicit), so I liked the suggestion to distinguish definition site from argument site, such as give [T](given peer ...).

1 Like

Tough I get this reasoning, I think that it provides both roles is actually one of the more confusing things about implicits, which still isn’t solved. If I recall correctly, differentiating call and usage site was one of the requirements of the new implicit system.

Personally I think that two keywords, even though it might be less semantically correct, would actually make more sense.

2 Likes

Given X means “supposing that X exists.” I don’t see how that makes sense for the definition.

3 Likes

I get that. Which is the original reasoning behind choosing only one keyword.

However give x: X (given y: Y) makes it visually much easier to parse what means what, especially when working with real code that is usually longer and more complex. I am not saying this is the best solution. However I think using the same keyword is still more confusing than using two, even if it is semantically correct.

I think this problem is disregarded a little bit too easily.

Edit: to expand on the previous. In the original motivation behind changing implicits (Principles for Implicits in Scala 3) it questions why implicits are not the run-away success as in Rust or Haskell. For me, one of these reasons is clearly because type classes make a clear distinction between providing a one and requiring one.

Compare using one in Haskell versus Scala:

class (Eq a) => Ord a where ... and given ord: Ord[A](given eq: Eq[A]) = ...

5 Likes

At definition site, given X can be read as "X is a given"; just like class X can be read as "X is a class" (and object, trait, val, def). “A given” is something that is established; not something that needs to be established.

I understand the reason why given has been chosen as a keyword, especially since in English it means both:

  • “Be ec an ExecutionContext” and
  • “Given that ec is an ExecutionContext”

Choosing given makes total sense in that regard. No argument.

I don’t want to “fight” or advocate against using a single keyword if people have their opinion set already (and discussed about it before). But I really want to emphasize (a last time) on what @bmeesters is writing.

I really think having a single keyword expressing:

Is really confusing.

If someone is asking me: “What does given mean in Scala?” I’m not sure I’d be able to explain it in a sentence not containing “either, or, or, or” which sounds a bit weird to me.

Thanks a lot for your explanations and the time spent to go over the questions.

6 Likes

Good point.

However val x = 1 also establishes x.

Clearly given x doesn’t mean x is a given, it means x is a Given. Meaning, you can’t just read it as English and understand it without knowing that Scala has a concept called Givens.

And that same logic works equally well for implicit. implicit val x doesn’t mean x is implicit, it means x is an Implicit.

[I admit there’s a difference: “x is implicit” (lowercase) is factually untrue (of course it is an Implicit, capital), while “x is a given” is true but uninteresting, so it might be somewhat less confusing, but I doubt meaningfully less. Besides the same holds for anything. In the case of class you don’t read it as x is class or x is a class in the common vernacular, you read it as x is a Class where Class is some concept defined by the language (in this case shared with many other languages, at least roughly).]

2 Likes

Not sure if you meant to reply for someone else, but I’m arguing against using one keyword. Given makes sense for implicit parameters, a lot more than for implicit definitions.