简体   繁体   English

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

[英]Polymorphic type inference for by-name and by-value types

I've been stuck with a type inference problem and I'm not sure if I'm doing something wrong, there is a bug in the compiler, or it's a limitation on the language 我一直陷在类型推断问题中,我不确定是否做错了什么,编译器中是否有错误,或者是对语言的限制

I've created a dummy example to show the problem, the use case makes no sense, but trust me I have a valid use case for this 我创建了一个虚拟示例来显示问题,用例没有意义,但是请相信我我有一个有效的用例

Let's say i have this code 假设我有这段代码

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")

That works and prints (1,foo) Now, if I change the code to be (notice the by-name argument) 可以工作并打印(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")

It will print (1,MyTest$$Lambda$131/192881625@61d47554) 它将打印(1,MyTest$$Lambda$131/192881625@61d47554)

Now, at this point I could try to pattern match and/or use a TypeTag to extract the value in case is a by-name parameter, but, what I'm actually trying to achieve is to do something like this 现在,在这一点上,我可以尝试进行模式匹配和/或使用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"))

Notice that the main intention is that I should be able to pass either the original type of the argument or a formatter instead, that's why the signature of this extension method uses Formatter[TypeOfOriginalParam] and that's also why I have implicit def noOpFormatter[T](v: T): Formatter[T] for when I don't want any formatting 注意,主要目的是我应该能够传递参数的原始类型或传递格式器,这就是为什么此扩展方法的签名使用Formatter[TypeOfOriginalParam]的原因,这也是为什么我具有implicit def noOpFormatter[T](v: T): Formatter[T]我不想要任何格式时的implicit def noOpFormatter[T](v: T): Formatter[T]

Now, here, I can't do much as the code fails to compile with this error 现在,在这里,我无能为力,因为代码无法通过此错误进行编译

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

I can make it run if I make the second type argument of my implicit class by-name 如果我通过隐式类的第二个参数作为名称,则可以使其运行

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"))

this prints (1,oof) 此打印(1,oof)

Now, the main problem is that I want to do this for any function regardless if any of its arguments is by-value or by-name. 现在,主要的问题是,无论函数的任何参数是按值还是按名称,我都想对任何函数执行此操作。 And that's where I'm stuck, I can create different implicit classes for every possible combination of cases where there is by-name params or not but it wouldn't be practical as I need to do this for every function from Function1 to Function10, and the the amount of possible combinations between by-name and by-value arguments would be massive. 这就是我要坚持的地方,我可以为是否存在按名称的参数的情况的每种可能的组合创建不同的隐式类,但这是不切实际的,因为我需要对从Function1到Function10的每个函数执行此操作,并且名称和值参数之间可能的组合数量将非常庞大。

Any ideas? 有任何想法吗? Should I really need to care about the lazyness of an argument if I'm only interested in the type? 如果我只对类型感兴趣,是否真的需要关心参数的惰性? Am I trying to do something unsupported by design or is maybe a bug in the compiler? 我是在尝试执行设计不支持的操作,还是编译器中的错误?

BTW, this is what I'm trying to avoid 顺便说一句,这就是我要避免的

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")

which it works (notice that I've used formatters or raw values randomly, it shouldn't matter if the original param was by-name or by-value) 它的工作原理(请注意,我随机使用了格式化程序或原始值,原始参数是按名称还是按值都没关系)

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

but it seems super odd to have to write all of that to me 但是不得不将所有这些都写给我似乎很奇怪

I'm not sure I entirely understand what your ultimate goal is, but I can explain to you why you're seeing what you're seeing. 我不确定我是否完全了解您的最终目标是什么,但是我可以向您解释为什么您看到自己看到的东西。

There are two facts about the Scala language that combine to produce the problem you're in: 有关Scala语言的两个事实共同导致您遇到的问题:

  1. By-name => A is not a type; 按名称=> A不是类型; and
  2. By-name has to be part of a method signature because the calling code needs to construct a lazy value. 按名称必须是方法签名的一部分,因为调用代码需要构造一个惰性值。

To see that (1) is true, observe that you cannot do this: 若要查看(1)是否为真,请注意您不能执行以下操作:

val a: => Int = 3

In other words, although (=> Int) => Int is a type, => Int is not a type. 换句话说,尽管(=> Int) => Int是类型,但=> Int不是类型。 You cannot have a value of type => Int . 您不能具有类型=> Int的值。

To see that (2) is true, consider what happens when you call a method with a by-name parameter: 若要确认(2)是正确的,请考虑当您调用带有by-name参数的方法时会发生什么:

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

foo(println("2"))

The calling code needs to know not to pass () as the argument, but rather to pass a promise. 调用代码需要知道不通过()作为参数,而是通过诺言。 The calling code must therefore know that foo takes a by-name parameter, and so the fact that it is a by-name parameter must be a part of the method signature. 因此,调用代码必须知道foo带有一个by-name参数,因此,它是一个by-name参数这一事实必须是方法签名的一部分。 Behind the scenes, the calling code must construct a no-arg method of type () => Unit and pass that. 在后台,调用代码必须构造() => Unit类型的no-arg方法并将其传递。

Now, how do these combine to produce the problem in your code? 现在,如何将它们结合起来以在代码中产生问题?

When you write: 当你写:

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

and pass an argument of type (Int, => String) => String , fact (2) requires that that P2 cannot bind to String . 并传递(Int, => String) => String类型的参数,事实(2)要求P2不能绑定到String If P2 bound to String , then f would have type (Int, String) => String , which contradicts the rule that by-name-ness must be reflected in the method signature. 如果P2绑定到String ,则f类型为(Int, String) => String ,这与必须在方法签名中反映按名称命名的规则相矛盾。 Although your printArgs doesn't call f , it could , so the signature of f must preserve the by-name-ness. 尽管您的printArgs不调用f ,但可以调用f ,所以f的签名必须保留按名称命名。

However, fact (1) requires that P2 cannot bind to => String , because => String is not a type. 但是,事实(1)要求P2无法绑定到=> String ,因为=> String不是类型。 So, the only remaining option is for P2 to bind to () => String . 因此,剩下的唯一选择是让P2绑定到() => String This is compatible with the implementation of by-name parameters as no-arg functions, and ensures that printArgs will receive the correct types from the caller and pass the correct types to f , should it choose to call f . 这与作为no-arg函数的by-name参数的实现兼容,并确保printArgs将从调用方接收正确的类型并将正确的类型传递给f (如果它选择调用f

As a final comment, observe that it is not possible to abstract over by-name parameters because use of a by-name parameter must generate appropriate byte-code. 作为最后的评论,请注意不可能抽象出名称参数,因为使用名称参数必须生成适当的字节码。 Observe that the following two functions must generate different byte-code: 请注意,以下两个函数必须生成不同的字节码:

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

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

Because type parameters in Scala are erased at compile time, it is never possible to use type parameters in a way that would alter a function's behavior. 由于Scala中的类型参数是在编译时删除的,因此永远不可能以会改变函数行为的方式使用类型参数。 Therefore, it is not possible to abstract over by-name arguments using type parameters alone. 因此,不可能仅使用类型参数来抽象名称参数。

This final observation shows how, ultimately, you can solve your problem: You must abandon using pure type parameters and introduce value parameters. 最后的观察结果最终显示了如何解决您的问题:必须放弃使用纯类型参数并引入值参数。 As Mateusz Kubuszok observes, one way to introduce value parameters is to use type-classes. 正如Mateusz Kubuszok观察到的那样,引入值参数的一种方法是使用类型类。 There are other ways, such as type tags or explicit value parameters. 还有其他方法,例如类型标记或显式值参数。 However, in order to alter the behavior of a function, you must rely on something other than type parameters alone. 但是,为了更改函数的行为,您必须仅依赖类型参数以外的其他东西。

I suggested using type classes and here is how I would implement them. 我建议使用类型类,这是我将如何实现它们。

First I would create a type class for printing a single argument. 首先,我将创建一个用于打印单个参数的类型类。

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

Then I would implement instances for handling by-name and by-value parameter types: 然后,我将实现用于处理按名称和按值参数类型的实例:

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 }
}

Except Scala forbid us from declaring by-name type in other places than function declaration syntax. 除了Scala禁止我们在函数声明语法之外的其他地方声明按名称类型。

error: no by-name parameter type allowed here

Which is why we're going to work around this: we'll declare by-name type in a function declaration and lift that function to our type class: 这就是为什么要解决此问题的原因:我们将在函数声明中声明按名称的类型,并将该函数提升为我们的类型类:

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)
}

Now, lets put everything together: 现在,让所有内容放在一起:

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)
  }
}

Finally, we can use the type class in printer to handle all cases at once: 最后,我们可以使用打印机中的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")

would print 会打印

(1,x)
(2,y)

Bonus 1: if you have a type which doesn't print anything useful if you toString it, you can just provide another implicit def/val and extend support to all 3x3 cases. 奖励1:如果您的类型如果对toString打印不会显示任何有用的信息,则可以只提供另一个隐式def / val并将支持扩展到所有3x3情况。

Bonus 2: for this specific example I basically shown how to implement and use Show type class, so what you probably need to do here would be simply reuse existing implementations (and maybe provide implicit def for just by-name case). 奖励2:对于这个特定示例,我基本上展示了如何实现和使用Show type类,因此您在这里可能需要做的就是简单地重用现有的实现(并可能仅按名称提供隐式def)。 But I'll leave that as an exercise for the reader. 但我会将其留给读者练习。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM