From 579cdb1281e7f3e16b13abc591c5e06b1aa32db5 Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 14 Dec 2017 14:20:39 +0100 Subject: Add `CaseClass.rawConstruct` and new `Patcher` example --- examples/shared/src/main/scala/patch.scala | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 examples/shared/src/main/scala/patch.scala (limited to 'examples/shared/src') diff --git a/examples/shared/src/main/scala/patch.scala b/examples/shared/src/main/scala/patch.scala new file mode 100644 index 0000000..668a2e2 --- /dev/null +++ b/examples/shared/src/main/scala/patch.scala @@ -0,0 +1,69 @@ +package magnolia.examples + +import scala.language.experimental.macros +import magnolia._ + +/** + * Type class for copying an instance of some type `T`, + * thereby replacing certain fields with other values. + */ +sealed abstract class Patcher[T] { + + /** + * Returns a copy of `value` whereby all non-null elements of `fieldValues` + * replace the respective fields of `value`. + * For all null elements of `fieldValues` the original value of the + * respective field of `value` is maintained. + * + * If the size of `fieldValues` doesn't exactly correspond to the + * number of fields of `value` an [[IllegalArgumentException]] is thrown. + */ + def patch(value: T, fieldValues: Seq[Any]): T +} + +object Patcher extends LowerPriorityPatcher { + + type Typeclass[T] = Patcher[T] + + def combine[T](ctx: CaseClass[Patcher, T]): Patcher[T] = + new Patcher[T] { + def patch(value: T, fieldValues: Seq[Any]): T = { + if (fieldValues.lengthCompare(ctx.parameters.size) != 0) { + throw new IllegalArgumentException( + s"Cannot patch value `$value`, expected ${ctx.parameters.size} fields but got ${fieldValues.size}") + } + val effectiveFields = ctx.parameters.zip(fieldValues).map { + case (param, x) => if (x.asInstanceOf[AnyRef] ne null) x else param dereference value + } + ctx.rawConstruct(effectiveFields) + } + } + + def dispatch[T](ctx: SealedTrait[Patcher, T]): Patcher[T] = + new Patcher[T] { + def patch(value: T, fieldValues: Seq[Any]): T = + ctx.dispatch(value)(sub ⇒ sub.typeclass.patch(sub cast value, fieldValues)) + } + + implicit def gen[T]: Patcher[T] = macro Magnolia.gen[T] +} + +sealed abstract class LowerPriorityPatcher { + + private[this] val _forSingleValue = + new Patcher[Any] { + def patch(value: Any, fieldValues: Seq[Any]): Any = { + if (fieldValues.lengthCompare(1) != 0) + throw new IllegalArgumentException( + s"Cannot patch single value `$value` with patch sequence of size ${fieldValues.size}") + val head = fieldValues.head + if (head.getClass != value.getClass) + throw new IllegalArgumentException( + s"Illegal patch value type. Expected `${value.getClass}` but got `${head.getClass}`") + head + } + } + + // once https://github.com/propensive/magnolia/issues/58 is fixed this can be marked `implicit` + def forSingleValue[T]: Patcher[T] = _forSingleValue.asInstanceOf[Patcher[T]] +} \ No newline at end of file -- cgit v1.2.3