Reputation: 518
Here someone says that star is underscore from scala 3, but I've seen some code like this in scala 2.13:
def make[F[_]: ContextShift: MonadError[*[_], Throwable]: Effect: Logging](): ...
Does it have a same meaning and just specify that type in * is not the same as in _?
Upvotes: 3
Views: 1552
Reputation: 27535
_
denotes (depending on context)
def foo[F[_]]: Unit
def bar(f: F[_]): F[_]
Here we want to understand the type constructor.
Type constructor would be (simplifying) that F
of something, that doesn't yet have that something defined, but we can apply A
to it and make it a F[A]
. E.g.
List
could be passed as F[_]
because it has a gap, if we fill it with e.g. String
it could become List[String]
Option
could be passed as F[_]
as well, it has a gap, if we filled it with e.g. Int
it would become Option[Int]
Double
cannot be used as F[_]
, because it doesn't have a gapTypes with a "gap" are often denoted as * -> *
, while types without them as *
. We could read *
simply as a type, while * -> *
as "type that takes another type to form a type" - or a type constructor.
(Higher-kinded types like one just mentioned are complex thing on its own, so it would be better for you to learn about them more outside of that question).
*
(from kind projector plugin) is used for kind projection - the syntax is inspired from the notation above to show where type would be passed if we wanted to create a new type:
Monad[F[List[*]]]
is really like:
type UsefulAlias[A] = F[List[A]]
Monad[UsefulAlias]
except that it works without a type alias.
If it was Dotty, it could be better expressed with a type lambda:
// Monad[F[List[*]]] is equal to
[A] =>> Monad[List[A]]
In your example:
def make[F[_]: ContextShift: MonadError[*[_], Throwable]: Effect: Logging](): ...
F[_]
is defined as type constructor - so you cannot pass there String
, Int
or Byte
, but you could pass there List
, Future
or Option
(because they take one type parameter)F[_]: ContextShift
is a shortcut for [F[_]](implicit sth: ContextShift[F])
- we can see that ContextShift
takes as a parameter something that takes a type parameter on its own (like F[_]
)[F[_]: MonadError[*[_], Throwable]
could be expanded to:
type Helper[G[_]] = MonadError[G, Throwable]
[F[_]: Helper]
which in turn could be rewritten as
type Helper[G[_]] = MonadError[G, Throwable]
[F[_]](implicit me: Helper[F])
or using a type lambda
[F[_]] =>> MonadError[F, Throwable]
It would probably be easier to read if it was written as:
def make[F[_]: ContextShift: MonadError[*, Throwable]: Effect: Logging]():
Thing is, that *
would suggest that expected type is
[A] =>> MonadError[A, Throwable]
meanwhile kindness of *
should be * -> *
instead of *
. So this *[_]
means "we want to create a new type constructor here by making this thing in place of *
a parameter, but we want to denote that this parameter is of kind * -> *
instead of *
[F[_]] =>> MonadError[F, Throwable]
so we'll add [_]
to show the compiler that it is a type constructor.
It is quite a lot to absorb, and it should be easier, I can only feel sorry and say that in Dotty it will be clearer.
Upvotes: 12