3

由于 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, B = String,不幸的是,这需要a: (Int, String)给出一个实例。

现在我想稍微扭曲一下这个 API 并定义以下函数:

def findApplicable[T](fn: Any => Any)

这样可以findApplicable[(Int, String)](commutative)自动生成专用于A = Int, B = String. 有没有办法在语言的能力范围内做到这一点?或者我必须升级到 scala 3 才能做到这一点?

UPDATE 1应该注意,commutative 的输出可以是任何类型,不一定是 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)]

糟糕,这不起作用,类型参数没有像值参数一样得到同等对待

4

2 回答 2

1

我不确定我是否 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(即B没有上下文绑定或其他用途),您甚至不需要中间类,而是可以使用通配符/存在类型:

def findApplicable[A](fn: A => _): fn.type = fn

这同样有效。

于 2021-09-15T13:51:14.560 回答
1

如果在某些时候某些调用站点知道参数的类型(实际上它们不是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(_))

但是,这样您必须同时传递类型参数InOut类型参数。Out如果单个类型有多个可能的 s In,那么您无能为力。

但是如果你想有Inputtype => Outputtype 含义,你可以使用依赖类型:

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,强制转换为一些假设类型,然后运行预定义的行为等。

于 2021-09-15T13:42:48.613 回答