隐式转换 vs 类型类
隐式转换 vs 类型类
在Scala中,我们可以使用至少两种方法来改造现有的或新的类型。假设我们想要表达可以使用Int
进行量化的某个东西。我们可以定义以下特征。
隐式转换
trait Quantifiable{ def quantify: Int }
然后,我们可以使用隐式转换来对字符串和列表进行量化。
implicit def string2quant(s: String) = new Quantifiable{ def quantify = s.size } implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{ val quantify = l.size }
在导入这些后,我们可以在字符串和列表上调用方法quantify
。请注意,可量化的列表存储其长度,因此它避免了在后续调用quantify
时对列表的昂贵遍历。
类型类
另一种方法是定义一个“证明”Quantified[A]
,它说明某个类型A
可以被量化。
trait Quantified[A] { def quantify(a: A): Int }
然后我们在某个地方为String
和List
提供这个类型类的实例。
implicit val stringQuantifiable = new Quantified[String] { def quantify(s: String) = s.size }
如果我们编写一个需要对其参数进行量化的方法,则写为:
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) = as.map(ev.quantify).sum
或使用上下文限定语法:
def sumQuantities[A: Quantified](as: List[A]) = as.map(implicitly[Quantified[A]].quantify).sum
但是何时使用哪种方法?
现在是问题的关键。我如何在这两个概念之间进行选择?
到目前为止,我已经注意到以下事项。
类型类
- 类型类允许使用上下文限定语法
- 使用类型类时,我不会在每次使用时创建一个新的包装对象
- 如果类型类具有多个类型参数,上下文限定语法将无法工作;想象一下,我不仅想用整数量化事物,还想用一些通用类型
T
的值量化。我希望创建一个类型类Quantified[A,T]
隐式转换
- 由于我创建了一个新对象,我可以在其中缓存值或计算更好的表示;但是是否应该避免这样做,因为它可能会发生多次,并且显式转换可能只会调用一次?
我对答案的期望
提供一个(或多个)使用案例,其中区分了这两个概念的差异,并解释为什么我会更喜欢其中之一。即使没有例子,也希望解释这两个概念的本质及其相互关系。
从上面的内容中,我们可以看出出现了Implicit conversion vs. type class这个问题。问题出现的原因是因为在Scala中有两种不同的技术来实现类的扩展和功能增强,即implicit conversion和type class。
Implicit conversion是通过隐式转换来实现的,通过定义一个implicit方法,将一个类转换为另一个类,从而实现对原类的扩展和功能增强。例如,上面的代码中定义了一个trait Foo1,其中有一个foo方法,通过隐式转换可以将一个A类型的对象转换为Int类型。而Foo0则表示已经应用了隐式转换的结果,不再需要传入参数。通过这种方式,可以实现对类的扩展和功能增强。
Type class则是通过声明一个类型类来实现的,通过定义一个trait来表示一个类型类,并在需要增强功能的类中实现该trait,从而实现对类的扩展和功能增强。例如,上面的代码中定义了一个trait Foo1,其中有一个foo方法,表示对类的功能增强。通过这种方式,可以实现对类的扩展和功能增强。
问题的解决方法是根据需要选择使用implicit conversion还是type class来实现对类的扩展和功能增强。如果只需要简单的功能增强,可以使用implicit conversion;如果需要更复杂的功能增强,可以使用type class。根据具体的需求和情况选择合适的方式。
Implicit conversion vs. type class这个问题的出现是因为在Scala中有两种不同的技术来实现类的扩展和功能增强,即implicit conversion和type class。解决方法是根据需要选择合适的方式来实现对类的扩展和功能增强。
隐式转换和类型类是解决不同问题的两种不同的方法。隐式转换允许将一个类型转换为另一个类型,使代码看起来像调用一个方法。而类型类则允许为一个类型添加属性,而不是为类型的实例添加属性。
隐式转换的优点是可以使代码更加简洁和直观。通过隐式转换,可以将一个类型转换为另一个类型,使得代码看起来更像是调用一个方法。例如,可以通过隐式转换将字符串转换为具有新功能的对象,并直接调用该功能。但是,隐式转换可能会导致代码可读性下降,因为它可能会隐藏一些重要的类型转换。
类型类的优点是可以在类型级别上添加属性,并且可以在没有类型实例的情况下访问这些属性。这意味着可以为一个类型定义一组属性,并在需要时进行访问。例如,在上面的示例中,可以为列表类型定义一个默认值属性,并通过类型类访问该属性,而不需要一个具体的列表实例。这是通过定义一个类型类和相应的隐式实例来实现的。
隐式转换和类型类之间的区别在于它们解决的问题不同。隐式转换主要用于将一个类型转换为另一个类型,以提供更好的代码简洁性和可读性。而类型类主要用于在类型级别上添加属性,并在需要时进行访问。
要解决隐式转换和类型类的问题,可以根据具体的需求选择使用哪种方法。如果需要在类型级别上添加属性并在需要时进行访问,则可以使用类型类。如果只需要将一个类型转换为另一个类型,以提供更好的代码简洁性和可读性,则可以使用隐式转换。
总结起来,隐式转换和类型类是解决不同问题的两种不同的方法。隐式转换主要用于将一个类型转换为另一个类型,以提供更好的代码简洁性和可读性。而类型类主要用于在类型级别上添加属性,并在需要时进行访问。根据具体的需求选择使用哪种方法。
隐式转换与类型类之间的区别
在《Scala In Depth》一书中,我不想重复我的材料,但我认为值得注意的是,类型类/类型特征的灵活性更强。
def foo[T: TypeClass](t: T) = ...
具有在其局部环境中搜索默认类型类的能力。然而,我可以通过以下两种方式之一随时覆盖默认行为:
1. 创建/导入一个在作用域中的隐式类型类实例来绕过隐式查找;
2. 直接传递一个类型类。
下面是一个例子:
def myMethod(): Unit = { // 用于覆盖 Int 的默认隐式 implicit object MyIntFoo extends Foo[Int] { ... } foo(5) foo(6) // 这些都使用了我覆盖的类型类 foo(7)(new Foo[Int] { ... }) // 这个需要不同的配置 }
这使得类型类的灵活性更强。另一个优点是,类型类/特征更好地支持隐式查找。
在你的第一个例子中,如果你使用了一个隐式视图,编译器将对以下内容进行隐式查找:
Function1[Int, ?]
这将查找Function1
的伴生对象和Int
的伴生对象。
请注意,Quantifiable
在隐式查找中没有出现。这意味着你必须将隐式视图放在包对象中,或者导入到作用域中。这需要更多的工作来记住发生了什么。
另一方面,类型类是显式的。你可以在方法签名中看到它在寻找什么。你还可以进行隐式查找:
Quantifiable[Int]
这将在Quantifiable
的伴生对象和Int
的伴生对象中查找。这意味着你可以提供默认值,并且新的类型(如MyString
类)可以在其伴生对象中提供默认值,并且会被隐式搜索到。
一般来说,我使用类型类。对于初始的例子来说,它们的灵活性更强。我只在使用Scala包装器和Java库之间的API层时使用隐式转换,即使这样做也可能是“危险”的,如果不小心的话。
关于“无限灵活性”:对于隐式定义,我们也可以导入我们需要的确切转换,或者显式调用需要的转换。