scalashapelessscala-implicits

Select field by type


I am trying to create a type class that allows to select a field for a given type. This is what I have done so far but the compiler is unable to find the Selector.Aux

case class AddressKey(street: String, city: String)

trait AddressKeySelector[A] {
  def addressKey(v: A): AddressKey
  def addressKeyColumnName: String
}

object AddressKeySelector {

  implicit def typeAddressKeySelector[A, Repr <: HList, K](
      implicit gen: Generic.Aux[A, Repr],
      reprSelector: Selector.Aux[Repr, K, AddressKey]): AddressKeySelector[A] =
    new AddressKeySelector[A] {
      override def addressKey(v: A): AddressKey = reprSelector(gen.to(v))

      override def addressKeyColumnName: String = ???
    }
}

It will be used like this

case class MyType(addressKeyField: AddressKey, otherField: String)
val data = MyType(AddressKey("addr", "city"), "other")
val addrKey = AddressKeySelector[MyType].addressKey(data) 
// addrKey: AddressKey = AddressKey(addr,city) 
val addrField = AddressKeySelector[MyType].addressKeyColumnName(data)
// addrField: String = addressKeyField

This should just work when there is one and only one field with type AddressKey. Any idea on how to implement it?


Solution

  • ops.hlist.Selector doesn't have an Aux, so I'm assuming you're using that one. Seeing as you want to extract the field name you probably want to use LabelledGeneric and ops.record.Selector instead. But then Selector.Aux[Repr, K, AddressKey] would still not work because Selector can only search by "key" (K) and not by "value" (AddressKey) while you are searching for the key by the value.

    Ideally I think you would implement it like this:

    import shapeless._, ops.record._
    
    trait AddressKeySelector[A] {
      def addressKey(v: A): AddressKey
      def addressKeyColumnName: String
    }
    
    object AddressKeySelector {
      def apply[A](implicit sel: AddressKeySelector[A]): sel.type = sel
    
      implicit def typeAddressKeySelector[A, Repr <: HList, K <: Symbol, Swapped <: HList](
          implicit 
          gen: LabelledGeneric.Aux[A, Repr],
          swap: SwapRecord.Aux[Repr, Swapped],
          swappedSelector: Selector.Aux[Swapped, AddressKey, K],
          reprSelector: Selector.Aux[Repr, K, AddressKey]
      ): AddressKeySelector[A] =
        new AddressKeySelector[A] {
          override def addressKey(v: A): AddressKey = reprSelector(gen.to(v))
    
          override def addressKeyColumnName: String = swappedSelector(swap()).name
        }
    }
    

    But for some reason that doesn't work.

    So I think your best bet is implementing the search by value yourself. It's relatively straightforward. You can take inspiration from the ops.hlist.Selector. Keeping in mind that every entry in the Hlist that is emitted by LabelledGeneric is encoded as labelled.FieldType[Key,Value] and that you can get the runtime value of Key (or in other words: the field name) with Witness.Aux[Key].