My point was, while that kind of jank is somewhat expected of DSLs, it’s much less so the case for built-in features
(and so the following only strengthens that argument)
Also anecdotally, I got confused for a second at Vector[(X, Y)] given the current discussion, and was like, “wait are we proposing Vector[...] as a way to add a type to [...] ?”
While of course on second thought we already have Vector(...) for that …
If Scala is going to get [] for collection literals, why wouldn’t go a step further and make it a new better syntax for tuple literals?
How come it can be better?
Tuples are collections already, so collections would be covered automatically.
It would also solve the long-overdue issue with the current tuple literal syntax – it would allow to declare empty and 1-item tuples consistently without clumsy workarounds (like (,) and (a,)):
[] – empty tuple;
[a] – 1-item tuple;
[a,b] – 2-items tuple and so forth.
Perhaps(!), it might also be possible to go even further, and unify tuple literals and tuple type definitions:
type T0 = []
type T1 = [Int]
type T2 = [Int, String]
which in turn would solve the other issue raised in this topic:
So it seems that [] for tuples could bring far more improvements than just a convenience syntax for collections (which to me does not seem really important).
whole-heartedly agree with collection literals just being tuples that auto-convert to the various collection types at appropriate points
but with []-tuples… a big issue for me is that parameter lists are in essence tuples and should have the same syntax. this proposal would diverge them for the worse imo
currently it’s consistent:
def f0 () = ... // empty tuple (not unit)
def f1 (a : Int) = ... // 1-item tuple
def f2 (a : Int, b : Int) = ... // 2-item tuple
def f1_2 (a : Int) : (Int, Int) = (a, a) // (Int) => (Int, Int)
val tup : (Int, Int) = f1_2 (5) // tuple in, tuple out
under your proposal it’s not:
def f1_2 (a : Int) : [Int, Int] = [a, a] // (Int) => [Int, Int], or [Int] => [Int, Int]
val tup : [Int, Int] = f1_2 (5) // tuple in, tuple out?
so it gives consistency in one aspect but loses in another
that said, this part on its own…
type T0 = []
type T1 = [Int]
type T2 = [Int, String]
…already makes sense to me. normally it’s TContainer[TItem1, ...] and above it simply looks like the container type has been left off: [TItem1, ...], in which case it’s an orphaned type param list and the container type would default (to tuple or perhaps to a context-dependent type). that parallels the way a collection literal like (1, 2, ...) defaults to a tuple, or as proposed above to a context-dependent collection type
Actually, it is not: parameter lists look quite different than tuples – they get parameter names. I’d say, parameter lists are more like named tuples, but they are not them either. Moreover, they can get using at the beginning and, in class consturctors, they can get val, var, override, etc. So pehaps, it would be for good to stop mixin up tuples and parameter lists.
so it gives consistency in one aspect but loses in another
But the way to request a non-default is to have an expected type, which is a huge pain to type in the case of tuples–it’s worse than the current syntax.
So that means it only works if the default is it’s a tuple, which means that the “I have no easy way to write a compact collection literal” problem isn’t even solved in the most common “I just need it to work” case, because a tuple is typed by element, and that usually isn’t what one needs.
Right–the type information is there (like it is if you use parens); the question is how that gets dispatched. If you have anything at all to dispatch it, then you may as well use tuple notation, because T(…) with an appropriate macro does everything that is requested for tuples.
I can’t tell what anyone expects, but I personally would expect that when I use some literal syntax, then it would capture the type literally. Which is exactly what tuple literals do, and the collection literals wouldn’t.
I’d like to emphasize on that: Scala literals are very powerful because they allow to capture types so precisely. It hard to justify a brand new syntax for literals that deliberatelly strips some type information off. To some extent, it contradicts the idea of type-first approach in Scala.
And the other consideration is even more important:
collection literals would be just a convenience “shortcut” syntax sugar for building collections. Probably nice to have, but it is only important for maybe 1-5% of Scala applications that have to construct vectors, matrices or configuration entries directly from the code. Most of Scala apps just don’t need it. Moreover, it is an anti-pattern for the majority of Scala apps.
tuple literals are ubiquitous in all kind of Scala applications, and it is annoying (and I guess confusing for newcomers) that (), (a) and (a, b) mean three completely different things. Therefore, replacing (...) with [...] for tuples would resolve that ambiguity gracefully.
Given that the latter subsumes the former, it is hard to me to justify adding the collection literal syntax alone.
We already have syntax that does exactly that. We don’t need another. It is even more difficult to justify a brand new syntax that is covered by existing syntax!
Yes, this has one less wrinkle, but it has one new visual confound (type vs value). And you can get the exact same thing if you’re willing to devote a single character (and a macro–and not a difficult one) to it now.
In contrast, we don’t have a good way to create collection literals or key-value literals for inferred types. It is much harder to get one character to do the work for you.
Also, way more code needs collection literals than needs 0-element and 1-element tuples. Those things are almost useless anyway; the only reason to think about them is if you’re abstracting over tuple arity (which is possible but esoteric).
to work, probably. I’ve gotten things of similar complexity to work. This is not such a trivial macro to enable. But it’s a case where the brevity is the point: the content is supposed to catch your eye, not the ceremony.
which is clearly wrong, because "eel" isn’t a valid port. Figuring it out as a tuple is a pain, because there is no Tuple[Int]. As a collection, it’s trivial to detect the mismatch.
Again, it can be done even so (at least if you dispatch with some sort of method). But it is a difficult lift library-wise (assuming the literal will obey type inference), whereas the tuple thing is a non-issue because tuples mostly aren’t used to abstract over number.
Actually, it is not: parameter lists look quite different than tuples – they get parameter names. I’d say, parameter lists are more like named tuples, but they are not them either. Moreover, they can get using at the beginning and, in class consturctors, they can get val, var, override, etc. So pehaps, it would be for good to stop mixin up tuples and parameter lists.
I’d rather say, it resolves yet another ambiguity
haha. yes, named tuples with some extra features, and baked in so the tupleness isn’t exposed. but still in essence tuples. mostly a superset if you will. i don’t recall ever reading about confusion in this regard (correct me if i’m wrong) so there’s no need to distinguish between them
if the () and (v) issues could be resolved then the need for an alternative tuple syntax goes away, no? and even if they can’t, are these big enough problems that we require a new [] tuple syntax that will have to replace a lot of existing tuple code and that could ending up more confusing (or perhaps not )
i don’t know what you’re saying, sorry. that this is hard to do as a macro so it can’t be done? there are surely many ways to do this. just make the compiler do what needs to be done: add a conversion from tuple to the given type or selectively interpret tuple syntax as a literal of the stated type or… something else. whatever is decided. the first step is accepting that tuples make excellent collection literals for various reasons and then the missing parts can be developed
which is clearly wrong, because "eel" isn’t a valid port. Figuring it out as a tuple is a pain, because there is no Tuple[Int]. As a collection, it’s trivial to detect the mismatch.
by Tuple[Int] do you mean a tuple that has an unknown number of Ints rather than a tuple that has 1 Int? putting aside the fact that a mix of string and int ports could very well make sense (named ports are a thing), i don’t see why there’s any pain here. for example, if it was config(ports : Set[Int]) then it might try to convert the given tuple to the set and fail because the only possible set type is Set[Int | String]
with tuples as collection literals then your examples become:
the syntax looks identical except for the dropping of the collection type. that says to me that it’s the exact same data and we’re leaving it up to the system to work out the type based on the target type (and in the absence of a target type it would simply be a tuple, which retains the most type info, as @sporarum mentioned, whilst adding the least extra on top). this feels to me much more natural and less confusing than a switching to a new bracket type
the 1-ary tuple could be solved by allowing trailing comma in tuples. i know that it looks ugly to some, but scala already has trailing commas. trailing commas bring the benefit of optimized (git commit) diffs when having multiline tuples (or sequences of arguments in parentheses in general)
trailing commas don’t cleanly solve the 0-ary tuple (i.e. empty tuple) problem, but in case of idea of converting tuples to applications in presence of target typing, we can add extra rule and treat unit value (i.e. () itself) like an empty tuple and then empty argument list
taking both above points together (plus target typing), code like:
def matrix[T](params: Array[T]*): Array[Array[T]] = params.toArray
val columnMat = matrix(
(1,),
(2,),
(3,),
)
val weirdMat = matrix(
(),
(),
(),
)
val seq0: Seq[Int] = ()
val seq1: Seq[Int] = (2,)
would expand to:
def matrix[T](params: Array[T]*): Array[Array[T]] = params.toArray
val columnMat = matrix(
Array(1,),
Array(2,),
Array(3,),
)
val weirdMat = matrix(
Array(),
Array(),
Array(),
)
val seq0: Seq[Int] = Seq()
val seq1: Seq[Int] = Seq(2,)
such proposal looks much more like just target typing (that also works with collection factories) rather than any sort of collection literals
Currently, trailing commas are only allowed at the end of the line before the closing ).
And what is more important – they mean nothing, i.e. they are ignored.
Even worse, in regards to 1-item tuples the syntax doesn’t work one may expect:
val t2: Tuple2[String, String] = (
"abc",
"def", // ok
)
val t1: Tuple1[String] = (
"ghi", // ERROR: Found: ("ghi" : String); Required: Tuple1[String]
)
val s: String = (
"jkl", // ok
)
Therefore, a sytnax rule like: if comma is not at the end of the line and it is followed by ) immediately (without line breaks) and also if there’s only 1 item before the comma and after (, then (and only then) it is a 1-item tuple (otherwise it is not) – that would be really ugly. I believe that Scala can do better than that.
Well–tuple literals make excellent collection literals; tuples themselves do not (atrocious performance, painful indexing)–the difference being that once you have a tuple at runtime, it is not very collection-friendly. But at macro-time, a tuple literal is just syntax, so you can write a macro to reinterpret it as something else, and you can intercept unit () as a zero-length tuple also (if it’s in a vararg; if it’s not, you can overload the method with Unit).
The problem is type inference. It’s solvable with a single character or with context. So it’s not disastrously bad. I’m not convinced it needs any compiler help at all, because you’re one character away from the regular compiler being able to do it.
My point is just that if we are going to get compiler and/or syntactic help, the collection problem is the comparatively difficult one and the tuple one is easy. Therefore it makes sense to reserve the [a, b] notation for collections first and foremost if we’re doing it at all.
I think it’s fine either way. What I think is not fine is failing to make collections easier in order to have a duplicate tuple notation whose main claim to fame is that it’s easier to create a zero-length tuple.
So–I’m happy to get M to work as I wrote it, if that’s what I want; I just would be annoyed if [] were used but used as by-default-tuple notation rather than collection notation.
The syntax rule would be: an item surrounded by parens and followed by a comma is a 1-element tuple, because it looks just like a 2-element+ tuple except it has only one element.
If the current syntax makes it more complicated, then we tweak the current syntax so that there aren’t weird exceptions that are only there because we didn’t use it for singleton tuple literals so nobody thought too much about it.
fair point about performance of tuples in general but in the context of the original problems i don’t think this matters. we’re either using the literals to reduce type duplication when the type is known, or we’re talking about very small collections or casual code like in prototyping where performance isn’t important and forcing an early choice of collection type is either a distraction or outright pointless (also, i think the only real performance consideration is creation and conversion, not really indexing)
so yes, good point about the distinction between tuple literal syntax and tuples themselves and we can use this sidestep the creation of an intermediate of the default type (eg. a tuple) in a lot of cases by selectively interpreting the literal as the target type
but i still reckon we need to support the case of prior instantiation with no explicit type:
// inline so selectively interpreted as the target type
config(ports = (2501, 1172, "eel", 9))
// no type but instantiation is forced so a default type has to be chosen?
val ports = (2501, 1172, "eel", 9)
println(ports)
config(ports = ports)
not allowing this to work would create a lot of confusion imo. how do you do this without some form on conversion from the default collection type to the final target type? (i think this is relevant regardless of the literal syntax and default type that end up being chosen so it’s not specific to tuple but nevertheless important to think about)