Pre-SIP: a syntax for aggregate literals

Yes, exactly. That is pretty much point 1 in your list above, and my original proposal (using [] rather than ()).
And that’s definitely a viable proposal, we can make that work. But please do consider the issues that were brought up about this:

  1. It can’t be used when you want to use a method other than apply. For example, LocalDate objects aren’t created using LocalDate(y, m, d), they’re created using LocalDate.of(y, m, d). Or a cats.data.NonEmptyList, which isn’t created with NonEmptyList(a,b,c,d) but NonEmptyList.of(a,b,c,d). I don’t think this is absolutely crucial, but it’s nice to have.
  2. It doesn’t work when you have more than one parameter list or a using clause.
  3. If we go with a () syntax, then simply wrapping an expression in parens – which is so far always a no-op – can now cause a constructor to be called. I think this makes it way too easy to accidentally trigger construction of an object that you didn’t mean to (e. g. typesafe ID wrapper types)
  4. If we go with a [] syntax, then it complicates the parser as @lihaoyi has helpfully pointed out

So overall, this approach doesn’t address all the use cases that I would like it to, and both of the syntaxes that have been proposed have drawbacks that I’d rather avoid. That is why currently the “companion object placeholder” model looks best to me.

That’s right! And the good news is that we already do that today for other language constructs. For example, this works perfectly fine:

val f: Int => Int => Int =
  x => y => x + y

Neither x nor y need a type annotation here, so this recursive, incremental type inference thing is actually already happening, and it’s a proven approach.

Yes, absolutely, and the scoping issue is exactly what I was trying to get at in my previous comment.
But the good news is that, again, we have a set of proven rules on how that should work, and it’s the scoping of _ in lambda expressions. So that’s why my suggestion is to use the exact same rules also for the scope of the # placeholder. That would work for every reasonable example I can come up with:

val _: List[Int] = #(1, 2, 3)
val _: Duration = #.fromNanos(42)
val _: List[Int] = (1 to 10).to(#)
val _: Future[Unit] = #(println("Hello, ragnar!"))(using ExecutionContext.global)

And actually, we can experiment with that syntax today! We just need to place CompanionObject.type => in front of the expected type and then use an _ instead of # and squint a bit! All these compile:

val _: List.type => List[Int] = _(1, 2, 3)
val _: Duration.type => Duration = _.fromNanos(42)
val _: List.type => List[Int] = (1 to 10).to(_)
val _: Future.type => Future[Unit] = _(println("Hello, ragnar!"))(using ExecutionContext.global)

And here’s an extra cool one:

val _: List[List.type] = #(#) 

// to simulate the syntax in current Scala:
val _: (List.type, List.type) => List[List.type] = _(_)
1 Like