不在Int上执行隐式转换

时间:2018-03-14 16:46:16

标签: scala typeclass implicit

在Scala中,我想为基本类型生成一些别名,然后通过类型类实现转换。这对我有用,也是理解类型类的机会。代码如下:

type Index = Int
val Index = Int

type Integer = Int
val Integer = Int

type Real = Double
val Real = Double // to have companion object of Double also be the companion object of Real

trait Convertible[A] {
  def toIndex(a: A): Index
  def toInteger(a: A): Integer
  def toReal(a: A): Real
}

implicit val ConvertibleIndex: Convertible[Index] = new Convertible[Index] {
  def toIndex(i: Index) = i
  def toInteger(i: Index) = i
  def toReal(i: Index) = i.toDouble
}

implicit val ConvertibleInteger: Convertible[Integer] = new Convertible[Integer] {
  def toIndex(i: Integer) = i
  def toInteger(i: Integer) = i
  def toReal(i: Integer) = i.toDouble
}

implicit val ConvertibleReal: Convertible[Real] = new Convertible[Real] {
  def toIndex(r: Real) = r.toInt
  def toInteger(r: Real) = r.toInt
  def toReal(r: Real) = r
}

implicit val ConvertibleString: Convertible[String] = new Convertible[String] {
  def toIndex(s: String) = s.toInt
  def toInteger(s: String) = s.toInt
  def toReal(s: String) = s.toDouble
}

implicit class ConvertibleSyntax[A](a: A)(implicit val c: Convertible[A]) {
  def toIndex = c.toIndex(a)
  def toInteger = c.toInteger(a)
  def toReal = c.toReal(a)
}

现在考虑一下:

val a = 3.toReal
val b = 3.0.toReal
val c = "3".toReal

a的语句无法编译,编译错误为method toReal is not a member of Int。但是,对于bc语句,隐式转换为ConvertibleSyntax已正确完成。

为什么隐式转换不适用于Int,但正在处理DoubleString

2 个答案:

答案 0 :(得分:4)

因为您为IndexInteger定义了含糊不清的含义(Int)。

编译器应该选择哪一个?

答案 1 :(得分:1)

我认为您可能会对 Scala 隐式转化的方式感到有些困惑。 (一个常见的错误,因为implicit有点过度使用。)

我认为你想要的,首先是隐式转换函数 - 甚至是隐式类。以下是使用后者的方法:

注意:IntIndexInteger的处理方式相同,RealDouble也是如此,这有点令人困惑,所以我已经减掉了到一些可行的东西。此外,Convertible不需要是泛型,因为其转换函数不需要参数。最后,您的类型不应同时包含typeval声明。

type Index = Int

type Integer = Int

type Real = Double

trait Convertible {
  def toIndex: Index
  def toInteger: Integer
  def toReal: Real
}

// Implicit classes cannot be defined in top-level scope, so they belong to an object.
object Implicits {

   implicit class ConvertibleInt(i: Int)
   extends Convertible {
     override def toIndex = i
     override def toInteger = i
     override def toReal = i.toDouble
   }

   implicit class ConvertibleDouble(d: Double)
   extends Convertible {
     override def toIndex = d.toInt
     override def toInteger = d.toInt
     override def toReal = d
  }

   implicit class ConvertibleString(s: String)
   extends Convertible {
     override def toIndex = s.toInt
     override def toInteger = s.toInt
     override def toReal = s.toDouble
  }
}

现在试试这个:

import Implicits._
val a = 3.toReal
val b = 3.0.toReal
val c = "3".toReal

这里发生了什么?好吧,implicit class声明定义了装饰带有附加函数的唯一构造函数参数的类。如果编译器发现您正在尝试在没有该方法的类型上调用方法,那么它将查看是否存在范围内的隐式转换类型。如果是,则使用它并调用该函数;如果没有,则会出现编译错误。 (import语句用于将类带入当前范围。)

因此,例如,当编译器看到"3".toReal时,它首先确定"3"String。由于此类型没有.toReal成员,因此它会尝试查找从String到具有此类成员的类型的转换。它找到ConvertibleString 隐式类,它接受String参数并提供.toReal方法。好极了!因此,编译器通过将"3"传递给ConvertibleString的构造函数来创建此类的实例,然后在结果上调用.toReal

另一方面,当implicit与值一起使用时,它告诉编译器该值是任何未提供的相同类型的隐式参数的默认值。 永远不要使用implicit具有原始或通用的图书馆类型!

例如:

final case class Example(i: Int)

// Default.
implicit val nameCanBeAnythingAtAll = Example(5)

// Function with implicit argument.
def someFunc(implicit x: Example): Unit = println(s"Value is $x")

现在,如果你写这样的东西:

someFunc

输出为Value is Example(5)

implicit值和参数是一个高级主题,我不会担心它们现在是如何使用的。