-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Implement given/with
syntax
#8017
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
Conversation
4e57df2
to
ff4c2e0
Compare
A note on indentation: Using object Foo:
def bar() = 1 instead of object Foo with
def bar() = 1 This does not mean we go back to the state where a I believe |
I did read the provided example test several times and each time feel there is something weird in the proposed syntax:
I like and use Scala, and would love it to have the most clear and plain syntax as possible, one everyone tech-savy can read and grasp without searching the internet, pls do not compromise on this! |
f66d150
to
398c8ce
Compare
Regarding the given import syntax, personally I would much rather have: |
Alias givens can be anonymous, e.g. | ||
```scala | ||
given as Position = enclosingTree.position | ||
given with outer: Context as Context = outer.withOwner(currentOwner) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is with x: T
allowed without parentheses, or should it be given with (outer: Context) as Context = ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. It needs to come in parens.
I was a proponent of splitting up And while subjective, I also think the move away from The only thing I'm not quite sold on is I would suggest the following tweak from this: given listOrd[T] with (ord: Ord[T]) as Ord[List[T]] ...into this: given[T] Ord[List[T]] with (ord: Ord[T]) as listOrd which harmonizes In the common case, the above would be written as: given[T] Ord[List[T]] with Ord[T] which is quite pleasant. Almost as pleasant as, The fact that there are different ways to introduce let bindings is a little unsatisfying. But again could be viewed as a feature if the idea is to bias toward unnamed givens, which I think is the correct pedagogical choice (named givens being reserved for advanced users). |
The problem with a syntax like that is that it looks backward when the result type depends on a parameter:
it also would be backward from the way |
@smarter Haskell "solves" that problem by essentially swapping the order, like: with[T] (ord: Ord[T]) given Ord[List[T]] as listOrd But it has the same problem as Haskell, which is that to parse out the instance type with your eyes, you have to look past the constraints, which can be arbitrarily long. OTOH you could use convention to format it like so: with[T] (ord: Ord[T])
given Ord[List[T]] as listOrd such that additional constraints don't change where you look for the Maybe with[T] (ord: Ord[T])
given (listOrd: Ord[List[T]]) Then at the risk of bike-shedding too much, change given[T] (ord: Ord[T])
implies (listOrd: Ord[List[T]]) Or possibly given[T] (ord: Ord[T])
provide (listOrd: Ord[List[T]]) |
4feaba3
to
ca5c8ae
Compare
@jdegoes Regarding I’d read given ord as Ord[T] { ... } “given ord as the Ord[T] instance where …” If the “ord as” part is missing it’s just “given the Ord[T] instance where…”. By that interpretation, the chosen syntax corresponds exactly to spelling it out loud. |
@odersky I think that's teachable, which is the most important concern for me. 👍 |
has class C(x: Int, y: Int) = A(y) with
def f = x * y It also reads nicely: class C of x and y is class A of y with added method f |
@@ -69,7 +69,7 @@ maximum(xs).with(descending.with(listOrd.with(intOrd))) | |||
|
|||
## Multiple With Clauses | |||
|
|||
There can be several with clauses in a definition and with clauses can be freely interspersed with normal parameters. Example: | |||
There can be several with clauses in a definition. Example: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It think it is better to write "There can be several with
clauses"
@@ -88,6 +88,17 @@ f(global).with(ctx).with(sym, kind) | |||
``` | |||
But `f(global).with(sym, kind)` would give a type error. | |||
|
|||
With clauses can be freely interspersed with normal parameters, but a normal parameter clause cannot |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With
clauses
@@ -88,6 +88,17 @@ f(global).with(ctx).with(sym, kind) | |||
``` | |||
But `f(global).with(sym, kind)` would give a type error. | |||
|
|||
With clauses can be freely interspersed with normal parameters, but a normal parameter clause cannot | |||
directly follow a with parameter clause consisting only of types outside parentheses. So the following is illegal: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with
parameter
I also don't understand why we're keeping "context bounds" around -- they're a relict of a time when declaring implicit/given/with parameters was awkward. But those times will soon be gone and we will be able have things like this just fine: def aggregate[A] with Monoid[A] (list: List[A]): A =
??? And there should be just one way of declaring implicit parameters IMHO. Btw, declaring implicit parameters first, and the ordinary after that has the benefit that it makes passing methods with implicit parameters easier: def f(g: List[Int] => Int): X = ???
given as Monoid[Int] = ???
f(aggregate) before one had to do this def aggregate[A](list: List[A])(implicit A: Monoid[A]): A = ???
def f(g: List[Int] => Int): X = ???
implicit val xxx: Monoid[Int] = ???
f(aggregate(_)) |
@sideeffffect I believe context bounds are still a lot more concise and easier to read than the alternatives. The latest commits allow normal parameters after given parameters, but it's fair to say that this point is still contentious. |
When defining typeclasses, why do we need to a modifier on the params? We can make // Defining typeclasses
given [T] (o: Ord[T]) => Ord[List[T]] {}
given [T] Ord[T] => Ord[List[T]] {}
given [T: Ord] => Ord[List[T]] {}
given Ord[Int] {}
given [T, U] (t: Ord[T], u: Ord[U]) => Ord[List(T, U)] {}
given [T, U] Ord[T], Ord[U] => Ord[(T, U)] {}
given [T, U] (t: Ord[T]) => (u: Ord[U]) => Ord[List(T, U)] {}
given [T, U] Ord[T] => Ord[U] => Ord[(T, U)] {}
given ExecutionContext = ExecutionContext.global
// with names
given listOrd[T] (o: Ord[T]) => Ord[List[T]] {}
given listOrd[T] Ord[T] => Ord[List[T]] {}
given listOrd[T: Ord] => Ord[List[T]] {}
given intOrd => Ord[Int] {}
given listOrd[T, U] (t: Ord[T], u: Ord[U]) => Ord[List(T, U)] {}
given listOrd[T, U] Ord[T], Ord[U] => Ord[List(T, U)] {}
given listOrd[T, U] (t: Ord[T]) => (u: Ord[U]) => Ord[List(T, U)] {}
given listOrd[T, U] Ord[T] => Ord[U] => Ord[List(T, U)] {}
given ec => ExecutionContext = ExecutionContext.global
// summoning instances
def max[T](lst: List[T])(given o: Ord[T]): Option[T] = ???
def[T] (lst: List[T]) max(given o: Ord[T]): Option[T] = ???
def[T: Ord] (lst: List[T]) max: Option[T] = ???
extension [T] on (s: List[T])(given Ord[T]) {
def max: Option[T] = ???
}
extension [T: Ord] on (s: List[T]) {
def max: Option[T] = ???
}
@main main = {
max(List(1,2))
max(List(1,2))(given summon[Ord[Int]])
}
|
That is in fact what's currently implemented. People objected on the grounds that the conditional clauses in given instances looked like function types. I am not so sure about this, but anyway, using |
If there is no further input I'd like to merge this by tomorrow. Not sure it's feasible to review this (it's quite a large delta and not very interesting technically) but if someone wants to review parts of it, please go ahead. |
👍 for merging. |
Now that we have a very lean syntax for implicit parameters, can we drop https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala ? It optimizes methods whose result type is an implicit function type but those can now be written almost as succinctly using |
c136712
to
0660984
Compare
@smarter lean syntax helps, but if you have many context parameters it's still needless duplication. There's value in abstraction if the abstraction is zero cost. Right now |
This reverts commit 0e5d3bf.
0660984
to
5707a9a
Compare
Was updated to new syntax by accident
Changes should be rolled out only after released.
For given instances: given ... For context parameters ... with ... For context functions A ?=> B
It's now ```scala given _ given T ``` Thta way it's more regular: A given selector is always followed by something: Either a wildcard or a type. The previous syntax for type bounds on normal imports is no longer supported. We might want to bring it back at some point, but it's not essential.
There are valid use cases, and syntactic awkwardness can be kept in check by the "one space following a with clause" rule.
5707a9a
to
744ab30
Compare
This is an elaboration and full implementation of the docs in #7973. Instead of fiddling with the instance keyword (witness or default) we change the parameter syntax by going back to with, which we had before given.
Using given for parameters has several potential problems:
To start with browsing:
https://github.com/dotty-staging/dotty/blob/change-given-with/docs/docs/reference/contextual/motivation-new.md