Разница между * (звездочка) и _ (подчеркивание) в параметре типа
Здесь кто-то говорит, что звездочка - это подчеркивание из scala 3, но я видел такой код в scala 2.13:
def make[F[_]: ContextShift: MonadError[*[_], Throwable]: Effect: Logging](): ...
Имеет ли он такое же значение и просто указывает, что тип в * не такой же, как в _?
1 ответ
_
обозначает (в зависимости от контекста)
- конструктор типа - если используется как в определении / ограничении параметра типа
def foo[F[_]]: Unit
- экзистенциальный тип - если применяется к чему-то, что должно использоваться как правильный тип
def bar(f: F[_]): F[_]
Здесь мы хотим понять конструктор типа.
Конструктор типа будет (упрощенно), что F
чего-то, что еще не определено, но мы можем применить A
к нему и сделать его F[A]
. Например
List
можно передать какF[_]
потому что в нем есть пробел, если мы заполним его, например,String
это могло статьList[String]
Option
можно передать какF[_]
также есть пробел, если мы заполнили его, напримерInt
это станетOption[Int]
Double
не может использоваться какF[_]
, потому что в нем нет промежутка
Типы с "пробелом" часто обозначаются как * -> *
, а типы без них как *
. Мы могли читать*
просто как тип, а * -> *
как "тип, который принимает другой тип для формирования типа" - или конструктор типа.
(Высокородные типы, подобные только что упомянутому, сами по себе сложны, поэтому было бы лучше, если бы вы узнали о них больше, помимо этого вопроса).
*
(от плагина доброго проектора) используется для проекции вида - синтаксис основан на обозначении выше, чтобы показать, куда будет передаваться тип, если мы хотим создать новый тип:
Monad[F[List[*]]]
действительно нравится:
type UsefulAlias[A] = F[List[A]]
Monad[UsefulAlias]
за исключением того, что он работает без псевдонима типа.
Если бы это была Дотти, это можно было бы лучше выразить с помощью лямбды типа:
// Monad[F[List[*]]] is equal to
[A] =>> Monad[List[A]]
В вашем примере:
def make[F[_]: ContextShift: MonadError[*[_], Throwable]: Effect: Logging](): ...
F[_]
определяется как конструктор типа - поэтому вы не можете пройти тудаString
,Int
илиByte
, но вы могли пройти тамList
,Future
илиOption
(потому что они принимают один параметр типа)F[_]: ContextShift
это ярлык для[F[_]](implicit sth: ContextShift[F])
- мы видим, чтоContextShift
принимает в качестве параметра что-то, что принимает параметр типа сам по себе (например,F[_]
)[F[_]: MonadError[*[_], Throwable]
можно расширить до:
который, в свою очередь, может быть переписан какtype Helper[G[_]] = MonadError[G, Throwable] [F[_]: Helper]
или используя лямбда типаtype Helper[G[_]] = MonadError[G, Throwable] [F[_]](implicit me: Helper[F])
[F[_]] =>> MonadError[F, Throwable]
Вероятно, было бы легче читать, если бы это было написано как:
def make[F[_]: ContextShift: MonadError[*, Throwable]: Effect: Logging]():
Дело в том, что *
предположил бы, что ожидаемый тип
[A] =>> MonadError[A, Throwable]
Между тем доброта *
должно быть * -> *
вместо того *
. Так это*[_]
означает "мы хотим создать здесь конструктор нового типа, сделав это вместо *
параметр, но мы хотим обозначить, что этот параметр имеет вид * -> *
вместо того *
[F[_]] =>> MonadError[F, Throwable]
так что мы добавим [_]
чтобы показать компилятору, что это конструктор типа.
Впитать довольно много, и должно быть легче, могу только пожалеть и сказать, что в Дотти будет понятнее.