scala – 磁铁模式和超载方法
Scala如何解决非重载和重载方法的“磁图模式”的隐式转换。
假设有一个特征应用(“磁铁图案”的变体))如下实现。 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的一个实例,所以我们可以传递任意类型A的任何值,因为从A =>的隐式转换应用[A]。 trait Foo[A] { def apply(a: Apply[A]): A = a() } 我们可以使用REPL和this workaround to de-sugar Scala code确保它的运行正常。 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") ) 这很有用,但是假设我们将一个复杂的表达式(with;)传递给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在我们把它传递给一个应用方法的时刻被严格评估,这不是我们一直期待的。 好(或坏)消息。我们可以使scalac在隐式转换中使用整个表达式。所以i = i 1将按预期的方式进行懒惰评估。我们(surprize,surprize!)我们添加一个重载方法Foo.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;我按预期在隐含的转换下做了。 所以我的问题是为什么呢?为什么应用隐式转换的范围取决于类中是否存在重载方法的事实。 解决方法
现在,这是一个棘手的事情。而且实际上真的很棒,我不知道“懒惰隐含不包括全部块”的“解决方法”问题。感谢那!
发生什么与预期类型相关,以及它们如何影响类型推断工作,隐式转换和重载。 类型推断和预期类型 首先,我们必须知道Scala中的类型推断是双向的。大多数推论是从下到上(给定一个:Int和b:Int,推断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本身之前这样做。它将类型信息从函数应用程序传播到lambda,这是一个参数。 自上而下的推论基本上依赖于预期类型的??概念。当类型检查程序的AST节点时,类型检查器不会空手动。它从“上面”(在这种情况下是功能应用程序节点)接收到一个预期类型。当类型检查lambda x =>在上述示例中,x 1,期望类型为Int => Int,因为我们知道foo有什么参数类型。这推动了类型推断,推断参数x的Int,这反过来允许类型检查x 1。 预期类型向下传播某些构造,例如块({})以及ifs和match的分支。因此,你也可以用foo来调用foo foo({ val y = 1 x => x + y }) 而typechecker仍然能够推断出x:Int。这是因为,当类型检查块{…}时,预期类型Int => Int被传递到对最后一个表达式的类型检验,即x => x y。 隐式转换和预期类型 现在,我们必须在组合中引入隐式转换。当类型检查节点产生类型T的值时,该节点的预期类型为U,其中T U(我可能在这里简化了一些事情,但是要点仍然是真的)。这就是为什么你的第一个例子不起作用。让我们仔细看一下: 为假,类型检查器查找隐式t> 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)。我们可以这样写“这个typechecker”状态“ { i = i + 1 i }: Apply[Int] 这个预期类型被传递给块的最后一个表达式,它给出: { i = i + 1 (i: Apply[Int]) } 在这一点上,由于i:Int和期望类型是Apply [Int],所以typechecker会发现隐式转换: { i = i + 1 fromLazyVal[Int](i) } 这只会引起我的迷茫。 重载和预期类型 好吧,时间在那里抛出超载!当typechecker看到一个应用程序的重载方法,它有更多的麻烦决定一个预期的类型。我们可以看到,通过以下示例: 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] 在这种情况下,整个程序段被放宽了。 最后解释。总而言之,主要的区别是当对块进行类型检查时,存在与不存在预期类型。使用预期的类型,隐式转换被尽可能多地推下来。没有预期类型,隐式转换在整个参数即整个块上应用于后验。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |