scala 什么是'import ev._'隐含证据,什么时候应该使用它?

qgelzfjb  于 2022-11-23  发布在  Scala
关注(0)|答案(1)|浏览(153)

算术scala是:

package Arithmetic
// The Arithmetic typeclass which implements various arithmetic operations on custom datatypes
abstract class Arithmetic[T <: Data] {
  implicit def cast(t: T): ArithmeticOps[T]
}

abstract class ArithmeticOps[T <: Data](self: T) {
  def *(t: T): T
  def mac(m1: T, m2: T): T // Returns (m1 * m2 + self)
  def +(t: T): T
  ...
}

object Arithmetic {
  implicit object UIntArithmetic extends Arithmetic[UInt] {
    override implicit def cast(self: UInt) = new ArithmeticOps(self) {
      ...
    }  

  implicit object SIntArithmetic extends Arithmetic[SInt] {
    override implicit def cast(self: SInt) = new ArithmeticOps(self) {
      ...
    }
}

在另一个scala文件中:

import Arithmetic._

class PE[T <: Data](inputType: T, ...)(implicit ev: Arithmetic[T]) extends Module {
  import ev._
  ...
}

我问题是:
1.一个抽象类可以有一个伴随对象吗?(如Arithmetic)
1.我知道有一些隐式转换。让我困惑的是,当我使用import Arithmetic._时,ev指的是什么?伴随对象还是抽象类?

nbnkbykc

nbnkbykc1#

首先,关于如果已经有一个trait/class Arithmetic,我们是否可以定义一个对象Arithmetic:是的。2有两种独立的名字:类型名和值名。抽象类声明和特征声明,如

abstract class Arithmetic[A] // or 
trait Arithmetic[A]

引入一个名为Arithmetic的 * named type *。伴随对象声明

object Arithmetic

引入了一个 * 值名称 * Arithmetic。类型名称和值名称位于不同的领域中,不会发生冲突。
如果Foobar[X]正在建模一个类型类,那么对于trait Foobar[X]有一个伴随对象Foobar是很常见的,因为编译器会优先在object Foobar中寻找示例定义。
第二,关于各种进口:

import Arithmetic._

只是一个包导入。令人困惑的是,因为你有一个类Arithmetic.Arithmetic和一个对象Arithmetic.Arithmetic,这就把类和对象带到了作用域中。
另一方面,这里这个

import ev._

本质上说:* "在此代码块中,当您需要+*时,请询问ev: Arithmetic[T]如何定义+*"*。
如何示例化ev取决于构造PE[T]的示例化站点上的类型T

val myUInt: UInt = ...
new PE[UInt](myUInt)

,您实际上是在告诉编译器:* "亲爱的编译器,我很懒,在我导入的一些包中搜索Arithmetic[UInt]的示例"*.编译器将去看看哪里可以得到compilerGenerated_ev: Arithmetic[UInt],并自动将其作为隐式参数传递:

val myUInt: UInt = ...

// 🤖: found it in `Arithmetic.UIntArithmetic`
val compilerGenerated_ev: Arithmetic[UInt] = Arithmetic.UIntArithmetic
new PE[UInt](myUInt /* 🤷‍♂️ */)(compilerGenerated_ev /* 🤖 */)

然后PE[T]-initializer内部的主体将接管,从compilerGenerated_ev导入*+,并使用它们进行计算。
这里,同样是更新的Scala 3语法的完整示例。它演示了编译器如何根据传递给PE-构造函数的内容插入两个不同的Arithmetic[Int]Arithmetic[Double]示例。

// This is just a package (it's very confusing that
// in your example, the package is also named `Arithmetic`
package arithmetic {

  // This defines what it means that "we can do Arithmetic on carrier set `A`"
  trait Arithmetic[A]:

    // Doing arithmetic on `A` means that we can add and multiply `A`s
    extension (a: A)
      infix def *(other: A): A
      infix def +(other: A): A

  // The companion object of the typeclass `Arithmetic` is one of the first
  // places where the compiler will search for instances of `Arithmetic[A]`
  object Arithmetic {

    // We can do arithmetic with integers
    given Arithmetic[Int] with
      extension (i: Int)
        infix def *(other: Int) = i * other
        infix def +(other: Int) = i + other

    // We can do arithmetic with Doubles
    given Arithmetic[Double] with
      extension (d: Double)
        infix def *(other: Double) = d * other
        infix def +(other: Double) = d + other
   
  }
}

// This import says: 
// "bring `trait Arithmetic[T]` and
// companion `object Arithmetic` into
// scope
import arithmetic._

// This class has the superpower of computing `x * x + x`
// for `x: T`, provided that we can do arithmetic on `T`.
class PE[T](x: T)(using ev: Arithmetic[T]) {
  // This import says:
  // "In the following, when looking for definitions for `+`
  //  and `*`, look inside of the `ev`
  import ev.*

  def foobar: T = x * x + x 
}

@main def example(): Unit = {
  println(PE(42).foobar)    // 1806: Int
  println(PE(0.001).foobar) // 0.001001: Double
}

相关问题