Switch order of type and term in `as` definitions?

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.

4 Likes

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.

2 Likes

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.

6 Likes

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.)

6 Likes

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.

4 Likes

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.

17 Likes

If you prefer the syntax with => and postfix as instead, please add a like to this post instead.

20 Likes

My takeaway from that screenshot is that colon as line continuation character can sometimes be in competition with colon as type introducer

10 Likes

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.

5 Likes

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 thiss 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.

3 Likes

3 posts were split to a new topic: Scala 3 significant indentation

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.

2 Likes

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.

4 Likes

I understand the reasoning and I agree that it’s good to have givens 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).

4 Likes

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.

1 Like

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.

21 Likes

I really like this. Best proposal I’ve seen so far, IMO. Much more consistent.

8 Likes