0

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)
GSerum_
  • 57
  • 8
  • 3
    An easy to understand use-case for an extension method would be to extend someone elses code. Much like your example, except you're using a library of shapes instead of declaring them yourself. You can write an extension method to add your area calculation functionality instead of writing a helper class. This helps keep the code consistent. – dan-frank Apr 11 '23 at 09:27

3 Answers3

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.

See also

What is the benefit of putting a method inside a case class compared to in its companion object?

Scala what is the difference between defining a method in the class instead on the companion object

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].

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 1
    Also, objects are values, while methods are not. Values have methods and methods are attached to values. - Extension methods are not even methods, but sugar syntax to call functions as if they where methods on a value. - Yeah, it seems OP is confused by the fact that both allow defining methods that consume a class... but by that logic, any method in any class can consume any other class, and even more the way they both are called is completely different. - I wonder @GSerum_ why you thought they both were related? – Luis Miguel Mejía Suárez Apr 11 '23 at 13:21
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.

Gaël J
  • 11,274
  • 4
  • 17
  • 32
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.

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.

Philluminati
  • 2,649
  • 2
  • 25
  • 32