Clause Interweaving, allowing `def f[T](x: T)[U](y: U)`

Any idea how to enable them? A Scastie link with an example would be best.

Here is what it should look like:

However RCs are not considered nightly or snapshot (at least for the purposes of experimental features), so the above does not work

We would need to add a nightly version to Scastie, which is a known issue, for example see this

Okay, as discussed in the issue, there is a work around, here is a fully working example:

4 Likes

Cool, thanks!

Is there a reason why Scala cannot allow type parameter groups in the same way as regular parameter groups? For example:

def f[A, B][C](f (A, B) => C)

That way we could require some types and let the compiler to infer the others, e.g.:

f[Int, Int]: (a, b) =>
  (a + b).toString

Currently the interleaved type parameters feature doesn’t allow that directly, because every type parameter list should be followed by regular parameters (in parentheses), but here the first parameter requires all the types at once. There is a work-around though:

def f[A, B]()[C](f (A, B) => C) = ???

f[Int, Int](): (a, b) =>
  (a + b).toString

But it doesn’t look very neat.

Nevertheless, the work-around shows that it should be feasible in principle.


If there were paramter groups in Scala, a “partial apply” pattern (which is quite ubiqutous, I’d say) could look way nicer. For example, currently in cats:

final class OptionOps[A](private val oa: Option[A]) extends AnyVal {
  def liftTo[F[_]]: LiftToPartiallyApplied[F, A] = new LiftToPartiallyApplied(oa)
}
object OptionOps {
  final class LiftToPartiallyApplied[F[_], A](oa: Option[A]):
    def apply[E](ifEmpty: => E)(implicit F: ApplicativeError[F, ? >: E]): F[A] =
      ApplicativeError.liftFromOption(oa, ifEmpty)
}

All that hassle is only required to support this syntax:

val opt: Option[String] = ...

opt.liftTo[IO]:
  new Exception("OOPS")

That could be simplified substantially with parameter groups, I guess:

// We could also benefit from `extension` here but it is out of the scope...
final class OptionOps[A](private val oa: Option[A]) extends AnyVal:
  def liftTo[F[_]][E](ifEmpty: => E)(using F: ApplicativeError[F, ? >: E]): F[A] =
      ApplicativeError.liftFromOption(oa, ifEmpty)

Then in opt.liftTo[IO] we define the first type, but let the compiler to infer the rest.

Moreover, it would presumably unify type parameters and regular parameters to some extent.

That could be solved by named type arguments but according to the doc it’s not expected to be a part of scala 3. I’m unsure if maybe scala 4 or if they just put it there not to lose it but don’t intend to use it? very weird to add something to the compiler with the intention of not using it.

1 Like

The reason was purely forward compatibility:
If we add it now, we can’t remove it later

The question then becomes, why might we want to remove it ?

Because named type parameters might be better:

def foo[A, B](x: A): B = ???

foo[B = String](4) // A inferred to =:= Int (or maybe the singleton type 4)

And we don’t want 2 features that solve the same problem
(Because then the user has to make a choice every time, and that’s added mental load)

It is not expected to be part of “Scala 3.0”, that’s very different !
We’re at 3.7 now I think, meaning it could come in 3.8 or 3.9 or …
But there would need to be a SIP first

But I agree that the writing is a bit misleading

3 Likes

Oh, I see: it really works with import scala.language.experimental.namedTypeArguments , thanks!

2 Likes

closely related discussion on the “partial application” aspect: How to solve the “PartiallyApplied” problem? - #2 by s5bug