-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Separate exported instance type and internal dispatch/combine type #107
Comments
I still know zero about internals of magnolia, but maybe a simpler approach would be to just ask end-user for a mapping: Hope that helps :) |
I think this would be really useful and can be generalized - typeclass derivation doesn't have to be regular in many ways:
Another simple example would be a CSV parser. It can handle only products of primitives by definition. It's not clear how to implement this though. |
Just wanted to say that the first point would be helpful for a similar library but for reactivemongo bson encoder/decoder (https://github.com/rethab/magnolia-bson) because Reactivemongo uses a |
semiauto is actually working in 0.9.0 😄 It was doing full auto in 0.8.0, oops! A |
@fommil yeah, it was not a semiauto in a usual sense, but nor it was a proper full auto to be precise :) it was something in the middle. |
this semiauto is also somewhere in the middle... if you derive the top level ADT eg. @deriving(Show, Equal, Arbitrary)
sealed abstract class JsValue { def widen: JsValue = this }
final case object JsNull extends JsValue
final case class JsObject(fields: IList[(String, JsValue)]) extends JsValue
final case class JsArray(elements: IList[JsValue]) extends JsValue
final case class JsBoolean(value: Boolean) extends JsValue
final case class JsString(value: String) extends JsValue
final case class JsDouble(value: Double) extends JsValue
final case class JsInteger(value: Long) extends JsValue it will continue to derive for all the fields in the coproduct, but not for the fields in the products. Which I feel is a very practical, yet safe, trade-off. |
As far as I understand things, nothing in the new release (0.9.1) changed so that having different type classes for the exported and internal types... You see any problems with the suggested solution? |
I first encountered this problem a few months ago and just fell back to shapeless. However recently I got two new use cases for this so it becomes more and more painful to not be able to use magnolia in this way.
All of that fits into the described issue. An alternative solution to the one suggested by @vpavkin is to make output type of gen dynamic, so its not trait QueryParamPayload[T] {
def decode(map: Map[String, Chain[String]]): T
}
object QueryParamPayload {
type Typeclass[T] = QueryParamDecoder[T]
def combine[T](ctx: CaseClass[QueryParamDecoder, T]): QueryParamPayload[T] = ???
def gen[T]: QueryParamPayload[T] = macro Magnolia.gen[T]
} So we derive from |
…to mode, when deriving collection a instance * Also forces all magnolia derivations to run in `blackbox.Context` (after typer) for performance. Seems magnolia doesn't use whitebox capabilities anyway * Fixes softwaremill/magnolia#107 for pureconfig-magnolia * Ported from `izumi` - 7mind/izumi#915
Seems like this actually works in a direct way, just wrap the output of object exportedReader {
type Typeclass[A] = ConfigReader[A]
def combine[A: ProductHint](ctx: CaseClass[ConfigReader, A]): ConfigReader[A] = MagnoliaConfigReader.combine(ctx)
def dispatch[A: CoproductHint](ctx: SealedTrait[ConfigReader, A]): ConfigReader[A] = MagnoliaConfigReader.dispatch(ctx)
implicit def exportReader[A]: Exported[ConfigReader[A]] = macro ExportedMagnolia.materializeImpl[A]
// Wrap the output of Magnolia in an Exported to force it to a lower priority.
// This seems to work, despite magnolia hardcode checks for `macroApplication` symbol
// and relying on getting a diverging implicit expansion error for auto-mode.
// Thankfully at least it doesn't check the output type of its `macroApplication`
object ExportedMagnolia {
def materializeImpl[A](c: whitebox.Context)(implicit t: c.WeakTypeTag[A]): c.Expr[Exported[ConfigReader[A]]] = {
val magnoliaTree = c.Expr[ConfigReader[A]](Magnolia.gen[A](c))
c.universe.reify(Exported(magnoliaTree.splice))
}
}
} See: |
You can also stick a implicit def gen[A](implicit lp: shapeless.LowPriority): ConfigReader[A] = macro lpgen[A]
def lpgen[A: c.WeakTypeTag](c: whitebox.Context)(lp: c.Tree): c.Tree = Magnolia.gen[A] |
@neko-kai thanks for this nice trick... |
* Fix pureconfig-magnolia not using existing pureconfig instances in auto mode, when deriving collection a instance * Also forces all magnolia derivations to run in `blackbox.Context` (after typer) for performance. Seems magnolia doesn't use whitebox capabilities anyway * Fixes softwaremill/magnolia#107 for pureconfig-magnolia * Ported from `izumi` - 7mind/izumi#915 * Remove blackboxification, copy test to `generic` * unused import * remove useless diffs
uses trick explained in softwaremill/magnolia#107 (comment)
After #221 it seems like it's possible to return a more precise type from |
…econfig#703) * Fix pureconfig-magnolia not using existing pureconfig instances in auto mode, when deriving collection a instance * Also forces all magnolia derivations to run in `blackbox.Context` (after typer) for performance. Seems magnolia doesn't use whitebox capabilities anyway * Fixes softwaremill/magnolia#107 for pureconfig-magnolia * Ported from `izumi` - 7mind/izumi#915 * Remove blackboxification, copy test to `generic` * unused import * remove useless diffs
This is a more principled version of #89. I found at least two cases when the typeclass intance being derived would have a type different from the typeclass used for
dispatch
andcombine
.1)
ObjectEncoder[A]
/Encoder[A]
In circe, generic derivation provides not just an
Encoder[A]
, but a more specificObjectEncoder[A]
, which means that the serialization result is always going to be a json object. But for all the inner steps of the derivation, a more generalEncoder[A]
typeclass is looked for and used. This makes sense - object internals don't have to be objects themselves.2)
Exported[Decoder[A]]
/Decoder[A]
This is how prioritization of derived instances works in circe.
auto
derives an instance ofExported[Decoder[A]]
(code). And there's a low-priority implicit conversionExported[Decoder[A]] => Decoder[A]
, which allows the default instances to be selected before the derived ones.But of course, under the hood derivation of
Exported[Decoder[A]]
combines and dispatches on justDecoder[A]
(otherwise default instances would not be picked up).Possible Solution
I feel that this is not specific to circe and similar concerns might arise in other contexts.
I understand this would complicate things quite a bit, but ideally for proper circe auto derivation we'd need something like this:
WDYT, how hard is that gonna be?
P.S. Thanks for 0.8.0, semiauto seems to be working perfectly! 👍
The text was updated successfully, but these errors were encountered: