Для чего полезен `class A [_]`?

Типы символов class A[_] или def a[_](x: Any) имеют параметр типа, на который нельзя ссылаться в теле, поэтому я не понимаю, для чего он полезен и почему он компилируется. Если попытаться сослаться на этот параметр типа, выдается ошибка:

scala> class A[_] { type X = _ }
<console>:1: error: unbound wildcard type
       class A[_] { type X = _ }
                             ^

scala> def a[_](x: Any) { type X = _ }
<console>:1: error: unbound wildcard type
       def a[_](x: Any) { type X = _ }
                                   ^

Может ли кто-нибудь сказать мне, есть ли у такого типа вариант использования в Scala? Точнее, я не имею в виду экзистенциальные типы или типы более высокого порядка в параметрах типа, а только те маленькие [_], которые образуют полный список параметров типа.


person kiritsuku    schedule 08.10.2012    source источник


Ответы (4)


Поскольку я не получил ожидаемых ответов, я перенес это на язык Scala.

Я вставляю сюда ответ Ларса Хупеля (так что все кредиты относятся к нему), который в основном объясняет то, что я хотел знать:

Я собираюсь нанести удар здесь. Я думаю, что использование этой функции становится понятным, когда речь идет о членах типа.

Предположим, вам необходимо реализовать следующую черту:

trait Function {
  type Out[In]
  def apply[In](x: In): Out[In]
}

Это будет (общая) функция, в которой тип возвращаемого значения зависит от типа ввода. Один пример для экземпляра:

val someify = new Function {
  type Out[In] = Option[In]   def
  apply[In](x: In) = Some(x)
}

someify(3) res0: Some[Int] = Some(3)

Все идет нормально. Теперь, как бы вы определили постоянную функцию?

val const0 = new Function {
  type Out[In] = Int
  def apply[In](x: In) = 0
}

const0(3) res1: const0.Out[Int] = 0

(Тип const0.Out[Int] эквивалентен Int, но не печатается таким образом.)

Обратите внимание, что параметр типа In на самом деле не используется. Итак, вот как вы могли бы написать это с помощью _:

val const0 = new Function {
  type Out[_] = Int
  def apply[In](x: In) = 0
}

Думайте о _ в этом случае как об имени параметра типа, на который фактически нельзя ссылаться. Это для функции на уровне типа, которая не заботится о параметре, как и на уровне значения:

(_: Int) => 3 res4: Int => Int = <function1>

Кроме …

type Foo[_, _] = Int
<console>:7: error: _ is already defined as type _
       type Foo[_, _] = Int

Сравните это с:

(_: Int, _: String) => 3 res6: (Int, String) => Int = <function2>

Итак, в заключение:

type F[_] = ConstType // when you have to implement a type member def
foo[_](...) // when you have to implement a generic method but don't
            // actually refer to the type parameter (occurs very rarely)

Главное, что вы упомянули, class A[_], полностью симметрично этому, за исключением того, что здесь нет реального варианта использования.

Учти это:

trait FlyingDog[F[_]] { def swoosh[A, B](f: A => B, a: F[A]): F[B] }

Теперь предположим, что вы хотите создать экземпляр FlyingDog для своего старого доброго class A.

new FlyingDog[A] { ... }
// error: A takes no type parameters, expected: one
// (aka 'kind mismatch')

Есть два решения:

  1. Вместо этого объявите class A[_]. (Не делай этого.)

  2. Используйте лямбда типа:

    new FlyingDog[({ type λ[α] = A })#λ]
    

или даже

new FlyingDog[({ type λ[_] = A })#λ]
person kiritsuku    schedule 10.10.2012

У меня было несколько случайных представлений о том, что это может означать здесь:

https://issues.scala-lang.org/browse/SI-5606

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

Другой вариант использования - когда параметр типа устарел, потому что улучшения в выводе типа делают его излишним.

trait T[@deprecated("I'm free","2.11") _, B <: S[_]] 

Тогда, гипотетически, можно было бы предупредить об использовании T[X, Y], но не T[_, Y].

Хотя не очевидно, будет ли аннотация до (стиль параметра значения) или после (аннотация к стилю типа).

[Edit: «почему он компилируется»: case class Foo[_](i: Int) все еще нормально вылетает на 2.9.2]

person som-snytt    schedule 09.10.2012

Подчеркивание в Scala указывает на экзистенциальный тип, то есть параметр неизвестного типа, который имеет два основных использования:

  • Он используется для методов, которые не заботятся о параметре типа
  • Он используется для методов, в которых вы хотите выразить, что один параметр типа является конструктором типа.

Конструктор типа - это, по сути, то, что нуждается в параметре типа для создания конкретного типа. Например, вы можете взять следующую подпись.

def strangeStuff[CC[_], B, A](b:B, f: B=>A): CC[A] 

Это функция, которая для некоторых CC[_], например List[_], создает List[A], начиная с B, и функцию B=>A.

Почему это может быть полезно? Что ж, оказывается, если вы используете этот механизм вместе с неявными выражениями и классами типов, вы можете получить то, что называется специальным полиморфизмом, благодаря аргументам компилятора.

Представьте, например, что у вас есть более высокий тип: Container[_] с иерархией конкретных реализаций: BeautifulContainer[_], BigContainer[_], SmallContainer[_]. Чтобы построить контейнер, вам понадобится

trait ContainerBuilder[A[_]<:Container[_],B] { 

def build(b:B):A[B]

}

Таким образом, по сути, ContainerBuilder - это то, что для определенного типа контейнера A [_] может построить A [B], используя B.

Хотя это было бы полезно? Что ж, вы можете представить, что у вас может быть функция, определенная где-то еще, например:

def myMethod(b:B)(implicit containerBuilder:ContainerBuilder[A[_],B]):A[B] = containerBuilder.build(b)

А затем в своем коде вы можете сделать:

val b = new B()
val bigContainer:BigContainer[B] = myMethod(b)
val beautifulContainer:BeautifulContainer[B] = myMethod(b)

Фактически, компилятор будет использовать требуемый тип возвращаемого значения myMethod для поиска неявного, который удовлетворяет требуемым ограничениям типа, и выдаст ошибку компиляции, если не существует ContainerBuilder, который неявно соответствует требуемым ограничениям.

person Edmondo1984    schedule 08.10.2012
comment
Я не имел в виду экзистенциальные типы или более высокие типы как параметры типа, я имел в виду параметр типа, который существует, но на него нельзя ссылаться. - person kiritsuku; 08.10.2012
comment
какой предыдущий? Тот, который был удален? - person kiritsuku; 08.10.2012
comment
Автор Стефана. Второй пример, который вы привели, незаконен. Почему вам нужен универсальный метод без универсального параметра? - person Edmondo1984; 09.10.2012
comment
Я не хочу этого, мне просто интересно, почему он компилируется, когда в этом нет никакого смысла. - person kiritsuku; 09.10.2012
comment
class A[_] компилирует class A[_] { type X = _ } нет. Я удивляюсь первому, потому что не вижу для него варианта использования (потому что последний не компилируется, почему удивление становится еще больше). - person kiritsuku; 09.10.2012
comment
вы не можете, потому что _ это означает неизвестный тип. как можно назначить то, что неизвестно? - person Edmondo1984; 09.10.2012

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

trait Something[A] {
  def stringify: String
}

class Foo extends Something[Bar] {
  def stringify = "hop"
}

object App {
  def useSomething(thing: Something[_]) :String = {
    thing.stringify
  }
}
person Stephane Godbillon    schedule 08.10.2012
comment
Вы имеете в виду экзистенциальный тип, что я имел в виду не так. - person kiritsuku; 08.10.2012