Using Context Bounds for types with "Default" implementation

Hi there. I’m in a situation where I have a class with a generic parameter, and I need to restrict the types to those which I can create a “Default” instance for. In Rust, I would just put a T: Default bound, then the caller would need to ensure that their type implements the Default trait. I’ve looked around and I haven’t found much mention of use cases similar to mine. If there’s a standard way to do this that I haven’t found, please let me know :slight_smile: but I’ll describe how I’m attempting to solve this now and what trouble I’m running into.

I recently discovered Context Bounds, which seemed like they should be able to solve my use case. My class setup looks like this:

trait Default[T] {
  implicit def default(): T
}
case class Container[Payload](common: String, specialized: Payload)
object Container {
  def emptyPayload[Payload: Default](common: String): Container[Payload] = {
    Container(common, implicitly[Default[Payload]].default())
  }
}
class MyAbstractClass[ClientPayload: Default](
  protected val merger: (Container[ClientPayload], Container[ClientPayload]) => Container[ClientPayload],
) {
  def doStuff(): Unit = {
    val zero: Container.emptyPayload("some common value")
    fold(zero)(merger)
    // ...
  }
}
class MyConcreteClass extends MyAbstractClass[MyPayload](MyConcreteClass.merger) {
  // ...
}
object MyConcreteClass {
  def merger // ...
  case class MyPayload(/* fields */)
  implicit object MyPayloadDefault extends Default[MyPayload] {
    override implicit def default(): MyPayload = MyPayload(/* default values */)
  }
}

The error I’m getting is

... could not find implicit value for evidence parameter of type com.my.package.Default[com.my.package.MyPayload]

I don’t know whether I have this almost right and the problem is something small (like scoping), or whether my approach is fundamentally flawed in some way. Some of the classes are located in separate files, but I did make sure to import the implicit MyPayloadDefault above the MyConcreteClass definition, like this:

import com.my.package.MyConcreteClass._
class MyConcreteClass extends MyAbstractClass[MyPayload](merger) { /* ... */ }
object MyConcreteClass {
  implicit object MyPayloadDefault extends Default[MyPayload] { /* ... */ }
}

So it seems to me that the MyConcreteClass.MyPayloadDefault should be in scope to satisfy the implicit requirement for MyAbstractClass, but it still seems not to be working.

If anybody has any clues as to what I’m doing wrong, I’d greatly appreciate it!

Hmm. You’re certainly on the right track – this is a pretty orthodox approach.

Not quite sure where the problem is. I put your code into ScalaFiddle, did some relatively mechanical cleanups to fix the obvious stuff, and it now seems to compile. Maybe it may be useful to compare that to what you have? Otherwise, can you point us to which line is causing the error you’re seeing? Right now it’s all guesswork…

1 Like

You haven’t provided an exact example (in scalafiddle or elsewhere) of this failing to compile, so i’m going to have to speculate.

One likely issue is that your typeclass instance is an implicit object rather than an implicit val or implicit def with an explicit type. There’s a scala compiler behavior (i think a LONG time bug) where it won’t do type inference on untyped expressions in the same compilation unit below the point of implicit need when looking for a satisfying implicit value. Given the structure you’ve provided, where you have an untyped implicit, but at the bottom of your example, this could well be the problem. Make it an implicit val, give it an explicit type, and see if that solves the problem.

1 Like

@jducoeur Wow, that’s very interesting. Looking at your Fiddle code, I noticed that you declared your companion object first, and the class second. When I did the same in my code, it seems to have just worked.

The line that had been causing the error was

class MyConcreteClass extends MyAbstractClass[MyPayload](merger) { /* ... */ }
                              ^

@virus_dave, thanks for your reply. When I posted I didn’t realize scalafiddle existed, or I would have put together a working demonstration of the problem. What I have now seems to work, but I’ll definitely keep your suggestion in mind in the future!

What I have now seems to work, but I’ll definitely keep your suggestion in mind in the future

So this is strong evidence that the cause is exactly what i described: An implicitly typed implicit value in the same compilation unit is not used if it hasn’t yet been ascribed a type and it is below the point of need.

I’d suggest that you make the change i described, otherwise you’ve set yourself up for a future debugging landmine when someone rearranges the file contents and it breaks.

Cheers!

1 Like

Ah ok, I’ll do that then. Thanks for the heads up!