Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doc pages for packagings and givens #17808

Closed
som-snytt opened this issue Nov 20, 2021 · 14 comments
Closed

Doc pages for packagings and givens #17808

som-snytt opened this issue Nov 20, 2021 · 14 comments

Comments

@som-snytt
Copy link
Contributor

Please add overview doc pages for packagings and givens, per

#11296
#8092

The import vs local for givens came up on the forum, and Bjorn requested an explanatory page.

These pages should not assume Scala 2 expertise, and should also cover these "edge" cases.

They should explain

Package definitions are not definitions.

and

ContextualImplicits are stacked in the order they are encountered.

@bjornregnell
Copy link
Contributor

This would be really helpful! Especially I'd like to understand why this

Welcome to Scala 3.1.0 (11.0.11, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.

scala> object X:
     |   given Int = 42
     | 
// defined object X

scala> def a = 
     |   import X.given Int
     |   given Int = 43
     |   summon[Int]
     | 
def a: Int

scala> a
val res0: Int = 42

does not give 43 or a compiler error that givens are ambiguous, but instead pick the formerly imported given and not the last given value.

@som-snytt
Copy link
Contributor Author

I'll try to supply this text and also discover where to stash it. I understand packages as packagings, but I've haven't looked at "encounter-order of contextual implicits".

The other feature request would be, Provide a -explain mode so that if summon[T] sees competing implicits, emit a verbose accounting. (Maybe verbose output is already available, I haven't checked.)

@bjornregnell
Copy link
Contributor

bjornregnell commented Nov 21, 2021

Thanks @som-snytt ! I'm currently working on a slide that tries to explain this to beginners (I'm this year daring to teach Scala 3 given using, while I previous years deemed Scala 2 implicits out of scope...).

This is what slide 29 says (translated from Swedish):

Priority order when summoning givens

It cannot be ambiguous which given value to choose if many, 
so the compiler selects givens in this priority order to avoid ambiguity:
1. Explicit arguments to context parameters marked with using
2. givens and imported givens in the current scope
3. givens in the companion object of the used type
4. ... (some more tricky rules that we don't cover here) 

If several givens are available for types that belong to a common type hierarchy 
then the compiler chooses the given value with the most specific type.

Is this text OK as an approximation of the full story for now? (next weeks lecturing starts on Tuesday...)

@bjornregnell
Copy link
Contributor

bjornregnell commented Nov 21, 2021

BTW: I'm introducing givens as a more flexible and powerful form of default arguments, to connect to what they already should know.

@som-snytt
Copy link
Contributor Author

@bjornregnell that is interesting and ambitious. I bet they have Coursera materials that might provide guidance or "wording".

That's an interesting idea: default args supply an arg without context, and a given is the same thing but contextual because it is supplied by the current context.

An example might be java.math.MathContext, where it's easy to imagine an API with a default but also taking a context parameter. def f(using ctx: MathContext = MathContext.UNLIMITED).

I agree that your items 2 & 3 are the fundamental concept, "lexical scope" vs "implicit scope", where implicit scope has to do with "related types" but our first intuition is the companion object of the type we wish to summon.

I'm still sorting item 4. 😄

@prolativ
Copy link
Contributor

@bjornregnell in your snippet the synthesized names of the givens are both given_Int so this seems to be the reason why they don't clash (you'll get an ambiguity if you rename one of them). However it's indeed still quite surprising that an imported given takes priority over a member , especially that if you print given_Int the REPL will show 43

@bjornregnell
Copy link
Contributor

bjornregnell commented Nov 22, 2021

Hmm. @prolativ So you mean that if the are called the same they are not ambiguous but if they are called differently they are ambiguous -- should it not be the other way around?

And: Did you mean that this is a bug that the priority is "backwards"?

@bjornregnell
Copy link
Contributor

bjornregnell commented Nov 22, 2021

Here is added printing of synthesized names as suggested:

scala> object X:
     |   given Int = 42
     | 
// defined object X
                                                                                                                                            
scala> X.given_Int
val res0: Int = 42
                                                                                                                                            
scala> def a =
     |   import X.given Int
     |   given Int = 43
     |   println(given_Int)
     |   println(X.given_Int)
     |   summon[Int]
     | 
def a: Int
                                                                                                                                            
scala> a
43
42
val res1: Int = 42

@prolativ
Copy link
Contributor

prolativ commented Nov 22, 2021

I mean if they have the same name then one definition shadows the other so this is not seen as an ambiguity. But the strange thing is that the rules of shadowing are different when referencing definitions directly and when searching for implicits

@bjornregnell
Copy link
Contributor

Aha. Thnx. Yes strange.

@bjornregnell
Copy link
Contributor

Should we file an issue -- is this a bug?

@bjornregnell
Copy link
Contributor

Or is the closing of this a "won't fix"? #8092

@som-snytt
Copy link
Contributor Author

I updated the closed ticket with a cleaner example. I suspect the ticket will remain closed and the behavior is just a puzzler. But it is really an edge case, in the sense that one should not introduce givens into scope in this way. What's needed is a scalafix lint to warn about it. (It's like old C puzzlers where you must decode the order in which ++ increments are applied. The proper answer is don't write code like that.)

I don't understand the previous comment about naming, because Scala 3 doesn't have Scala 2 shadowing of implicits.

Maybe I will understand the comment after I trying to write some doc. (I also need more experience with the syntax. There is another ticket to document given {} syntax IIRC.

@bjornregnell
Copy link
Contributor

bjornregnell commented Nov 22, 2021

That's an interesting idea: default args supply an arg without context, and a given is the same thing but contextual because it is supplied by the current context.

Default args also have context, so that starting-point in pre-knowledge seams reasonable:

scala> var ctx = 42 // don't do this at home
var ctx: Int = 42

scala> def f(x: Int = ctx) = x + 1
def f(x: Int): Int

scala> f()
val res0: Int = 43

scala> ctx = 43
ctx: Int = 43

scala> f()
val res1: Int = 44

@ckipp01 ckipp01 transferred this issue from lampepfl/dotty-feature-requests Jun 5, 2023
@scala scala locked and limited conversation to collaborators Jun 5, 2023
@ckipp01 ckipp01 converted this issue into discussion #17809 Jun 5, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants