Scala扩展方法 vs 伴生对象

huangapple go评论128阅读模式
英文:

Scala extension method vs companion object

问题

Extension method(扩展方法):扩展方法允许你在类型定义后为该类型添加方法,也就是说,它允许你为封闭类添加新方法。

Companion object(伴生对象):伴生类或对象可以访问其伴生对象的私有成员。使用伴生对象来存放不特定于伴生类实例的方法和值。

请告诉我要翻译的其他内容。

英文:

As the title says, what exactly is the difference between an extension method and a companion object? Is either available in both Scala 2 and Scala 3? And what are the exact differences between use cases and functional differences?

Definitions by the scala docs:

Extension method:
Extension methods let you add methods to a type after the type is defined, i.e., they let you add new methods to closed classes.

class Circle(x: Double, y: Double, radius: Double)

extension (c: Circle)
  def circumference: Double = c.radius * math.Pi * 2

Companion object:
A companion class or object can access the private members of its companion. Use a companion object for methods and values which aren’t specific to instances of the companion class.

class Circle(radius: Double):
  def area: Double = calculateArea(radius)

object Circle:
  private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0)

答案1

得分: 1

以下是翻译好的部分:

"Actually there's not much in common between extension method and companion object. Extension method is a method and companion object is an object. It's a little weird to compare a method with an object."

"Functionality is quite different. Companion object doesn't let you add methods to a type (after the type is defined, without modification of the type). Extension method isn't for class-level ('static', not instance-level) methods and values and isn't for accessing private members."

"Both of them exist in Scala 2 and Scala 3."

"The code"

"// Scala 3
extension (c: Circle)
def circumference: Double = c.radius * math.Pi * 2"

"is similar to"

"// Scala 2
implicit class CircleOps(c: Circle) {
def circumference: Double = c.radius * math.Pi * 2
}"

"The code for companion object is the same in Scala 2 and Scala 3."

"Maybe you're actually asking where to place a new method."

  • If a method is an instance-level method you should

    • put it to the class/trait if you can modify it, or
    • make the method an extension method if you can't.
  • If a method is a class-level ('static') method you

    • should put it to the companion object if you can modify it, or
    • can make the method an extension method to companion object if you can't.
  • You can always consider some third service/helper/manager class as an alternative if you don't need to access private members."

"Sometimes extension methods are used to avoid issues with variance. For example for covariant 'class MyClass[+T]' we can't define a method accepting 'T'"

  def foo(t: T): Unit = () // doesn't compile: covariant type T occurs in contravariant position in type T of parameter t

We have to make the method generic

  def foo[S >: T](t: S): Unit = ()

Alternatively we could define an extension method


extension [T] (mt: MyClass[T])
  def foo(t: T): Unit = ()

For example https://github.com/milessabin/shapeless/blob/v2.3.10/core/src/main/scala/shapeless/syntax/hlists.scala#L25-L26

These methods are implemented here and pimped onto the minimal HList types to avoid issues that would otherwise be
caused by the covariance of ::[H, T].

英文:

Actually there's not much in common between extension method and companion object. Extension method is a method and companion object is an object. It's a little weird to compare a method with an object.

Functionality is quite different. Companion object doesn't let you add methods to a type (after the type is defined, without modification of the type). Extension method isn't for class-level ("static", not instance-level) methods and values and isn't for accessing private members.

Both of them exist in Scala 2 and Scala 3.

The code

// Scala 3
extension (c: Circle)
  def circumference: Double = c.radius * math.Pi * 2

is similar to

// Scala 2
implicit class CircleOps(c: Circle) {
  def circumference: Double = c.radius * math.Pi * 2
}

The code for companion object is the same in Scala 2 and Scala 3.

Maybe you're actually asking where to place a new method.

  • If a method is an instance-level method you should

    • put it to the class/trait if you can modify it, or
    • make the method an extension method if you can't.
  • If a method is a class-level ("static") method you

    • should put it to the companion object if you can modify it, or
    • can make the method an extension method to companion object if you can't.
  • You can always consider some third service/helper/manager class as an alternative if you don't need to access private members.

See also

https://stackoverflow.com/questions/69274679/what-is-the-benefit-of-putting-a-method-inside-a-case-class-compared-to-in-its-c

https://stackoverflow.com/questions/40645267/scala-what-is-the-difference-between-defining-a-method-in-the-class-instead-on-t

https://stackoverflow.com/questions/1053592/what-is-the-difference-between-class-and-instance-methods

Sometimes extension methods are used to avoid issues with variance. For example for covariant class MyClass[+T] we can't define a method accepting T

class MyClass[+T]:
  def foo(t: T): Unit = () // doesn't compile: covariant type T occurs in contravariant position in type T of parameter t

We have to make the method generic

class MyClass[+T]:
  def foo[S >: T](t: S): Unit = ()

Alternatively we could define an extension method

class MyClass[+T]

extension [T] (mt: MyClass[T])
  def foo(t: T): Unit = ()

For example https://github.com/milessabin/shapeless/blob/v2.3.10/core/src/main/scala/shapeless/syntax/hlists.scala#L25-L26

> These methods are implemented here and pimped onto the minimal HList types to avoid issues that would otherwise be
caused by the covariance of ::[H, T].

答案2

得分: 1

这大致是一个偏好问题之间的问题:

  • 使用具有所需方法的辅助类(它不一定是伴生对象,实际上可以是任何类,如果您没有访问原始类,也无法更改伴生对象)
  • 直接在原始类上提供该方法,就像它是由原始类的作者编写的一样

如果您正在维护原始类:不要执行上述任何操作,而是更新原始类(除非因为责任问题它不属于原始类)。

根据情况,其中一个可能比另一个更合适。对于您的问题,没有明确的答案。

英文:

It's roughly a matter of preference between:

  • using a helper class with the desired method (which doesn't have to be the companion object, it can be any class, actually if you don't have access to the original class you can't change the companion object either)
  • having the method available directly on the original class like if it had been written by the author of the original class

If you are maintaining the original class: do none of the above and update the original class instead (unless for matter of responsibilities it does not belong to the original class).

Depending situations, one is a better fit than the other. There's no definitive answer to your question.

答案3

得分: 1

Extension methods allow you to extend classes you may not necessarily have source control of. So I can extend things like java.time.ZonedDateTime (This comes from the JDK and typically cannot be changed). Companion objects must live in the same place as the class definition so you need control of the class's source file to provide one. This is fairly significant difference between the features.

当Scala编译器在查找隐式变量时,除了检查通常的调用函数范围外,还会查找伴生对象。如果一个应用程序有一个JSON[A]类型,并希望有一个JSON[ZonedDateTime],它将在伴生对象中查找ZonedDateTime,以查看是否存在。这是提供伴生对象的一个主要原因。这个特性以及能够定义或重写某些特殊方法,如构造函数或复制方法,使伴生对象成为Scala工具包的有用部分。

有一部分重叠的功能,您可以通过扩展或通过伴生对象(或者我应该说是直接定义在类上)来添加方法,选择哪种解决方案都不会有关紧要。在某些情况下,这可能无关紧要。在其他情况下,功能是全局的还是局部的,可能会影响您选择扩展方法,因为扩展方法可以便宜地在不同范围内创建,而伴生对象对整个应用程序是全局的。我会根据我的解决方案知识选择我认为最合适的方法。

英文:

Extension methods allow you to extend classes you may not necessarily have source control of. So I can extend things like java.time.ZonedDateTime (This comes from the JDK and typically cannot be changed). Companion objects must live in the same place as the class definition so you need control of the class's source file to provide one. This is fairly significant difference between the features.

When the Scala compiler is looking for an implicit variable as well as checking the usual scope of calling functions, the compiler also looks in the companion object. If an app has a JSON[A] type and wants a JSON[ZonedDateTime] it will look inside the companion object for ZonedDateTime to see if one exists. This is a major reason to provide a companion object. This feature along with being able to define or override certain special methods such as constructors or copy methods make companion objects a useful part of the Scala toolkit.

There is a piece of overlapping functionality where you could add a method either via an extension or via a companion object (or defined directly against the class I should say) and it won't matter which solution is chosen. In certain scenarios it may not matter. In other scenarios, whether the functionality is global or locality may influence your choice as extension methods can be created cheaply and differ inside various scopes, where are companion objects are global to the whole app. I would use the my knowledge of the solution to pick what I think is most suitable.

huangapple
  • 本文由 发表于 2023年4月11日 14:40:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75983048.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定