磁铁图案与过载方法

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (1)
  • 关注 (0)
  • 查看 (11)

Scala如何为非重载和重载方法解析从“Magnet模式”中的隐式转换有很大的不同。

假设有一个特征Apply(A)“磁模式”的变化),具体实施如下。

trait Apply[A] {
 def apply(): A
}
object Apply {
  implicit def fromLazyVal[A](v: => A): Apply[A] = new Apply[A] {
    def apply(): A = v
  }
}

现在我们创造了一个特征Foo有一个单曲apply以…为例Apply所以我们可以传递任意类型的任何值。A因为有一个隐式转换A => Apply[A]...

trait Foo[A] {
  def apply(a: Apply[A]): A = a()
}

我们可以使用REPL

scala> val foo = new Foo[String]{}
foo: Foo[String] = $anon$1@3a248e6a

scala> showCode(reify { foo { "foo" } }.tree)
res9: String =    
$line21$read.foo.apply(
  $read.INSTANCE.Apply.fromLazyVal("foo")
)

假设我们通过了一个复合表达式(与;)apply方法。

scala> val foo = new Foo[Int]{}
foo: Foo[Int] = $anon$1@5645b124

scala> var i = 0
i: Int = 0

scala> showCode(reify { foo { i = i + 1; i } }.tree)
res10: String =
$line23$read.foo.apply({
  $line24$read.`i_=`($line24$read.i.+(1));
  $read.INSTANCE.Apply.fromLazyVal($line24$read.i)
})

隐式转换只应用于复杂表达式的最后一部分(即,i),而不是整个表情。所以,i = i + 1在我们将其传递给apply方法。为了做到这一点,我们添加了一个重载方法(超值,超值!)Foo.apply任何类型的,但不是Apply...

trait Foo[A] {
  def apply(a: Apply[A]): A = a()
  def apply(s: Symbol): Foo[A] = this
}
scala> var i = 0
i: Int = 0

scala> val foo = new Foo[Int]{}
foo: Foo[Int] = $anon$1@3ff00018

scala> showCode(reify { foo { i = i + 1; i } }.tree)
res11: String =
$line28$read.foo.apply($read.INSTANCE.Apply.fromLazyVal({
  $line27$read.`i_=`($line27$read.i.+(1));
  $line27$read.i
}))

正如我们所看到的,整个表情i = i + 1; i按预期在隐式转换下完成。

提问于
用户回答回答于

类型推断和预期类型

首先,我们必须知道Scala中的类型推断是双向的.。大多数推理都是自下而上的(给出)a: Intb: Int、推断a + b: Int),但有些东西是自上而下的。例如,推断lambda的参数类型是自上而下的:

def foo(f: Int => Int): Int = f(42)
foo(x => x + 1)

在第二行中,在解析后foo成为def foo(f: Int => Int): Int,这种类型的推理者可以看出x必须是类型Int。它这样做以前把羊羔自己打出来。它将类型信息从函数应用程序传播到lambda,这是一个参数。

自上而下的推理基本上依赖于预期类型.。当键入程序AST的一个节点时,打字机不会空手启动.。它从“以上”(在本例中是函数应用程序节点)接收预期的类型。在打字时x => x + 1在上面的示例中,预期的类型是Int => Int,因为我们知道foo。这使得类型推断成为推论。Int对于参数x,这反过来又允许打字机进行检查。x + 1.

预期类型在某些构造中传播,例如块({})的分支ifS和match埃斯。因此,您也可以调用foo带着

foo({
  val y = 1
  x => x + y
})

打字机还能推断出x: Int。那是因为,当输入代码块时{ ... },预期类型Int => Int传递给最后一个表达式的类型,即,x => x + y.

隐式转换和期望类型

现在,我们必须在混合中引入隐式转换。当键入某个节点时,生成一个类型的值。T,但是该节点的预期类型是U何地T <: U为false,打字机将查找隐式。T => U(我可能在这里简化了一些事情,但要点仍然是真的)。这就是为什么您的第一个示例不起作用的原因。让我们仔细研究一下:

trait Foo[A] {
  def apply(a: Apply[A]): A = a()
}

val foo = new Foo[Int] {}
foo({
  i = i + 1
  i
})

foo.apply,参数(即块)的预期类型是Apply[Int](A已经被实例化为Int)。我们可以这样“写”这个打字机的“状态”:

{
  i = i + 1
  i
}: Apply[Int]

这种预期类型是传下来到块的最后一个表达式,该表达式提供:

{
  i = i + 1
  (i: Apply[Int])
}

在这一点上,因为i: Int所期望的类型是Apply[Int],打字机查找隐式转换:

{
  i = i + 1
  fromLazyVal[Int](i)
}

这只会导致i被懒惰。

超载和预期类型

好了,是时候把过载扔进去了!当打字机看到重载方法的应用程序时,它在确定预期类型时会遇到更多的困难。通过下面的示例我们可以看到这一点:

object Foo {
  def apply(f: Int => Int): Int = f(42)
  def apply(f: String => String): String = f("hello")
}

Foo(x => x + 1)

给予:

error: missing parameter type
              Foo(x => x + 1)
                  ^

在这种情况下,打字机找不到预期类型会导致参数类型不被推断。

如果我们对你的问题采取“解决办法”,我们就会有不同的后果:

trait Foo[A] {
  def apply(a: Apply[A]): A = a()
  def apply(s: Symbol): Foo[A] = this
}

val foo = new Foo[Int] {}
foo({
  i = i + 1
  i
})

现在,当打字机对块进行排版时,无期望类型一起工作。因此,它将在没有表达式的情况下对最后一个表达式进行类型检查,并最终将整个块作为一个Int:

{
  i = i + 1
  i
}: Int

直到现在,使用一个已经被键入的参数,它才试图解决重载。由于没有任何重载是直接符合的,所以它试图应用一个隐式转换。Int任一种Apply[Int]Symbol...。它发现fromLazyVal[Int],它适用于整个争论...。它不再把它推到街区内,它给出:

fromLazyVal({
  i = i + 1
  i
}): Apply[Int]

在这种情况下,整个区块是懒惰的。

扫码关注云+社区