the Arithmetic.scala is:
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) {
...
}
}
in another scala file:
import Arithmetic._
class PE[T <: Data](inputType: T, ...)(implicit ev: Arithmetic[T]) extends Module {
import ev._
...
}
my question is:
- Can an abstract class has a companion object?(such as the Arithmetic)
- I know there is some implicit conversion. what makes me confused is that when I use
import Arithmetic._
, what is theev
refer to? the companion object or the abstract class?
CodePudding user response:
First, regarding whether we can define an object Arithmetic
if there is already a trait / class Arithmetic
: yes. There are two independent kinds of names: type-names and value-names. Abstract class declarations and trait declarations such as
abstract class Arithmetic[A] // or
trait Arithmetic[A]
introduce a named type with name Arithmetic
. The companion object declaration
object Arithmetic
introduces a value name Arithmetic
. The type-names and the value-names live in separate realms, and do not collide.
Having a companion object Foobar
for a trait Foobar[X]
is quite common if Foobar[X]
is modeling a typeclass, because the compiler will look for instance definitions preferentially in the object Foobar
.
Second, regarding the various imports:
import Arithmetic._
is just a package import. Confusingly, because you have a classArithmetic.Arithmetic
and object Arithmetic.Arithmetic
, this brings the class and the object into scope.
On the other hand, this here
import ev._
says essentially: "In this block of code, when you need
or *
, go ask the ev: Arithmetic[T]
how
and *
are defined".
How ev
gets instantiated depends on the type T
at the instantiation site where PE[T]
is constructed. If you're constructing
val myUInt: UInt = ...
new PE[UInt](myUInt)
, you're essentially telling the compiler: "Dear compiler, I'm lazy, go search for an instance of Arithmetic[UInt]
in some of the packages that I've imported". The compiler will go and see where it can get an compilerGenerated_ev: Arithmetic[UInt]
, and pass it automatically as the implicit argument:
val myUInt: UInt = ...
//