Reputation: 87
I have a class like this, that is giving detailed answer to what part of the overlaying algorithm went wrong if someone is interested and I want to enhance it in the future with many other possible invalidities in data.
case class Validity(grouping: Boolean = true,
matchingValuesValidation: Boolean = true) {
def compute: Boolean =
List(grouping,
matchingValuesValidation
).forall(identity)
def merge(ot: Validity): Validity =
Validity(
grouping && ot.grouping,
matchingValuesValidation && ot.matchingValuesValidation
)
}
I know all the fields will be Boolean, computation won't change. I would like to make methods compute and merge somehow iterate through all fields, so if I enhance it, there is no need to do it 3 times. If I use Map, I can add whatever key -> value pair I want and that is not desirable, I want to keep the structure. At the same time, I would like to enhance the class by simply adding another Boolean parameter to the class. Any ideas are greatly appreciated, thanks in advance
Upvotes: 1
Views: 212
Reputation: 555
Instead of reflection you can use shapeless to do the heavy lifting for you:
import shapeless.ops.hlist.{Mapper, Zip}
import shapeless.{::, Generic, HList, HNil, Poly1}
private sealed trait lowPrioMergerMapper extends Poly1 {
implicit def mapperT[T]: Case.Aux[(T, T), T] = at[(T, T)] { case (v1, _) => v1}
}
private object MergerMapper extends lowPrioMergerMapper {
implicit def mapperBoolean: Case.Aux[(Boolean, Boolean), Boolean] = at[(Boolean, Boolean)] { case (v1, v2) => v1 && v2 }
}
def booleanFieldMerger[T, HL <: HList, ZOut <: HList](a: T, b: T)(
implicit
gen: Generic.Aux[T, HL],
zipWith: Zip.Aux[HL :: HL :: HNil, ZOut],
mapper: Mapper.Aux[MergerMapper.type ,ZOut, HL]
): T = {
val aRepr = gen.to(a)
val bRepr = gen.to(b)
gen.from((aRepr zip bRepr) map MergerMapper)
}
And the usage would be like:
val validity = Validity()
val validity2 = Validity(a = false)
val validity3 = Validity(b = false)
val validity4 = Validity(a = false, b = false)
booleanFieldMerger(validity,validity2) shouldBe validity2
booleanFieldMerger(validity2,validity3) shouldBe validity4
List(validity2,validity3).fold(validity)((a,b) => booleanFieldMerger(a,b)) shouldBe validity4
And for evaluation of the fields you can use:
import shapeless.HList
import shapeless.ops.hlist._
def evaluateBooleanFields[T, HL <: HList](input: T)(
implicit
gen: Generic.Aux[T, HL],
toTrav: ToList[HL,Any]): Boolean = {
gen.to(input).toList.foldLeft(true){
case (acc, e: Boolean) => acc && e
case (acc, _) => acc}
}
And the usage would be the same as above:
evaluateBooleanFields(validity) shouldBe true
evaluateBooleanFields(validity2) shouldBe false
Upvotes: 1
Reputation: 1924
You know what? YOLO.
case class Validity(grouping: Boolean = true, matchingValuesValidation: Boolean = true) {
def values: List[Boolean] = {
import scala.reflect.runtime.universe._
val fields = typeOf[Validity].members.collect { case m: MethodSymbol if m.isCaseAccessor => m }.toList
val mirror = runtimeMirror(this.getClass.getClassLoader)
fields.map(mirror.reflect(this).reflectField(_).get.asInstanceOf[Boolean])
}
def compute: Boolean = values.forall(identity)
def merge(ot: Validity) = Validity.fromValues(this.values.zip(ot.values).map(v => v._1 && v._2).reverse)
}
object Validity {
def fromValues(values: List[Boolean]): Validity = {
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(Validity.getClass.getClassLoader)
val constructorSymbol = typeOf[Validity].decl(termNames.CONSTRUCTOR).asMethod
val classSymbol = mirror.reflectClass(typeOf[Validity].typeSymbol.asClass)
val constructor = classSymbol.reflectConstructor(constructorSymbol)
constructor(values:_*).asInstanceOf[Validity]
}
}
Upvotes: 1