The problem with that I think is that you can’t even read which type is implicitly given; Eq[A]
is buried somewhere in the middle and hard to spot.
I agree with the line of reasoning, and find both the newly proposed syntax and the older proposed syntax vastly better than the Scala 2 syntax. So won’t complain much. But I also agree with @Sciss that it is harder to spot the actual ‘final type’ of the given.
True, but there is a vertical layout option as well:
given [A]
=> K0.CoproductInstances[Eq, A]
=> Eq[A]
as eqSum
That would work well if the parts get large.
Putting the name at the end is inconsistent with everything else.
Scala’s greatest strength is its internal consistency. The most persistent criticisms of Scala are ways in which it is inconsistent or too flexible leading to inconsistency.
I still have no idea why any of this is an improvement over
implicit eqSum[A](implicit K0.CoproductInstances[Eq, A]): Eq[A]
(Making “def” and the parameter name optional is nice and has potential application beyond implicits.)
I find the argument of consistency quite convincing. Even though it might be already late, may I ask, why was the :
dropped? I’m asking, because if we kept :
for givens, we wouldn’t have to deal with as
and how it’s used in pattern matching – very different use case.
It would the look like this, staying close to def
and val
work and thus staying consistent.
trait Ord[T] {
...
}
given intOrd: Ord[Int] {
...
}
// or
given Ord[Int] {
...
}
given listOrd[T](using Ord[T]): Ord[List[T]] {
...
}
// or
given [T](using ord: Ord[T]): Ord[List[T]] {
...
}
This approach basically only removes the names of the variables and uses given
instead of val
/def
/… and so it should be easy to teach/explain.
There are two issues with using :
instead of as
- it does not distinguish visually well enough between the parameters and the thing we are defining.
- it looks weird with indentation syntax which by now is a given (forgive the pun).
Here’s a screenshot that shows the difference.
And here’s the same with named instances:
My takeaway is that prefix as
is not that bad, really. It’s biggest downside is for anonymous conditional instances, where the as
connective does not really make sense. But one might get used to that.
So rather than opening up the discussion again, let’s do a poll. If you prefer the existing syntax, please like this post.
If you prefer the syntax with =>
and postfix as
instead, please add a like to this post instead.
My takeaway from that screenshot is that colon as line continuation character can sometimes be in competition with colon as type introducer
There is a semantic ambiguity with =>
: In given S => T => U
, it is impossible to know whether the given instance type is S => T => U
or T => U
or U
.
The type ?=>
does not help: In given S ?=> T ?=> U
could mean a given instance of the type S ?=> T ?=> U
or T ?=> U
or U
.
We need to separate the instance type from its requirements explicitly with keywords.
The as
approach does not suffer from the problem. However, the anonymous version needs improvement and the using
in the condition block is unnecessary.
I like the new syntax. I think all the argumentation for it makes sense and the screenshots very vividly demonstrate the importance of syntax highlighting: the final type of given is easy to spot even if it’s followed by as
with a name. And overall, IMO it looks lighter, easier on the eyes.
At this late stage, I don’t have strong feelings about which I personally prefer, though I’ve disliked Scala’s syntax for self-types for as long as it’s existed in its current form, and “[type] as [name]” style might offer a possible improvement here.
Here’s the current syntax:
trait Orange() { orange: Fruit =>
// ...
}
which allows this
to be renamed to orange
(and hence not be shadowed by other potential this
s in nested classes), and also assigned a type Fruit
as a “promise” that Orange
must have Fruit
, or one of its subtypes, mixed-in before it is instantiated.
I think that could be better written as,
trait Orange() as orange requires Fruit:
// ...
which would avoid the syntax where the body of a template looks like a lambda. The requires
keyword would be optional if only a self-name is required, and not a self-type, and it was the syntax used for self-types up to roughly Scala 2.2ish, before the current style was introduced.
I don’t want to complicate this discussion, but I think that even though self-types are not so frequently used any more, they fall into the category of things that are optionally named, and could benefit from having syntax that is more consistent with other optionally-named things in the language.
The more I look at it, the more I think the former syntax is better. It is visually more distinct what part of the definition is what - name, parameters (by using using
), return type - compared to arrows to arrows to arrows. I also expect all these arrows won’t be appreciated by the large part of the Scala community that are not active on forums like these. More symbolic operators make it look more arcane and I expect also harder to explain.
Since we also don’t have much time to get to use this syntax and tweak it further based on experience I am not in favor of introducing this change.
In my very humble opinion the new proposed syntax does a better job at setting givens apart as a distinct feature. The current syntax looks a bit like a word salad that wants to be different from a regular def
but doesn’t quite manage it yet. You have given
, then something method-like with a redundant using
that seems to be inherited from implicit def
when it was still possible to have both implicit and explicit parameters. Then instead of being consistent and going with the method-like thing and writing the type ascription with a :
there’s the out-of place as
—which apparently only exists because :
conflicts with the optional “optional braces” thing.
I’m being a bit hyperbolic for the sake of the argument here.
I understand the reasoning and I agree that it’s good to have given
s be distinct from normal functions/methods. I just think that all these arrows will make it hard for new developers / scala 2 developers to see what’s exactly going on.
given [T] => Ord[T] => Ord[List[T]] as listOrd = ...
Intuitively I think this looks more like a curried function from some generic T
to a Ord[T]
to a List[Ord[T]]
instead of: I have some generic T
, I expect an Ord
for that T
, and I will give you a Ord[List[T]]
.
I wouldn’t mind toying with some more ideas, but there is very little time for feedback/experimenting with these ideas. And I think what we have now is good enough (even if there are always things to improve).
Well you don’t have a T
. The arrow-based syntax says precisely what it should: give me a T
, give me an Ord[T]
, and I’ll give you an Ord[List[T]]
. This can be understood following the usual Curry-Howard isomorphism: the given
declaration is a constructive proof that if you have some T
and some Ord[T]
, then there exists a corresponding Ord[List[T]]
value. This is in line with how implicits have been used for automatically synthesizing type-level proofs. Fittingly, the fat arrow looks like mathematical implication.
What if the as
keyword is replaced by something else, without changing the syntax structure?
given foo is Foo[A]
given foo(using Context) is Foo = ???
case foo is Foo() =>
// or
given foo naming Foo[A]
given foo(using Context) naming Foo = ???
case foo naming Foo() =>
// or
given foo for Foo[A]
given foo(using Context) for Foo = ???
case foo for Foo() =>
So my poll was almost a draw. 12 votes for existing syntax vs 14 for new. Which tells me that neither syntax is fully convincing.
One interesting question is whether the given syntax should be consistent with the rest or intentionally different. My tendency would be to say it should be consistent as long as it does not give up any benefits.
With that in mind, I have explored a different way of doing things that is even more consistent than the current syntax. In particular, it drops as
in favor of :
.
I won’t copy the very long PR message here but comments are welcome on this thread.
I really like this. Best proposal I’ve seen so far, IMO. Much more consistent.