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:
- 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.
- It doesnāt work when you have more than one parameter list or a
using
clause.
- 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)
- 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] = _(_)