代码之家  ›  专栏  ›  技术社区  ›  ultrasecr.eth

按名称和按值类型的多态类型推断

  •  8
  • ultrasecr.eth  · 技术社区  · 7 年前

    我一直被一个类型推断问题困扰着,我不确定我是否做错了什么,编译器中是否有一个bug,或者是语言上的一个限制

    我已经创建了一个虚拟的例子来说明这个问题,用例没有意义,但是相信我,我有一个有效的用例

    假设我有这个密码

    val function: (Int, String) => String = (_, _) => ""
    
    implicit class Function2Ops[P1, P2, R](f: (P1, P2) => R) {
       def printArgs(p1: P1, p2: P2): Unit = println(p1, p2)
    }
    
    function.printArgs(1, "foo")
    

    它可以工作和打印 (1,foo) 现在,如果我将代码改为(注意by name参数)

    val function: (Int, => String) => String = (_, _) => ""
    
    implicit class Function2Ops[P1, P2, R](f: (P1, P2) => R) {
       def printArgs(p1: P1, p2: P2): Unit = println(p1, p2)
    }
    
    function.printArgs(1, "foo")
    

    (1,MyTest$$Lambda$131/192881625@61d47554)

    现在,我可以尝试模式匹配和/或使用TypeTag提取值,以防是by name参数,但是, 我真正想做的就是做这样的事

    trait Formatter[T] {
      def format: String
    }
    
    case class ReverseStringFormat(v: String) extends Formatter[String] {
      override def format: String = v.reverse
    }
    
    case class PlusFortyOneFormat(v: Int) extends Formatter[Int] {
      override def format: String = (v + 41).toString
    }
    
    implicit def noOpFormatter[T](v: T): Formatter[T] = new Formatter[T] {
      override def format: String = v.toString
    }
    
    val function: (Int, => String) => String = (_, _) => ""
    
    implicit class Function2Ops[P1, P2, R](f: (P1, P2) => R) {
       def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println( p1.format, p2.format)
    }
    
    function.printArgs(1, ReverseStringFormat("foo"))
    

    Formatter[TypeOfOriginalParam] 这也是为什么我 implicit def noOpFormatter[T](v: T): Formatter[T] 当我不需要任何格式的时候

    Error:(22, 40) type mismatch;
       found   : ReverseStringFormat
       required: Formatter[=> String]
       function.printArgs(1, ReverseStringFormat("foo"))
    

    val function: (Int, => String) => String = (_, _) => ""
    
    implicit class Function2Ops[P1, P2, R](f: (P1, => P2) => R) {
       def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println( p1.format, p2.format)
    }
    
    function.printArgs(1, ReverseStringFormat("foo"))
    

    (1,oof)

    现在,主要的问题是我想对任何函数这样做,不管它的参数是按值还是按名称。

    有什么想法吗?如果我只对类型感兴趣,我真的需要关心争论的懒散性吗?我是想做一些设计不支持的事情,还是编译器中有一个bug?

    顺便说一句,这是我想避免的

    val function: (Int, => String) => String     = (_, _) => ""
    val function2: (Int, String) => String       = (_, _) => ""
    val function3: (=> Int, String) => String    = (_, _) => ""
    val function4: (=> Int, => String) => String = (_, _) => ""
    
    implicit class Function2Ops[P1, P2, R](f: (P1, => P2) => R) {
      def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println("f1", p1.format, p2.format)
    }
    
    implicit class Function2Opss[P1, P2, R](f: (P1, P2) => R) {
      def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println("f2", p1.format, p2.format)
    }
    
    implicit class Function2Opsss[P1, P2, R](f: (=> P1, P2) => R) {
      def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println("f3", p1.format, p2.format)
    }
    
    implicit class Function2Opssss[P1, P2, R](f: (=> P1, => P2) => R) {
      def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println("f4", p1.format, p2.format)
    }
    
    function.printArgs(1, "foo")
    function2.printArgs(1, ReverseStringFormat("foo"))
    function3.printArgs(1, "foo")
    function4.printArgs(PlusFortyOneFormat(1), "foo")
    

    它的工作原理(注意,我随机使用了格式化程序或原始值,不管原始参数是按名称还是按值)

    (f1,1,foo)
    (f2,1,oof)
    (f3,1,foo)
    (f4,42,foo)
    

    但把这些都写给我似乎太奇怪了

    2 回复  |  直到 7 年前
        1
  •  1
  •   Mateusz Kubuszok    7 年前

    首先,我将创建一个类型类来打印单个参数。

    trait PrintArg[A] { def printArg(a: A): String }
    

    object PrintArg extends PrintArgImplicits {
      def apply[A](implicit pa: PrintArg[A]): PrintArg[A] = pa
    }
    trait PrintArgImplicits extends PrintArgLowLevelImplicits {
      implicit def printByName[A] = new PrintArg[=> A] { def printArg(a: => A) = a.toString }
    }
    trait PrintArgLowLevelImplicits {
      implicit def printByValue[A] = new PrintArg[A] { def printArg(a: A) = a.toString }
    }
    

    Scala禁止我们在函数声明语法之外的其他地方按名称类型声明。

    error: no by-name parameter type allowed here
    

    def instance[A](fun: A => String): PrintArg[A] = new PrintArg[A] { def printArg(a: A) = fun(a) }
    
    def printByName[A] = {
      val fun: (=> A) => String = _.toString
      PrintArg.instance(fun)
    }
    def printByValue[A] = {
      val fun: A => String = _.toString
      PrintArg.instance(fun)
    }
    

    现在,让我们把所有东西放在一起:

    trait PrintArg[A] { def printArg(a: A): String }
    object PrintArg extends PrintArgImplicits {
      def apply[A](implicit pa: PrintArg[A]): PrintArg[A] = pa
      def instance[A](fun: A => String): PrintArg[A] = new PrintArg[A] { def printArg(a: A) = fun(a) }
    }
    trait PrintArgImplicits extends PrintArgLowLevelImplicits {
      implicit def printByName[A] = {
        val fun: (=> A) => String = _.toString
        PrintArg.instance(fun)
      }
    }
    trait PrintArgLowLevelImplicits {
      implicit def printByValue[A] = {
        val fun: A => String = _.toString
        PrintArg.instance(fun)
      }
    }
    

    最后,我们可以使用printer中的type类一次处理所有情况:

    implicit class Function2Ops[P1: PrintArg, P2: PrintArg, R](f: (P1, P2) => R) {
      def printArgs(p1: P1, p2: P2): Unit =
        println(PrintArg[P1].printArg(p1), PrintArg[P2].printArg(p2))
    }
    
    val function1: (Int, String) => String = (_, _) => ""
    val function2: (Int, => String) => String = (_, _) => ""
    
    function1.printArgs(1, "x")
    function2.printArgs(2, "y")
    

    将打印

    (1,x)
    (2,y)
    

    toString

    奖励2:对于这个具体的例子,我基本上展示了如何实现和使用 Show 在这里 只需重用现有的实现(并且可能只为名称大小写提供隐式def)。但我会留给读者作为练习。

        2
  •  1
  •   Owen    7 年前

    我不确定我完全理解你的最终目标是什么,但我可以向你解释为什么你看到了你看到的。

    关于Scala语言,有两个事实共同导致了您所处的问题:

    1. => A 不是类型;和
    2. 按名称必须是方法签名的一部分,因为调用代码需要构造一个延迟值。

    val a: => Int = 3
    

    换句话说,尽管 (=> Int) => Int 是一种类型, => Int 不是类型。不能有类型为的值 =>内景

    为了验证(2)的正确性,请考虑使用by name参数调用方法时发生的情况:

    def foo(a: => Unit) {
      println("1")
      a
    }
    
    foo(println("2"))
    

    调用代码需要知道不能传递 () foo 接受按名称参数,因此它是按名称参数的事实必须是方法签名的一部分。在幕后,调用代码必须构造类型为no arg的方法 () => Unit

    现在,如何将这些结合起来在代码中产生问题?

    implicit class Function2Ops[P1, P2, R](f: (P1, P2) => R) {
       def printArgs(p1: P1, p2: P2): Unit = println(p1, p2)
    }
    

    并传递一个类型为 (Int, => String) => String ,事实(2)要求 P2 String . 如果 第2页 字符串 f 应该是那种类型的 (Int, String) => String ,这与方法签名中必须反映按名称的属性的规则相矛盾。尽管你的 printArgs 不打电话 f 能够 ,所以 f

    然而,事实(1)要求 第2页 => String ,因为 不是类型。所以,剩下的唯一选择就是 第2页 () => String . 这与按名称参数作为无参数函数的实现兼容,并确保 打印参数 将从调用者接收正确的类型并将正确的类型传递给 ,它应该选择呼叫吗 f .

    作为最后一点,请注意不可能按名称参数进行抽象,因为使用按名称参数必须生成适当的字节码。请注意,以下两个函数必须生成不同的字节码:

    def foo(a: => Unit) {
      println(a)
    }
    
    def foo(a: Unit) {
      println(a)
    }
    

    因为Scala中的类型参数在编译时被删除,所以永远不可能以改变函数行为的方式使用类型参数。因此,仅使用类型参数不可能按名称抽象参数。

    最后的观察结果显示了最终如何解决问题:必须放弃使用纯类型参数,而引入值参数。正如mateuszkubuszok观察到的,引入值参数的一种方法是使用类型类。还有其他方法,例如类型标记或显式值参数。但是,为了改变函数的行为,必须依赖于类型参数以外的其他参数。

    推荐文章