But in Scala 3 you can tag anything with a string type, if you set it up properly. If all you want from newtypes is labels on the old types, it’s just
/** A stable identifier to disambiguate types by label */
type LabelVal = String & Singleton
/** A labelled type; create with `val x: Int \ "eel" = \(5)`; access with `x ~ "eel"` or `x.unlabel` */
opaque infix type \[+A, L <: LabelVal] = A
object \ {
inline def apply[A, L <: LabelVal](a: A): (A \ L) = a
extension [A, L <: LabelVal](la: A \ L)
inline def ~(l: L): A = la
inline def unlabel: A = la
}
Now you can run around typing things as String \ "first" and so on.
val x: Int \ "eel" = \(5)
val y: Int = x // Fails
val y: Int \ "cod" = x // Fails
val y = x // Succeeds, infers y: Int \ "eel"
val z: Int \ "eel" = x // Also succeeds
type Moray = Int \ "eel"
val w: Moray = x // This works too
println(6 < x ~ "eel") // Prints false
println(6 < x) // Fails
At this point, you have no need for compiler-defined syntactic sugar at all!
def foo: (String \ "name", Int \ "age") = ???
already works. Of course you can’t do what you want with the tuple–that requires some machinery–but with string literal types, you don’t even need supertagged. Admittedly it might be nice to have actual identifiers instead of strings–identifiers are easier to type, and String \ " name" isn’t the kind of mistake you want to enable. But it’s not that bad, and it’s already there for free.
It is interesting to see, that most discussions are about the details how to.
I must say, I am not convinced that named tuples are a good idea at all.
Why? Because I think it makes the language and the code too complicated.
We have already the fast and easy way, aka tuples. And we have a nice and neat way, aka case classes.
Are case classes more code then named tuples? Sure!
But better a little more code, then 3 ways to construct product types.
Also I think named tuples are very bad when it comes to refactoring/maintenance of the code.
If you need something “quick and easy” you won’t do something like type User = (name: String, age: Int) but just def getUser(id: String): (name: String, age:Int). Which then propagates up the call hierarchy. Have fun renaming name to fullName or something like that.
But let’s say you actually would write type User = (name: String, age: Int), would your tooling help you with the renaming of name to fullName? Nope. At least not when the feature comes live, since it is a new concept that needs extra handling.
And if you write type User = (name: String, age: Int), then why not just case class User(name: String, age: Int)? Which has all the tooling and refactoring support TODAY!
Therefore, I really think named tuples are a very bad idea, that make Scala worse.
//I need fast iteration over 1 000 000 rows with zero copy
stream[(a:String,b:Int,...,z:Int)]("""
select a,b,c ..., z from very_large_table
""").foreach{row =>
println(s"""I am happy to get value from iterator without data copy,
I just can get it by ${row.a()}""")
}
And please do not tell me that I can live without it, I know it )))
I think “IDE doesn’t support it yet, therefore we can never have it” might be a bit of an unreasonable argument.
As the current implementation exists, it requires an @experimental language feature import, so is not even usable without nightly/snapshot compiler, and it probably wouldn’t become stabilised (even with SIP approval) until IDEs, linters etc can support it.
The primary use case as mentioned is for some form of container with typed field selection that can be easily constructed by macros. E.g. parsing some schema into a data structure, or dataframe-like APIs - macros are not allowed to generate classes where the API is not already defined e.g. in a trait. However they would be able to construct named tuples. Other libraries already exist to do a similar thing with structural types, but these are not integrated as well with the compiler, and need to reinvent the wheel to be flexible.
I think we should stick to case classes. As Nabil said we do not need a third way to construct product types. Any decent code will use case classes. If someone is too lazy to define it let him deal with _1, and _2 I think Scala needs to work on tooling more than named tuples. I don’t see the benefits.
this can be done with a macro and STRUCTURAL TYPES from the library that does the stream function. why complicate the language. We have all the tools available.
stream(stream"“”
select a,b,c …, z from very_large_table
“”“).foreach{ row =>
println(s”${row.a()}")
}
I see a massive benefit in having a lightweight way to define intermediary record-like types without paying the cost of a class at each step. It isn’t about being a lazy programmer but about the performance and readability.
Consider the following code:
type NameWithAge = (name: String, age: Int)
def collectNameWithAge: Seq[NameWithAge] = ???
def format(record: NameWithAge): String = s"${record.name} is ${record.age} old"
val report = collectNameWithAge.map(format).mkString("\n")
IMHO it is much cleaner than the alternatives, and more performant at runtime than the case class.
to be clear tuples are syntax sugar for a case class, so this example I am not sure shows the benefit of performance you claim - perhaps if you do a bunch of conversions that actually end up casting the named tuple, rather than allocating a new case class with a different name (but same fields) this would make more sense
I don’t think any of this is right. This is exactly where case classes shine.
case class NameWithAge(name: String, age: Int) {}
type Name With Age = (name: String, age: Int)
// Everything else exactly the same either way
// And the case class, unlike the tuple, does NOT have `age` boxed!
You need an example more like
def joe(n: Int): (name: String, age: Int) = ("Joe", n)
// vs
case class NameWithAge(name: String, age: Int) {}
def joe(n: Int) = NameWithAge("Joe", n)
By the time you define a type variable, you’ve already hit the same syntactic complexity as a case class (saving only four letters: type = vs case class).
It ofcourse solves the task but it does not shine at all. It leads to decoupling declaration with usage and makes code harder to read and refactor it forces using magic names and so on.
It is just little toothache. Ok, somebody does not need it. It is normal. But It is really easy to understand , just do not use anonymous functions in functional approach. A named function allows to do the same, does not it?
I agree. I think any argument in favor of named tuples (or structurally-typed records) should not involve the definition of a type alias, otherwise the benefits over case classes are too small.