Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant grouping of implicit value classes

I'm writing a set of implicit Scala wrapper classes for an existing Java library (so that I can decorate that library to make it more convenient for Scala developers).

As a trivial example, let's say that the Java library (which I can't modify) has a class such as the following:

public class Value<T> {
    // Etc.
    public void setValue(T newValue) {...}
    public T getValue() {...}
}

Now let's say I want to decorate this class with Scala-style getters and setters. I can do this with the following implicit class:

final implicit class RichValue[T](private val v: Value[T])
extends AnyVal {
  // Etc.
  def value: T = v.getValue
  def value_=(newValue: T): Unit = v.setValue(newValue)
}

The implicit keyword tells the Scala compiler that it can convert instances of Value to be instances of RichValue implicitly (provided that the latter is in scope). So now I can apply methods defined within RichValue to instances of Value. For example:

def increment(v: Value[Int]): Unit = {
  v.value = v.value + 1
}

(Agreed, this isn't very nice code, and is not exactly functional. I'm just trying to demonstrate a simple use case.)

Unfortunately, Scala does not allow implicit classes to be top-level, so they must be defined within a package object, object, class or trait and not just in a package. (I have no idea why this restriction is necessary, but I assume it's for compatibility with implicit conversion functions.)

However, I'm also extending RichValue from AnyVal to make this a value class. If you're not familiar with them, they allow the Scala compiler to make allocation optimizations. Specifically, the compiler does not always need to create instances of RichValue, and can operate directly on the value class's constructor argument.

In other words, there's very little performance overhead from using a Scala implicit value class as a wrapper, which is nice. :-)

However, a major restriction of value classes is that they cannot be defined within a class or a trait; they can only be members of packages, package objects or objects. (This is so that they do not need to maintain a pointer to the outer class instance.)

An implicit value class must honor both sets of constraints, so it can only be defined within a package object or an object.

And therein lies the problem. The library I'm wrapping contains a deep hierarchy of packages with a huge number of classes and interfaces. Ideally, I want to be able to import my wrapper classes with a single import statement, such as:

import mylib.implicits._

to make using them as simple as possible.

The only way I can currently see of achieving this is to put all of my implicit value class definitions inside a single package object (or object) within a single source file:

package mylib
package object implicits {

  implicit final class RichValue[T](private val v: Value[T])
  extends AnyVal {
    // ...
  }

  // Etc. with hundreds of other such classes.
}

However, that's far from ideal, and I would prefer to mirror the package structure of the target library, yet still bring everything into scope via a single import statement.

Is there a straightforward way of achieving this that doesn't sacrifice any of the benefits of this approach?

(For example, I know that if I forego making these wrappers value classes, then I can define them within a number of different traits - one for each component package - and have my root package object extend all of them, bringing everything into scope through a single import, but I don't want to sacrifice performance for convenience.)

like image 644
Mike Allen Avatar asked Feb 12 '18 23:02

Mike Allen


People also ask

What are implicit classes in Scala?

Scala 2.10 introduced a new feature called implicit classes. An implicit class is a class marked with the implicit keyword. This keyword makes the class's primary constructor available for implicit conversions when the class is in scope. Implicit classes were proposed in SIP-13.

How does Scala implicit work?

Implicit parameters are the parameters that are passed to a function with implicit keyword in Scala, which means the values will be taken from the context in which they are called.

What is implicit object in Scala?

The implicit system in Scala allows the compiler to adjust code using a well-defined lookup mechanism. A programmer in Scala can leave out information that the compiler will attempt to infer at compile time. The Scala compiler can infer one of two situations: A method call or constructor with a missing parameter.

Where does Scala look for Implicits?

Unless the call site explicitly provides arguments for those parameters, Scala will look for implicitly available given (or implicit in Scala 2) values of the correct type. If it can find appropriate values, it automatically passes them.


1 Answers

implicit final class RichValue[T](private val v: Value[T]) extends AnyVal

Is essentially a syntax sugar for the following two definitions

import scala.language.implicitConversions // or use a compiler flag

final class RichValue[T](private val v: Value[T]) extends AnyVal
@inline implicit def RichValue[T](v: Value[T]): RichValue[T] = new RichValue(v)

(which, you might see, is why implicit classes have to be inside traits, objects or classes: they also have matching def)

There is nothing that requires those two definitions to live together. You can put them into separate objects:

object wrappedLibValues {
  final class RichValue[T](private val v: Value[T]) extends AnyVal {
    // lots of implementation code here
  }
}

object implicits {
  @inline implicit def RichValue[T](v: Value[T]): wrappedLibValues.RichValue[T] = new wrappedLibValues.RichValue(v)
}

Or into traits:

object wrappedLibValues {
  final class RichValue[T](private val v: Value[T]) extends AnyVal {
    // implementation here
  }

  trait Conversions {
    @inline implicit def RichValue[T](v: Value[T]): RichValue[T] = new RichValue(v)
  }
}

object implicits extends wrappedLibValues.Conversions
like image 77
Oleg Pyzhcov Avatar answered Oct 12 '22 00:10

Oleg Pyzhcov