Context bounds for type parameters already in scope

Often I find myself wanting an implicit typeclass for a type that is defined in an outer scope, and that means I have to fallback to full implicit notation

trait Foo[F[_]] { def foo(implicit F: Monad[F]) = ... }

It would be really nice to be able to use context bounds with a notation that says “this type is already defined”, e.g.

trait Foo[F[_]] { def foo[@F: Monad] = ... }

Predictable names, in general for context bounds, would also be great. e.g. the generated value would be called F in the above. If there are multiple typeclasses for the same type then name them F1, F2, etc. If there is already a parameter of the same name then fail to compile (breaks backwards source compatibility so perhaps behind a compiler flag).

2 Likes

Hello,

How about:

trait Foo[F[_]] { def foo[F2[_] <: F[_] : Monad] = ... }

(haven’t tested this)

Best, Oliver

I’d like to avoid subtyping, especially in the context of implicits.

I agree that, within Scala’s way for encoding the type-class idiom, having to use implicit parameters for this case, while transparent, can be too cumbersome. I regret to say that this syntax would be too similar to (and thus easy to mistake with) that for declaring type parameters.

I wonder, though, if it would be possible to achieve a similar effect with a form of guarded declarations, before the def, indicating under what conditions (implicit predicates and values) would the method be applicable.

trait Foo[F[_]] {
    [| F: Monad |] def foo = ... 
} 

This would not only apply to implicit type-class instances on the parameter F, but it could also apply to cases like Option.flatten.

class Opt[A] {
    [B | A <:< Option[B] ] def flatten: Option[B] = ... 
} 

Such a syntax would carry an intuition of "if your types are such that P, Q, and R, then there is this declarations available. Provided that this is always referred as syntactic sugar to the complex original statements, it would not be difficult.

It is worth mentioning, at this point, that such syntactic sugar may be relatively easy to define as a macro annotation with Scala Meta. You do not need any semantic information in here, since you just de-sugar your syntax into the old one.

4 Likes

Then what about this? :upside_down_face:

trait Foo[F[_]] { def foo[G[X]>:F[X]<:F[X]:Monad] = ... }

I tried and it seems to work. But it introduces a redundant type parameter and is almost as verbose, so it’s not a good solution.

I think the real problem is that the syntax of implicits is just so cumbersome. It would be nice to have a more concise way to list implicit parameters, such as a special {...} argument list (like in Agda), where you don’t necessarily give a name to each parameter:

trait Foo[F[_]] { def foo(n:Int){Monad[F]} = ... }
// instead of:
trait Foo[F[_]] { def foo(n:Int)(implicit F: Monad[F]) = ... }
3 Likes

I think I once saw someone suggest to let [] mean “infer by the compiler” and () “provide by the user”.

I’d say mentioning names in the square brackets part of definition without declaring new parameter is suspicious.
Real trouble in the (implicit ...) syntax is really long key word implicit before first parameter. The Martin proposed here https://github.com/lampepfl/dotty/issues/1260 some new syntax.
replacing def foo(implicit F: Monad[F]) with def foo?(F : Monad[F])
We also probably need some special version of implicit parameter clause defining anonymous parameter like
def foo?(Monad[F]) that is analogous to def foo?(ev: Monad[F]) and beside being shorter also ensures that name could not be shadowed, and instance will be available regardless of naming in inner scopes

1 Like

This is indeed a bit of a strange syntax, but it does have its place. I find it especially useful where a function has many implicits that are simply “forwarded” to other functions that it calls.