Как в 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(_))

Однако таким образом вы должны передавать как параметры типа, так и параметры. Если есть несколько возможных Outs для сингла 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

Это работает точно так же.

Другие вопросы по тегам