Hi @Ichoran, and thank you for taking the time once again.
vargargs
I think your proposal is entirely orthogonal to varargs
It does solve a problem that varargs as they exist today have, and the fact that this problem could also be solved in other ways doesn’t change that. Also the point here isn’t so much that varargs need to be fixed, it’s to demonstrate that varargs are not a substitute for the syntax that I’m proposing.
redundancy
Okay, but this is exactly the opposite from what you’re proposing.
No, it’s not as one-dimensional, my point isn’t that every saved keystroke = moar gooder. What I’m proposing isn’t to write everything with as little redundancy as possible, it is to give users a choice which redundant parts they want to spell out and which ones they prefer to have inferred from the context. Today, users can leave out parameter names, and the compiler knows which parameter it is from the position in the parameter list. Users don’t have that choice for redundant type names today.
I believe choice is good to have because which one is more legible depends on a lot of factors including the application domain, the data itself and the expected level of knowledge of the reader. One size does not fit all, and never allowing the type name to be spelled out would be as wrong as enforcing it.
about named tuples
I think that’s because you’ve gotten used to thinking of data as essentially untyped save for a bundle of key-value pairs.
That is not how I think about data at all, and I wouldn’t be using Scala if I did.
Now, Scala is lacking a good way to interface with the land of key-value pairs where the keys are strings and the values are well-defined types. That’s what named tuples provides. Check it out!
For now, named tuples are experimental, and I for one am not convinced that we want an entirely different category of types when we already have case classes – I would rather have a convenient syntax available to create case classes instead of rewriting all my case classes into named tuples. Not to mention that I might not even control many of those case classes, as is the case for example for the rather complex data types in zio-k8s.
And there’s more: named tuples can’t have defaults either which, again, makes them completely unsuitable for k8s manifests or the kind of code that Guardrail will generate for typical OpenAPI schemas. Moreover, named tuples require me to always specify each and every field name, and I don’t want that either.
So no, named tuples are very much not a substitute for what I have in mind, they’re not even close, and perhaps this proposal could supplant them entirely by making case classes more convenient than they currently are.
people decided for some reason that being explicit was important, and you’re overriding that.
It seems to me that that might have just been a historical accident more than anything else. As Haoyi and I have pointed out, many people, like the designers and users of C#, C++ and other languages, have apparently come to the conclusion that being explicit is not that important after all. C++ initialization lists work for pretty much everything for a reason.
Some questions answered
What happens if there are multiple apply methods?
[stuff]
desugars to ExpectedType(stuff)
. Hence, Array[C]([2], [3, 4], [5, "eel"])
desugars to Array[C](C(2), C(3, 4), C(5, "eel"))
. It would just work.
Does it work with unapply too?
For now, my idea is just the desugaring of expressions above. Creating objects is much more common than destructuring (as evidenced by the fact that most languages had facilities for the former much earlier than the latter, if they have it at all), therefore I think the current destructuring syntax is probably good enough. But I could change my mind on this. If we want to replace named tuples with this, then it might be necessary to have that, too.
Does it work if the type isn’t a class?
If ExpectedType(stuff)
works, then so does [stuff]
, it’s really quite straight-forward I would say!
The original proposal said something about companion objects and apply methods – I’ve changed my mind about that as it’s unnecessarily specific and doesn’t cover e. g. regular (non-case) classes.
Does it see through type aliases?
Yes (unless opaque)
Does it trigger implicit conversions?
implicit conversions work like you would expect them to.
val xs: Array[String] = ["eel", Some("bass"), o]
// desugars to
val xs: Array[String] = Array("eel", Some("bass"), o)
If a class can take itself (or a supertype) as an argument, can you nest
[]
as deep as you want?
Only if that is the only unary constructor of that class. val c: C = [[x]]
would desugar to val c: C = C([x])
. If C has more than one unary constructor, say one that takes C
and another that takes Int
, then it’s not clear what the expected type of the expression [x]
would be, so it’s not clear if it should desugar to C(x)
or Int(x)
. At that point you get a compiler error.