I have been experimenting with the new given
syntaxes in Scala 3.6.1. Fairly successfully, in that with some effort it’s been possible to migrate an existing codebase onto the new syntax without hitting any showstopper issues
I notice the new documentation revisions seem to emphasize the use of Context Bound syntax, now enhanced with aggregation and aliases (eg T: {Eq, Show as s}
).
But there’s one aspect of using context bounds that remains awkward to express even in Scala 3.6, and that is when both:
- The context bounds are capability traits on a higher-kinded effect type (
F[_]
). - The capability traits also take other type parameters
In this case, one wants to express the context bound as a type with a higher-kinded hole, which the effect type will slot into.
Here’s a real life example, from a telemetry generation domain:
given [F[_]: {Sync, UUIDGen, [F[_]] =>> Tell[F, SpanLogRecord], [F[_]] =>> Stateful[F, List[SpanLogRecord.BeginSpan]]}] => SpanLog[F]
In natural language, this says: Given a effect type F[_], the SpanLog[F]
capability requires:
- The
Sync[F]
andUUIDGen[F]
capabilities (from Cats Effect library) - The
Tell[F, SpanLogRecord]
capability (from Cats MTL library [1]) which abstracts over the ability to emitSpanLogRecord
s to an output channel - The
Stateful[F, List[SpanLogRecord.BeginSpan]]
capability, which abstracts over the ability to get/put aList[SpanLogRecord.BeginSpan]
to a stateful store
However, you can see that bounds Sync
and UUIDGen
can be written gracefully, whereas the subsequent two are verbose… [F[_]] =>> Tell[F, SpanLogRecord], [F[_]] =>> Stateful[F, List[SpanLogRecord.BeginSpan]]}
… ugh!!
The difference is the latter two also take other type parameters. And so there’s a need to indicate where in the whole type the F[_]
should fit. Currently this requires a type lambda.
A more succinct syntax option has been termed a “higher-kinded type lambda placeholder” and unfortunately it remains yet unavailable in Scala 3. This is a sorely missed reversion, relative to the syntactic capabilities we had with Scala2+KindProjector plugin [2].
With such syntax, in future it might look like:
given [F[_]: {Sync, UUIDGen, Tell[_, SpanLogRecord], Stateful[_, List[SpanLogRecord.BeginSpan]]}] => SpanLog[F]
Yes please!
PS
[1] Cats MTL defines general capability traits like Tell
and Stateful
that are completely agnostic/unrelated to monad-transformers. There’s an issue to clarify the docs around this oft-misunderstood aspect, but someone has been too lazy to fix it yet
[2] I noticed the Scala 3 documentation “Wildcard Arguments in Types” covering the proposed replacement for Kind Projector is sketchy and slightly inaccurate - it would benefit from a review. I think the correct flag is now -Xkind-projector:underscores
. It could also include some essential details from the 2 → 3 migration notes, which AFAICT are not documented anywhere in the Scala 3 reference. And perhaps mention of which Scala release underscore placeholder syntax will become enabled without needing the -X
flag.