When doing implicit resolution with type parameters, why does val placement matter?
Asked Answered
V

2

0

In one file, I have:

trait JsonSchema[T] {
  val propertyType: String

  override def toString: String = propertyType
}

object JsonSchema {
  implicit def stringSchema: JsonSchema[String] = new JsonSchema[String] {
    override val propertyType: String = "string"
  }

  implicit def intSchema: JsonSchema[Int] = new JsonSchema[Int] {
    override val propertyType: String = "integer"
  }

  implicit def booleanSchema: JsonSchema[Boolean] = new JsonSchema[Boolean] {
    override val propertyType: String = "boolean"
  }
}

In my main file:

case class MetaHolder[T](v: T)(implicit val meta: JsonSchema[T])

object JsonSchemaExample extends App {
  println(MetaHolder(3).meta.toString)
  println(MetaHolder("wow").meta.toString)
}

That works hunky-dory. Now suppose I do this instead:

case class MetaHolder[T](v: T) {
  val meta: JsonSchema[T] = implicitly[JsonSchema[T]]
}

It no longer compiles. Why?

My goal is to modify the anonymous Endpoint classes in the scala Finch library by adding a val meta to everything. I've been able to do this without any fancy-business so far, but now I want to do some fancy implicit resolution with shapeless to provide a JsonSchema definition for arbitrary case classes. My question is how to do this while maintaining backward compatibility. As in: provide the jsonschema meta feature for people who want to opt in, don't change the compilation burden for anyone who does not want to use meta,

If instead I go the first route, with an added implicit parameter, wouldn't that require a special import to be added by everyone? Or am I missing something and would backward compatibility still be maintained?

Void answered 18/12, 2019 at 11:38 Comment(1)
It is recommended to first understand how implicits work. See where does Scala looks for implicits it should be clear why you redactor didn't make sense. Also, I do not understand what do you mean with users requiring an special import. - Finally, your implicits definitions at the beginning should have been vals instead of defs.Rebuttal
L
4

There is big difference between implicit x: X among parameters and implicitly[X] inside body.

When you say implicitly[X] this means "check now whether in the current scope there is an implicit X".

When you say def foo(...)(implicit x: X) = ... this means "check later when foo is called that in the scope of the call site there will be an implicit X (and for now inside foo just assume without checking that there is)".

class Foo(...)(implicit x: X) is similar to the latter, "check when constructor is called that there will be an implicit X".

Regarding whether users have to import or not. If you put implicits for type X to companion object of X then they will be found automatically (implicits for type X[Y] should be put to companion object of either X or Y). If you put them somewhere else then they have to be imported to the current scope.

Liquidity answered 18/12, 2019 at 20:3 Comment(0)
B
0

In order for implicitly[JsonSchema[T]] to compile, there must be a JsonSchema[T] in the implicit scope, which means that there must be a JsonSchema[T] (or something implicitly convertible to a JsonSchema[T]) passed through as an implicit argument, as you had with:

case class MetaHolder[T](v: T)(implicit val meta: JsonSchema[T])
Brucite answered 18/12, 2019 at 16:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.