#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[_]
вместо того, чтобы записывать его.