-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Better syntax for conditional given instances #7788
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
Comments
Would
be equivalent to
? |
Having parentheses change the meaning so radically seems to be a major footgun. Besides this doesn't solve the problem because in other cases given still means something else. The fact that the implicit keyword can do two different things was the only real issue with the keyword and it's still not solved. (Also, I still don't see how the given stuff isn't like 5x as hard to learn and teach than implicits. They seem to have a whole bunch of unique syntax rules. This just adds to that.) Of topic perhaps but here's a crazy idea.
|
That would hamper usability more than anything else IMHO. It would also mostly defeat the purpose of the new extension methods mechanism. |
I'm intrigued but also wary of the reuse of given listOrd[T]: Ord[T] ~> Ord[List[T]] ... ( And once we have that, we could also replace the syntax for given function types, instead of def foo(given Int ~> Foo[Int]) = ... it's immediately clear that foo locally has something equivalent to a global def foo(given (given Int) => Foo[Int]) = ... |
To expand on what I just said: the other interesting thing here is that when teaching this stuff, we could first teach parameter-less givens and given function types, then use them as building blocks for teaching parameterized givens. This is different from how you'd teach implicits, were you'd have to use implicit vals and defs as building blocks to introduce implicit defs/classes, and implicit function types would be a more advanced concept. |
The issue is, nobody in their right mind should demand a given of function type. It's way too common a type to be passed around implicitly. So it makes no sense to define a given of function type either, which means the @liufengyun proposed going the other way: Drop all type and value parameters from a given instance, and allow given instances to define polymorphic functions instead. Then it would be: given [T] => Ord[T] => Ord[List[T]] ... |
I assume that should be This is (conceptually) a function type with a given parameter, which is available implicitly given Ord[T] => Ord[List[T] = ... But this is a regular function type available implicitly: def foo(given Ord[T] => Ord[List[T]) = ... To get something equivalent I need the dreaded double-given again: def foo(given (given Ord[T]) => Ord[List[T]) = ... |
Polymorphic function types use |
I am not sure the reduction in given-given cognitive switching is worth the novel syntax and divergence from typical method syntax. So I'm neutral on the change. I think it starts out non-ideal and ends up non-ideal. I think the problem remains squarely with If anything, it would make more sense for I don't particularly like how it looks, but I find it way clearer. given listOrd[T](summon Ord[T]): List[Ord[T]]]
given [T](summon Ord[T]): List[Ord[T]]
given (summon Context): Context I think clarity is the most important consideration. |
I tried something like that a long time ago, when we discussed @lihaoyi 's proposal of implicits as default parameters. I found it does not work, since there are too many choices for the user to make. The default that an implicit parameter is also a provider is a good one, it turns out. |
This syntax looks awfully like a dependent function type signature – in fact, it's exactly a dependent function type signature after the |
Which is fully intended, and if we adopt @liufengyun's proposal we'd extend the same treatment to polymorphic function types. The idea could be: A given instance has never parameters on its own but it can define a normal type or a function type. We could go even further and build modus ponens into implicit resolution i.e. if we look for an |
Just wanted to ask everyone to once in a while take a step back and look critically at what we've got so far. Is it better than where we are with I think there's a lot of talk about how the problem with |
The problem with |
I admit the proposed syntax reads better and looks nice. I just finding it troubling that it looks very much like something it's not: a function. The fact that adding parenthesis changes the meaning is disturbing and will, I fear, be the source of great deal of confusion. |
Let's try to decompose the ambiguity argument. Why could ambiguity be bad?
maybe we should expect that we can define an anonymous given parameter of implicit function type like this?
But no, in the latter case we have to write |
To be clear, I'm not worried about being confused when writing the code, moreso when reading it. If my eyes are focused on the source code:
then now I have to backtrack in order to parse that. Does it have This will also contaminate all usages of It's easy to handwave and say "it will be fine, nobody should be doing X so it won't matter." And maybe that would turn out to be true, but I think the current awkwardness of There could be a case made for this if the syntax for a function type If I'm writing |
That's a fair point, but it still means that if someone asks "What does |
@jeremyrsmith The multiple meaning problem you mention exists already today.
means one thing, but
means another, where
would treat the |
I would answer it means a given instance for |
It's true that the same tokens exist within a I still think that the problem this is meant to solve is a bit overstated, though, and I question whether it's worth it. Anonymous instances are a nice new bonus, but it's always possible to insert a name when the definition looks awkward without one. I guess you could make an argument that What would it look like for multiple given [T, U]: (Wizzle[T], Wozzle[U]) => Wuzzle[T, U] |
given (lol) the polymorphic function syntax above I guess it would be one of these: given [T, U] => Wizzle[T] => Wozzle[U] => Wuzzle[T, U]
given [T, U] => (Wizzle[T], Wozzle[U]) => Wuzzle[T, U] I have to admit I'm warming up to it a bit, at least when using anonymous instances... and thinking about it more, I never really use derivations without any type arguments involved, which was really the only case that felt difficult to parse. I'll walk back my objection a little bit 😄 |
I'm a bit unclear about what syntax would be no longer supported. You would no longer be able to have final given clause consisting of only anonymous instances on a conditional given? But anonymous given clauses would still be supported in other places (e.g. a non-final clause)? |
A little more real-world example: given [H, T <: HList] => (lengthT: Length[T]) => Length.Aux[H :: T, Succ[lengthT.Out]] doesn't look more objectionable than before. But it does show that the given lengthCons[H, T <: HList](given lengthT: Length[T]) <: Length[H :: T] |
Currently both this and
are supported. We might narrow it down to one or the other before the final version ships.
I agree it depends on your use cases. One reason I proposed the change was that I thought of a use case where conditional instances would come up all the time and should be as lightweight as possible. That was using given instances as axioms and inference rules for theorem proving. So, the old syntax might not be a big problem now but it might become one, depending on where people go with givens.
given instances would not longer have given clauses. |
@Jasper-M @odersky to be clear, I'm not arguing in favor of making implicit parameters not be implicit locally without an extra keyword. My point was that I seem to recall that the argument that it's confusing to have one keyword mean two things was one of the strongest arguments for changing around the whole implicits thing, and in the end we're not actually going along with it.
I am very skeptical about this. Scala 2 implicits are dead simple to explain and have almost no syntax rules to learn (we already know what a modifier is). Givens seem to have an arbitrary number of syntaxes and there's no way to know what each one does.
FWIW I strongly disagree with the whole philosophy. The value-add is in high-level and declarative mechanisms ("this is implicit") vs. low-level or imperative mechanisms ("set byte X into register Y"). I don't agree that we should expect the computer to work well with intuitionist expressions of intent. It results in a much more complex mental model. (In some cases it creates bigger problems, like when people started thinking Google was not just a way of getting to content that people put on the web, but that it could give answers to questions, which is a fiction that falls apart as soon as there's disagreement about something.)
I don't think implicit meant different things, but it was used as a lower-level tool to common patterns, and some anti-patterns. Just as scala did with I just don't see I can summarize everything a beginner needs to know to understand |
Fix #7788: Add new syntax for conditional given instances
This reverts commit d1579e6. # Conflicts: # compiler/src/dotty/tools/dotc/parsing/Parsers.scala # docs/docs/internals/syntax.md # tests/pos/reference/delegates.scala
This reverts commit d1579e6. # Conflicts: # compiler/src/dotty/tools/dotc/parsing/Parsers.scala # docs/docs/internals/syntax.md # tests/pos/reference/delegates.scala
This reverts commit d1579e6. # Conflicts: # compiler/src/dotty/tools/dotc/parsing/Parsers.scala # docs/docs/internals/syntax.md # tests/pos/reference/delegates.scala
Uh oh!
There was an error while loading. Please reload this page.
Using present given syntax, it's a bit awkward to define conditional given instances:
It's worse in the anonymous case:
and even worse in the monomorphic case:
Context bounds can help avoid many cases where a
given
parameter would be needed butthey do not work in all situations, e.g. they don't work if the typeclass in question has more than one parameter.
Going with the "intent over mechanism" motto, it would be nice if there was specific syntax for
conditional givens. Here's a proposal to fix the three use cases:
The new syntax reads much better: "given
Ord[T]
impliesOrd[List[T]]]
...".An immediate concern is that this looks too much like we implement a function type. Yes, but
=>
also in cases and self-types(...)
.A separate question is whether in that case we would still support the old syntax with given parameters in instances. My vote would be no, let's have a single way to define things.
I believe that the new syntax also alleviates a concern people were having about the dual use of
given
as a provider and a consumer. In a sense that dual use is inevitable since given parameters are both consumers and providers. But it's still a concern ifgiven
's with different meanings are used next to each other. With the new syntax, this is no longer possible since "providers" (i.e. given instances) don't contain anymore "consumers" (i.e. given parameters) in their clauses.The text was updated successfully, but these errors were encountered: