【问题标题】:In Scala, how to summon a polymorphic function applicable to an input type, without knowing the output type or full type arguments?在 Scala 中,如何在不知道输出类型或完整类型参数的情况下调用适用于输入类型的多态函数?
【发布时间】:2021-11-04 14:16:33
【问题描述】:

由于 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)]

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

【问题讨论】:

  • 不确定你为什么想要/需要findApplicable,你能详细说明一下吗?
  • 它用于类似于 LEANprover 的“策略模式”(typista.org/lean-for-scala-programmers-4)的半自动类型验证。 “commutative”函数是 (A, B) 的建设性证明的一个示例(有关此类比的详细信息,请参见 Curry-Howard 同构:en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence)。在战术模式下,用户只给出证明的名称和输入,编译器应该确定其余的
  • "commutative" 是玩具示例的一部分,生产用途将用于证明复杂张量代数管道和神经架构的稳健性:github.com/tribbloid/shapesafe
  • 请注意,除非您将 commutative 的返回类型替换为 ((A, B)) =&gt; (B, A),否则您的第一个示例不会编译。如果您还想对函数的数量进行抽象,那么您还有另一个问题。

标签: scala scala-2.13 polymorphic-functions


【解决方案1】:

如果某个调用站点知道参数的类型(它们实际上不是Any =&gt; 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 作为类型参数。如果单个In 类型有多个可能的Outs,那么您无能为力。

但是如果你想有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 检查类型,强制转换为一些假定的类型,然后运行预定义的行为等。

【讨论】:

    【解决方案2】:

    我不确定我是否 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
    

    这同样有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-24
      • 1970-01-01
      • 2020-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多