could not find implicit value for shapeless.ops.record.Selector

431 views Asked by At

I am playing around with shapeless to explore the possibility of extracting the value type in a shapeless labeled record. The motivation is that I can then use type classes and implicits to dispatch the process flow based on the type. However, the following code is not working.

import shapeless._, record._
import shapeless.ops.record.Selector
import shapeless.syntax.RecordOps
import shapeless.syntax.singleton._

object ShapelessRecordTest extends App {

  trait Extractor[K, T] {
    type OUT_K
    def extract: T => OUT_K
  }

  object Extractor {

    type Aux[K, T, OUT_K0] = Extractor[K, T] {
      type OUT_K = OUT_K0
    }

    implicit def apply[T, Repr, OUT](k: Witness)(
      implicit gen: LabelledGeneric.Aux[T, Repr],
      selector: Selector.Aux[Repr, k.T, OUT]
    ): Aux[k.T, T, OUT] = new Extractor[k.T, T] {
      type OUT_K = OUT
      def extract: T => OUT_K = (e: T) => {
        val rep = gen.to(e)
        val rprOp: RecordOps[Repr] = new RecordOps[Repr](rep)
        rprOp(k)
      }
    }
  }

  case class Person(name: String, address: String, age: Int)

  implicit val gen = LabelledGeneric[Person]

  val nameWit: Witness = 'name
  val nameExtractor: Extractor.Aux[nameWit.T, Person, String] = Extractor(nameWit)

  def main(args: Array[String]): Unit = {
    val joe = Person("Joe", "Brighton", 33)
    println(nameExtractor.extract(joe))
  }

}

I am using Scala 2.12.8 and the compiler error is

{
    "resource": "/workspace/connecterra/stream-data-pipeline/flink/src/main/scala/io/connecterra/ShapelessRecordTest.scala",
    "owner": "_generated_diagnostic_collection_name_#0",
    "severity": 8,
    "message": "could not find implicit value for parameter selector: shapeless.ops.record.Selector.Aux[Repr,ShapelessRecordTest.nameWit.T,OUT]",
    "source": "scalac",
    "startLineNumber": 37,
    "startColumn": 74,
    "endLineNumber": 37,
    "endColumn": 75
}

I'd like to understand what I am missing here. Or is it possible at all in shapeless?

1

There are 1 answers

0
Dmytro Mitin On

There's not much sense to make apply implicit if you're going to call it explicitly. It's better not to mix materializing method and implicit method defining instances of type class. RecordOps are not supposed to be created manually (there is import shapeless.record._ for that). Sometimes it's better to call apply method of a type class (e.g. shapeless.ops.record.Selector) directly rather than to rely on extension methods. When you write val nameWit: Witness = 'name you loose type refinement. There is no sense to create implicit val gen = LabelledGeneric[Person]. For Selector, Repr should be <: HList.

Try

import shapeless._
import shapeless.ops.record.Selector

object ShapelessRecordTest {

  trait Extractor[K, T] {
    type OUT_K
    def extract: T => OUT_K
  }

  object Extractor {

    type Aux[K, T, OUT_K0] = Extractor[K, T] {
      type OUT_K = OUT_K0
    }

    def apply[T](k: Witness)(implicit 
      extractor: Extractor[k.T, T]): Aux[k.T, T, extractor.OUT_K] = extractor

    implicit def extractor[T, Repr <: HList, K](implicit
      gen: LabelledGeneric.Aux[T, Repr],
      selector: Selector[Repr, K]
    ): Aux[K, T, selector.Out] = new Extractor[K, T] {
      type OUT_K = selector.Out
      def extract: T => OUT_K = (e: T) => {
        val rep: Repr = gen.to(e)
        selector(rep)
      }
    }
  }

  case class Person(name: String, address: String, age: Int)

  val nameWit = Witness('name)
  val nameExtractor: Extractor.Aux[nameWit.T, Person, String] = Extractor(nameWit)

  def main(args: Array[String]): Unit = {
    val joe = Person("Joe", "Brighton", 33)
    println(nameExtractor.extract(joe)) // Joe
  }
}