Pre SIP: Named tuples

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. :thinking:
I must say, I am not convinced that named tuples are a good idea at all. :confused:
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. :-1:

2 Likes

IMHO: Comparing case classes with named tuples is like comparing anonymous function with named one…

If there are named tuples with support of match types it will be killer feature for my job

3 Likes

Please be more specific :slightly_smiling_face:

1 Like
//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.

6 Likes

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 :smiley: I think Scala needs to work on tooling more than named tuples. I don’t see the benefits.

1 Like

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()}")
}

So yes I can say you can live without them.

2 posts were split to a new topic: Tooling support

If I am not missing something, It can not be done without disadvantages. :wink:

2 posts were merged into an existing topic: Tooling support

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.

1 Like

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

I would expect this to use a generic Tuple2 class and be optimized further by escape analysis on the JVM.

1 Like

I don’t need a full-blown case class for my example. I’m saving more than a few letters.

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.

1 Like

can you elaborate more on this, please?

In your example, what are the drawbacks of using a case class?

In my opinion, tuples are useful in situations where you would not (or could not) use a case class. Otherwise, you would simply use a case class, no?

A typical example is a foldLeft call as shown in Pre SIP: Named tuples - #28 by lrytz. Other examples are projects like Iskra or frameless, although named tuples alone may not be enough to support them, as shown in this section about projections between data types.

1 Like