Разница между * (звездочка) и _ (подчеркивание) в параметре типа - PullRequest
1 голос
/ 08 мая 2020

Здесь кто-то говорит, что звездочка является подчеркиванием от scala 3, но я видел такой код в scala 2.13:

def make[F[_]: ContextShift: MonadError[*[_], Throwable]: Effect: Logging](): ...

Есть ли у него тот же смысл и просто укажите, что тип в * не такой, как в _?

1 Ответ

6 голосов
/ 08 мая 2020

_ обозначает (в зависимости от контекста)

  • конструктор типа - если он используется как в определении / ограничении параметра типа
    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[_], потому что он не 't имеет пробел

Типы с "пробелом" часто обозначаются как * -> *, а типы без них как *. Мы могли бы читать * просто как тип, а * -> * как «тип, который принимает другой тип для формирования типа» - или как конструктор типа.

(Высшие типы, подобные только что упомянутому, сложны вещь сама по себе, так что было бы лучше, чтобы вы узнали о них больше, помимо этого вопроса).

* (из плагин доброго проектора ) используется для доброй проекции - синтаксис основан на приведенной выше нотации, чтобы показать, где будет передаваться тип, если мы хотим создать новый тип:

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]

при этом доброта * должна быть * -> * вместо *. Итак, это *[_] означает «мы хотим создать здесь новый конструктор типа, сделав это вместо * параметра, но мы хотим обозначить, что этот параметр имеет вид * -> * вместо ** 1101. *

[F[_]] =>> MonadError[F, Throwable]

, поэтому мы добавим [_], чтобы показать компилятору, что это конструктор типа.

Это довольно много для усвоения, и это должно быть проще, я могу только почувствовать извини и скажи что в Дотти будет понятнее.

...