Как ссылаться на параметр параметрического типа более высокого уровня?

#scala #generics

#scala #обобщения

Вопрос:

Предположим, у вас есть такая черта:

 trait Foo[A]{
    def foo: A
}
  

Я хочу создать функцию, подобную этой:

 def getFoo[A <: Foo[_]](a: A) = a.foo
  

Компилятор Scala выводит Any для возвращаемого типа этой функции.
Как я могу ссылаться на анонимный параметр _ в подписи (или теле) getFoo ?
Другими словами, как я могу отменить анонимность параметра?

Я хочу иметь возможность использовать функцию, подобную

 object ConcreteFoo extends Foo[String] {
  override def foo: String = "hello"
}

val x : String = getFoo(ConcreteFoo)
  

который завершается ошибкой компиляции по очевидным причинам, потому что getFoo неявно объявлен как Any .

Если это невозможно с помощью Scala (2.12, если на то пошло), мне было бы интересно узнать рациональную или техническую причину этого ограничения. Я уверен, что есть статьи и существующие вопросы по этому поводу, но мне, похоже, не хватает правильных условий поиска.


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

 def getFoo[A <: Foo[_]] = (a: A) => a.foo

val f = getFoo[ConcreteFoo.type]

//In some other, unrelated place
val x = f(ConcreteFoo)
  

Поскольку у меня нет параметра типа A , компилятор не может вывести параметры, R и A если я это сделаю

 def getFoo[R, A <: Foo[R]]: (A => R) = (a: A) => a.foo
  

как и предлагалось. Я хотел бы избежать необходимости вручную указывать параметр типа R ( String в данном случае), потому что он кажется избыточным.

Комментарии:

1. Они называют это «анонимным» именно по той причине, что у него нет имени, и поэтому на него нельзя ссылаться. Итак, «деанонимизировать» буквально означает «дать ему имя»: def getFoo[B, A <: Foo[B]](a: A): B = a.foo

Ответ №1:

Чтобы ответить буквально на ваш точный вопрос:

 def getFoo[R, A <: Foo[R]](a: A): R = a.foo
  

Но поскольку вы не используете тип A , вы можете фактически полностью опустить его и <: Foo[..] привязку, сохранив только возвращаемый тип:

 def getFoo[R](a: Foo[R]): R = a.foo
  

Обновление (вопрос был значительно изменен)

Вы могли бы ввести дополнительный apply вызов, который выводит возвращаемый тип из отдельного неявного свидетеля возвращаемого типа:

 trait Foo[A] { def foo: A }

/** This is the thing that remembers the actual return type of `foo`
  * for a given `A <: Foo[R]`.
  */
trait RetWitness[A, R] extends (A => R)

/** This is just syntactic sugar to hide an additional `apply[R]()`
  * invocation that infers the actual return type `R`, so you don't
  * have to type it in manually.
  */
class RetWitnessConstructor[A] {
  def apply[R]()(implicit w: RetWitness[A, R]): A => R = w
}

def getFoo[A <: Foo[_]] = new RetWitnessConstructor[A]
  

Теперь это выглядит почти так, как вы хотели, но вы должны предоставить неявное значение, и вам нужно вызвать getFoo[ConcreteFoo.type]() дополнительную пару круглых скобок:

 object ConcreteFoo extends Foo[String] {
  override def foo: String = "hey"
}

implicit val cfrw = new RetWitness[ConcreteFoo.type, String] {
  def apply(c: ConcreteFoo.type): String = c.foo
}

val f = getFoo[ConcreteFoo.type]()
val x: String = f(ConcreteFoo)
  

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

Комментарии:

1. Хорошо, это было быстро и просто. Спасибо. Я полагаю, что мой MCVE был недостаточно точным в отношении моего фактического использования. Я добавил пример для своего использования, не могли бы вы расширить свой ответ на этот вопрос?

2. @LukeG Да, это тоже можно сделать. Так много странных вещей можно сделать с системой типов Scala… Обновлен ответ.

3. Спасибо за удивительное дополнение. На данный момент я решил, что это действительно не стоит того. Я пытался использовать его с комбинаторами синтаксического анализа, где возвращаемого типа getFoo[A] нет A => R , но Parser[R] . Я не уверен, что делаю что-то не так, но, по-видимому, становится только хуже.

4. Комбинаторы синтаксического анализа @LukeG обычно используются в аналогичных контекстах аналогичным образом, и обычно они выглядят примерно одинаково. Я никогда не чувствовал необходимости в чем-то подобном тому, что вы пытались здесь при использовании комбинаторов синтаксического анализа. Возможно, это проблема XY.

5. Допустим, это простая проблема, излишне усложненная. По (более или менее обязательным) академическим причинам у меня есть отдельный сканер и анализатор. Сканер генерирует List[Token] . Некоторые токены расширяются trait ValueToken[A]{def value: A} (в основном литералы, то есть токены со «значением», противоположным ключевым словам и разделителям). Всякий раз, когда я использую эти токены в правиле грамматики, я хотел, чтобы это было как можно проще. Будучи перефекционистом, которым я являюсь, я хотел иметь его в общем виде для каждого T <: ValueToken[_] вместо того, чтобы записывать его.