Как в Scala вызвать полиморфную функцию, применимую к типу ввода, не зная тип вывода или аргументы полного типа?
Начиная с Scala 2.12 (или 2.13, можно быть уверенным), компилятор может выводить аргументы скрытого типа по нескольким методам:
def commutative[
A,
B
]: ((A, B) => (B, A)) = {???} // implementing omitted
val a = (1 -> "a")
val b = commutative.apply(a)
Последняя строка успешно выведена, к сожалению, для этого требуется экземпляр
a: (Int, String)
будет дано.
Теперь я хотел бы немного покрутить этот API и определить следующую функцию:
def findApplicable[T](fn: Any => Any)
Такой, что
findApplicable[(Int, String)](commutative)
автоматически сгенерировать правильную функцию, специализированную для
A = Int, B = String
. Есть ли способ сделать это в рамках возможностей языка? Или мне придется для этого перейти на scala 3?
ОБНОВЛЕНИЕ 1 следует отметить, что вывод коммутатора может быть любого типа, не обязательно Function2, например, я пробовал следующее определение:
trait SummonedFn[+I, -O] extends (I => O) {
final def summon[II <: I]: this.type = this
}
Затем переопределите
commutative
использовать это:
def commutative[
A,
B
]: SummonedFn[(A, B), (B, A)] = {???} // implementing omitted
val b = commutative.summon[(Int, String)]
К сожалению, не работает, параметры типа не обрабатываются так же, как параметры значения
2 ответа
Если в какой-то момент какой-то call-сайт знает типы аргументов (на самом деле они не
Any => Any
) это можно сделать с помощью классов типов:
trait Commutative[In, Out] {
def swap(in: In): Out
}
object Commutative {
def swap[In, Out](in: In)(implicit c: Commutative[In, Out]): Out =
c.swap(in)
implicit def tuple2[A, B]: Commutative[(A, B), (B, A)] =
in => in.swap
}
На сайте звонка:
def use[In, Out](ins: List[In])(implicit c: Commutative[In, Out]): List[Out] =
ins.map(Commutative.swap(_))
Однако таким образом вы должны передавать как параметры типа, так и параметры. Если есть несколько возможных
Out
s для сингла
In
типа, то тут мало что можно сделать.
Но если ты хочешь иметь
Input
тип =>
Output
тип импликации, вы можете использовать зависимые типы:
trait Commutative[In] {
type Out
def swap(in: In): Out
}
object Commutative {
// help us transform dependent types back into generics
type Aux[In, Out0] = Commutative[In] { type Out = Out0 }
def swap[In](in: In)(implicit c: Commutative[In]): c.Out =
c.swap(in)
implicit def tuple2[A, B]: Commutative.Aux[(A, B), (B, A)] =
in => in.swap
}
Звоните на сайт:
// This code is similar to the original code, but when the compiler
// will be looking for In it will automatically figure Out.
def use[In, Out](ins: List[In])(implicit c: Commutative.Aux[In, Out]): List[Out] =
ins.map(Commutative.swap(_))
// Alternatively, without Aux pattern:
def use2[In](ins: List[In])(implicit c: Commutative[In]): List[c.Out] =
ins.map(Commutative.swap(_))
def printMapped(list: List[(Int, String)]): Unit =
println(list)
// The call site that knows the input and provides implicit
// will also know the exact Out type.
printMapped(use(List("a" -> 1, "b" -> 2)))
printMapped(use2(List("a" -> 1, "b" -> 2)))
Вот как можно решить проблему, зная точный тип ввода. Если вы этого не знаете ... тогда вы не можете использовать компилятор (ни в Scala 2, ни в Scala 3) для генерации этого поведения, поскольку вы должны реализовать эту функциональность с помощью некоторого отражения во время выполнения, например, проверки типов с помощью
isInstanceOf
, приведение к некоторым предполагаемым типам, а затем выполнение предопределенного поведения и т. д.
Я не уверен, что понимаю вопрос на 100%, но похоже, что вы хотите создать какое-то расширенное приложение частичного типа. Обычно вы можете получить такой API, введя промежуточный класс. А чтобы сохранить как можно больше информации о типе, вы можете использовать метод с зависимым возвращаемым типом.
class FindApplicablePartial[A] {
def apply[B](fn: A => B): fn.type = fn
}
def findApplicable[A] = new FindApplicablePartial[A]
scala> def result = findApplicable[(Int, String)](commutative)
def result: SummonedFn[(Int, String),(String, Int)]
И собственно в этом случае, поскольку
findApplicable
сам по себе не заботится о типе (т.е.
B
не имеет привязки к контексту или другого использования), вам даже не нужен промежуточный класс, но вместо этого можно использовать подстановочный / экзистенциальный тип:
def findApplicable[A](fn: A => _): fn.type = fn
Это работает точно так же.