Pre-SIP: a syntax for aggregate literals

[ as the start of a collection literal can only occur at the start of an expression, whereas [ as the start of a type parameter list can never occur at the start of an expression. I thus don’t see how your code demonstrates ambiguity. Both f[1, 2] and Arr[1, 2] are unambiguously method calls with two integer singleton type parameters.

FWIW, I know these trains are really hard to stop anymore. But I think this feature will very quickly be one of the ones that get forbidden by linters.

In general, eliding identifiers from syntax because the compiler should be able to calculate which identifiers to use, is a big minefield. Implicits do this already and this can already cause confusion (even though this is a core language construct).

I think if this feature were introduced, there would only be rare situations where it’s both useful and acceptable to use. There would be many more situations where it’s harmful to use for various reasons. So please don’t add it.

We already have aggregate literals. Seq(1, 2, 3) is one. This proposal would locally save you a few keystrokes (depending on the type you want) at a global expense (global in the sense of all Scala programmers everywhere).

And the points under:

Do we really need this just avoid typing Vector every now and then?

to me just provide further evidence that this feature would do more harm than good.

3 Likes

You should read the thread. What is currently being debated has essentially nothing to do with the original proposal.

Not sure I agree with that. Whatever the mechanism, the goal of new syntax (or new meanings of existing syntax) to avoid specifying constructors is still what’s being discussed, isn’t it?

I have been. And I just wanted to register my objection, along with @Jasper-M (I think), that I believe this is neither necessary nor desirable.

2 Likes

That seems overstated. In particular, I expect I would use this for defining test data all the time – wouldn’t surprise me if it would be helpful in hundreds of test files for a larger code base. Yes, it just saves some characters – but in many of these cases, it would make the test data clearer, and that matters to me.

I get that you would turn it off, but I think you’re making an over-broad assumption that everybody else would.

Actually no, Martin decided that it needs to be limited to collections only. Essentially it’s List and Map syntax at this point.

Still, the goal is to avoid specifying List and Map constructors (or others that implement a special typeclass). Is the juice worth the squeeze? We have a lot of "yes"es already in this thread, so I just wanted to register a dissent. For posterity, I guess.

6 Likes

I also find the difference between Seq(1, 2, 3) and [1, 2, 3] marginal and not worth the effort of adding a new syntax not fitting the rest of the language.

The rest of the desired behaviour can be implemented using implicit conversions for domains like tests, where this is deemed helpful. You can even define val S = Seq or even something like val & = Seq or whatever you like for such domains and use it like &(1, 2, 3).

7 Likes

I agree.

Also, I don’t find the “other languages have it” argument compelling. It seems to me that other languages added this because they lack things we do have, and those things that we do have already give us a concise-enough syntax for this.

11 Likes

Yes, at this point I agree.

When I originally proposed the feature, it was just a syntax to call apply on the companion object of the expected type. This is quite flexible because it can be used for collections, case classes and many other types, and it’s trivial to adopt because everything that’s needed to use it (apply methods on companion objects etc) is already there.
I then developed this further with the even more flexible idea of a placeholder (with # used as a strawman syntax) for the companion object, with syntax rules analogous to those for the _ placeholder in lambda expressions. This idea covers the same use cases as the original idea, and a whole lot more. It does so with the simplest possible syntax, a single token expression. It can be explained with a single sentence: “the # placeholder works like the _ placeholder in lambda expressions, except it doesn’t stand for a function parameter but for the expected type’s companion object”. It would have saved not only a bunch of typing but also a lot of import statements. It fit right in with Scala because it is entirely based on existing concepts, like the notion of an expected type (already used for lambdas), scoping rules (stolen from _ lambda expressions) and companion objects. So I’m going to toot my own horn here: the power-to-weight ratio of that proposal would have been insane.

The feature that is being discussed now is simply an attempt to bolt a Swift feature onto Scala with no regard for existing practice (e. g. using apply to create collections). Users of the tried-and-true way of constructing collections would be penalized over those using the new thing because the latter would be using a Builder to create the collection. It has a bunch of arbitrary special cases like using Seq when there is no expected type, it effectively turns -> into a syntax for Maps. It covers a small fraction of the use cases of the # proposal, it’s more complex and harder to adopt.

This is what I designed, this is what I wanted


Yes, I might cut myself if I use it unwisely.

This is what we're now discussing


You’re right, I’m not going to cut myself with this!

And I don’t want any part of it.

6 Likes

This is a good point. With an explicit short operator (either & or something else, maybe even c like R), this seems like something that could be implemented just as a stdlib/library level, and would bring pretty much the same benefits (quick example: Scastie - An interactive playground for Scala.)

I have mixed feelings about the proposal overall. Like Jeremy mentions, I don’t think I would like this on production code (it seems too easy to accidentally use a change collection’s type) and, while I understand that there’s no ambiguity with using [/], it can still be a bit confusing IMO (I don’t have a good example, but I imagine that you can get some good puzzlers out of there).

However, I do think I would use it for tests, “configs” (sbt, mill…) and maybe quick scripts. I can also see it being useful on some linear algebra problems.

So, I do see some use for this, but for my use cases it would be OK to just be part of the stdlib (not the compiler) - ideally under an import (similar to chainingops), so that it would be easy to opt out

Someone clever might even be able to implement implicit conversions straight from tuples to lists / maps using tuple metaprogramming.

Silly me: Scala 3 tuples already contain a method toList which does the work of finding the common supertype. Creating an implicit conversion should be trivial should anyone desire it.

Disagree, I would say enabled as default is the point of this so to be easier to play with for beginners - you could use a linter or flags to disable it in production code

Explaining this to Scala beginners would require explaining givens and type classes, or just hand waiving the explanation (and make students unable to understand the resulting error messages).

At least for our course (where we use Scala, but to teach language concepts, not programming introduction) I would likely put it on the list of features I have to ignore, to not have to explain it.

Overall, this seems much more like a feature aimed at people confident with the syntax, that would appreciate the shortcut, not people that are still learning the language.

1 Like

Actually you don’t need to explain the type inference algorithm for people to use it, so given it has a default of seq or map then it’s only if someone asks questions why it refines to expected type - but I’m sure there are many things that just work and you didn’t learn the precise algorithm how

On the other hand - the way that the conversion is activated is still important - perhaps expected type is not a good model, perhaps there should be explicit (but inlineable) conversion - personally I’m not in favour there

To be honest, after seeing what @JD557 created I’m too becoming more skeptical about the current proposal. The user-space solution gives nice short syntax for Seqs and Maps, and I can have even my preferred val y = *(1 -> *(1, 2, 3)) variant. Misusing the type syntax for the same looks OTOH very odd to me.

I actually think such short syntax has value, though. Even in “production code”.

I’ve used PHP before and after they added […] as shortcut for array(…) and that small syntax twist uncluttered the code really a lot! It was quite a big deal, and one of the updates I loved most.

But the situation in PHP was different in that you used array(…) more or less for everything, as you had no case classes and such. So the effect on Scala would be much smaller. Still I think it would be nice to have such syntax. But maybe a pure (std.) lib solution based on the presented Scastie would be already enough?

This does not mean that the proposed “macro inline givens” shouldn’t be introduced. This seems like a powerful concept worth exploring! But maybe in a different context than “sequence literals”.


OK, looking on the syntax a little bit longer I would actually like to write

val y = (1 -> (1, 2, 3)*)* 

That wouldn’t be possible without compiler support. (Or would it? Do we still have postfix syntax? But not without lang import, right?)

I think this looks even more “natural” than the prefixed syntax. It’s outright the “var-args expansion operator” reused, which makes imho intuitively sense, and fits current Scala syntax nicely.

Not sure it isn’t ambiguous, though. Passing such thing to a var-args receiver could end up looking “funny”… But why would you do that at all? You would just use the var-args in that case, I guess. (And passing a Map as a var-arg parameter does not work anyway).

Postfix operators are a syntax for method calls, not for function invocation, and in order to invoke a method, you first need to assign a type to the expression you’re invoking it on. So it wouldn’t work.

Oh and I wish we could just bury this thread at this point. The original proposal was rejected, and is there anybody left who thinks the typeclass based proposal is worth it? Because I’m not getting that impression.

Don’t be too disappointed.

The discussion yielded two interesting results in the end, even not your desired feature.

  • There seems to be a need for a Seq and Map shorthand of some form, and something like that could end up in the std. lib in the long run, I guess.
  • The “macro inline givens” implementation could become a good replacement for macro implicit conversions, which is currently still an uncovered spot.

So the discussion was fruitful I think.

3 Likes