Syntax for type tuple with one element

There is so much weird here that comes of tuples being like an HList type and not a type product.

The name unit to me implies the unit of the type product operation, which is how the ML family including Haskell uses it. (x, y) : T1 * T2. And maybe you write that type as (T1, T2) if you want. A tuple of one type isn’t a thing because it’s just the type, and the product of a type with unit is the identity. T * () = T = () * T. The type product is associative. T1 * (T2 * T3) = (T1 * T2) * T3. There are usually syntactic features making it different structurally which you need for stuff to make sense with opaque types, but… significantly, you never need a tuple of a single type in this model to be different from the type itself.

But here in Scala tuples aren’t type products, they’re heterogenous lists. This is very useful for being able to analyze and synthesize types with them. So that cat is out of the bag. *: isn’t a type product, it isn’t associative. () isn’t a unit type even if we call it that because we don’t actually have an operator for it to be a unit. A list of one item does need to be different from a single item, and here we are.

Anyway… I felt I should say something because I think this impedance mismatch is part of the frustration and confusion and misunderstanding here.

(And it’s all exacerbated because for a lot of people their first introduction to the term is Python’s “tuple” which has nothing to do with any of this and instead just means “immutable list”.)

6 Likes

I have made an unfortunate discovery while drafting a SIP for this feature:

val a = (
  1,
)

a // 1: Int

Trailing commas only apply on multiline parameter lists, which explains why (1,2,) doesn’t work, but the following does and desugars to (1,2):

(
  1,
  2,
)

However this seems to have been generalized to singleton tuples where (\n1,\n) is seen as equivalent to (1) itself equivalent to just 1 !
(This is also present in both the current Scala 3 Next and latest Scala 2)

This can be traced to the original implementation:

Where skipping the comma is done in the Scanner, so even before the Parser knows if this is a parameter list or a tuple !

(Of course in the former this is not an issue since f(1,) would be equivalent to f(1))

However the corresponding SIP does not mention this, only focussing on parameter list

The PR description does extend it to notably tuples, but again no mention of this edge-case is done

As such, I am not sure whether this is a bug or not ?
This question matters because if this is not a bug, the feature does affect previously valid code (and not invalid-but-the-compiler-thinks-it’s-fine)

I also forgot to mention that:

f(1,)

Is actually invalid in Scala, which makes (1,) a less elegant generalization than what I thought

Given this, maybe ((1)) is the move ?

It also has the benefit of removing the need for autotupling:

f(a, b) // f is passed two parameters: a and b
f((a, b)) // f is passed one parameter: Tuple2(a, b)

I am worried for the interactions between such a syntax and people with sight or reading issues such as blurry vision or dyslexia
If this syntax would make it hard for you to read code, please let us know

1 Like

Interestingly enough, i proposed this syntax more than a month ago:

So …

… it looks fine to me :smile:

I didn’t mean to take credit !
I was referring to it, albeit … very implicitly ^^‘’

I think it’s pretty neat, although non-standard

1 Like

The interaction of scanner and parser is subtle.

was put on hold because it only “pulls its weight” if it also introduces some indentation checks.

One bit of unused syntax is (_). I don’t pretend to know what it should mean; beginners use it in place of (x => x). Maybe it looks like empty tuple.

To me it seems like it was put on hold because it was unclear what the changes did ^^’

If it’s already valid, we shouldn’t use it for something else

I find it sad that nobody seriously considered my idea.

The only feedback was: “I don’t like it”, without even trying to come up with counter arguments.

My solution solves all technical issues, and as a bonus, makes Scala syntax more coherent, and removes some TIMTOWTDI.

The only constructive (and in fact valid) remark was from @sjrd, namely the teaching issue, and “all books would need rewriting”. But this is also true for any other change or addition to the language! Languages tend to evolve. (Even things like C; if you tried to learn from the K&R book today this wouldn’t go well) So imho the argument is less strong than it seems at first. (A lot of programming book are already outdated when they go into print, actually… :smiley:)

OTOH my idea doesn’t really introduce any new construct. It’s already as of today valid Scala syntax. The idea just restricts some syntactic variants to free up the syntax for the most intuitive and coherent tuple syntax possible.

The {} syntax for an “empty expression” (which is obviously of type Unit) is the most intuitive I can think of.

Like already said, this wouldn’t be an issues if the only valid syntax would be: x * {a + b} vs x *: (a + b). The former is already valid syntax. One would just need to outlaw (and auto-rewrite) the parens for grouping. Given that grouping expressions are actually quite seldom in most “typical” Scala codebases (enterprise apps) I really don’t see the big issue here.

And once more: There is also already precedence where a language change made the compiler require curly braces for grouping, and wants to rewrite the parens, namely in the mentioned named tuples case.

Currently, braces enclose a block, which are statements followed by an expression. Parens close an expression (or comma-separated elements for args).

This is why the compiler warns about expressions which are “orphaned” in “statement position”.

Welcome to Scala 2.13.16 (OpenJDK 64-Bit Server VM, Java 23.0.2).
Type in expressions for evaluation. Or try :help.

scala> locally {
     | 1
     | + 2
     | }
       1
       ^
On line 2: warning: a pure expression does nothing in statement position; multiline expressions might require enclosing parentheses
val res0: Int = 2

scala> locally (
     | 1
     | + 2
     | )
val res1: Int = 3

The ergonomics are much improved with leading infix:

➜  ~ scala -Xsource:3 -Xsource-features:leading-infix
Welcome to Scala 2.13.16 -Xsource:3.0.0 (OpenJDK 64-Bit Server VM, Java 23.0.2).
Type in expressions for evaluation. Or try :help.

scala> locally {
     | 1
     | + 2
     | }
val res0: Int = 3

and safety is improved with -Wnonunit-statement.

But parens say it’s an expression and you don’t have to guess.

Currently, I find using empty braces as a value “wartful”:

def u = {} // empty Set syntax
def v = ()

because braces require inserting the unit value, which is like value discard for statements.

def p = { val x = 42 }
def q = { val x = 42; () } // expansion

Optional braces has reduced the need to add braces for debug:

def f =
  println(x)
  x

and that would also work if infix gets indentation support. There is a PR that was put on the back burner. I think the use case was to make boolean expressions easier to read, in particular. I haven’t experimented with it, but I do leverage leading infix whenever I can.

I should have added that the syntax is erroneous, though that doesn’t stop beginners from trying it, because beginners are naturally filled with optimism and wonder.

No, that is the counter-argument.

It is counter to massive experience with every use of grouping in every arena of life all the time.

Honestly, if Scala were to make this change and enforce it, I would probably never use Scala again (or not the newer versions). I genuinely would hate it that much because it takes something that I use incessantly and must continue to use incessantly everywhere else and makes it not work in Scala.

It would be easier to change strings from "" to “” (unicode open and close quotes \u201C and \u201D).

The counterargument is specifically: this is such a burdensome change that it is not worth it. I wouldn’t learn a language like that either; I don’t care if it’s “more consistent”, it’s not usable enough. Something that I write on practically every line of code isn’t something I’m willing to change, and I expect rather a few people are similarly inclined.

Given that .tup or something is 1000x easier (literally–maybe 10,000x?), while I appreciate realizing that, in fact, we could make () always indicate tuples, I don’t actually want that to happen. “Oh, that’s curious–that’s a good point!” is as far as I would want to take it.

So, hence, no “serious” consideration. Or, rather, “I (really, really) don’t like it” is the counter-argument.

5 Likes