magnus
magnus

Reputation: 87

Get a list of field values of the same type in a case class

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

Answers (2)

stsatlantis
stsatlantis

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

Jeremy
Jeremy

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

Related Questions