如何在Scala中链接隐式转换?

78

4
这是一篇有关于“pimp/enrich”标签的元问题,因为该标签实在太过糟糕了。请看链接:http://meta.stackexchange.com/q/184514/135887 - Charles
3个回答

108

Scala对于自动转换添加方法有一个限制,即在尝试查找方法时不会应用多个转换。例如:

class A(val n: Int)
class B(val m: Int, val n: Int)
class C(val m: Int, val n: Int, val o: Int) {
  def total = m + n + o
}

// This demonstrates implicit conversion chaining restrictions
object T1 { // to make it easy to test on REPL
  implicit def toA(n: Int): A = new A(n)
  implicit def aToB(a: A): B = new B(a.n, a.n)
  implicit def bToC(b: B): C = new C(b.m, b.n, b.m + b.n)

  // won't work
  println(5.total)
  println(new A(5).total)

  // works
  println(new B(5, 5).total)
  println(new C(5, 5, 10).total)
}

编辑:自Scala 2.11起,视图边界('<%')已不再使用 https://issues.scala-lang.org/browse/SI-7629 (您可以使用类型类)

但是,如果隐式定义本身需要一个隐式参数(视图边界),Scala将会一直寻找额外的隐式值,直到满足需求为止。请参考上一个示例:

// def m[A <% B](m: A) is the same thing as
// def m[A](m: A)(implicit ev: A => B)

object T2 {
  implicit def toA(n: Int): A = new A(n)
  implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n)
  implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)

  // works
  println(5.total)
  println(new A(5).total)
  println(new B(5, 5).total)
  println(new C(5, 5, 10).total)
}

“神奇!”你可能会这么说。但事实并非如此。以下是编译器将如何翻译每个内容:
object T1Translated {
  implicit def toA(n: Int): A = new A(n)
  implicit def aToB(a: A): B = new B(a.n, a.n)
  implicit def bToC(b: B): C = new C(b.m, b.n, b.m + b.n)

  // Scala won't do this
  println(bToC(aToB(toA(5))).total)
  println(bToC(aToB(new A(5))).total)

  // Just this
  println(bToC(new B(5, 5)).total)

  // No implicits required
  println(new C(5, 5, 10).total)
}

object T2Translated {
  implicit def toA(n: Int): A = new A(n)
  implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n)
  implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)

  // Scala does this
  println(bToC(5)(x => aToB(x)(y => toA(y))).total)
  println(bToC(new A(5))(x => aToB(x)(identity)).total)      
  println(bToC(new B(5, 5))(identity).total)

  // no implicits required
  println(new C(5, 5, 10).total)
}

因此,虽然bToC被用作隐式转换,但aToBtoA作为隐式参数传递,而不是作为隐式转换链接。 编辑 相关问题:

5
好的解释。不允许隐式转换链接的声明原因是为了避免复杂性和调试噩梦。那么我想知道,为什么允许对隐式参数进行链接呢? - Adrian
3
好的!我学到了新东西。这应该放在“隐藏功能”页面上。 - Aaron Novstrup
1
请注意,如果你尝试进行高等类类型的链接操作,那么类型推断可能会出问题。例如,我有一个M[A]。我有一个隐式的A=>B和一个隐式的M[]=>N[],其中M和N都是单子的。我想使用这两个转换创建一个N[B]。链式调用需要一个额外的方法调用,第一个调用用于捕获M[_],第二个调用用于捕获A。 - jsuereth
谢谢@DanielC.Sobral!非常好的回答,真的为我澄清了很多事情。 - Tomer Gabel
@DanielC.Sobral 非常好的答案!阅读了几次后,它不再像疯狂的巫术一样了。 - mo-seph
显示剩余5条评论

12

请注意,您也可以使用隐式参数构建圆圈。然而,这些参数会被编译器检测到,如下所示:

class Wrap {
  class A(implicit b : B)
  class B(implicit c : C)
  class C(implicit a : A)

  implicit def c = new C
  implicit def b = new B
  implicit def a = new A
}

给用户的错误提示并不是很清楚,它只是抱怨在所有三个构造站点中都找不到参数的隐式值。这可能会在不太明显的情况下掩盖根本问题。


1

这里有一段代码,可以累加路径。

import scala.language.implicitConversions

// Vertices
case class A(l: List[Char])
case class B(l: List[Char])
case class C(l: List[Char])
case class D(l: List[Char])
case class E(l: List[Char])

// Edges
implicit def ad[A1 <% A](x: A1) = D(x.l :+ 'A')
implicit def bc[B1 <% B](x: B1) = C(x.l :+ 'B')
implicit def ce[C1 <% C](x: C1) = E(x.l :+ 'C')
implicit def ea[E1 <% E](x: E1) = A(x.l :+ 'E')

def pathFrom(end:D) = end

pathFrom(B(Nil))   // res0: D = D(List(B, C, E, A))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接