Search code examples
scalageneric-programmingshapeless

writing generic function using method name


I am trying to write the following method:

case class Config2(repoName: String)

def buildAction[A, M, R <: HList]()
                          (implicit
                           gen: Generic.Aux[Config2, R],
                           mod: Modifier.Aux[R, M, A, A, R])
  : (A, Config2) => Config2 = {
    (arg: A, c: Config2) => {
      val rec = mod.apply(gen.to(c), _ => arg)
      gen.from(rec)
    }
  }

When trying to use it with:

buildAction[String, Witness.`'repoName`.T, String :: HList]()

I get an error:

could not find implicit value for parameter gen: shapeless.Generic.Aux[com.advancedtelematic.tuf.cli.Cli.Config2,shapeless.::[String,shapeless.HList]]
[error]   val _ = buildAction[String, Witness.`'repoName`.T, String :: HList]()

am I missing some import here?

Second question is, can I somehow rewrite this signature so I don't don't have to specify all the types? In practive the Config2 type takes a long list of fields so it's not pratical to write this all the time

Update:

I simplified this to the following:

  val CGen = LabelledGeneric[Config]

  def buildAction[A, M]()
                       (implicit mod: Modifier.Aux[CGen.Repr, M, A, A, CGen.Repr])
  : (A, Config) => Config = {
    (arg: A, c: Config) => {
      val rec = mod.apply(CGen.to(c), _ => arg)
      CGen.from(rec)
    }
  }

Which allows me to just write:

buildAction[String, Witness.`'repoName`.T]()

But I still have to specify that Witness. Is the a way I could write buildAction[String]("repoName") and have some method provide the Witness implicitly?

Update: the following works!

  val CGen = LabelledGeneric[Config]

  def buildAction[A](witness: Witness)
                    (implicit mod: Modifier.Aux[CGen.Repr, witness.T, A, A, CGen.Repr]):
  (A, Config) => Config = {
    (arg: A, c: Config) => {
      val rec = mod.apply(CGen.to(c), _ => arg)
      CGen.from(rec)
    }
  }

  buildAction[RepoName]('repoName)

Solution

  • am I missing some import here?

    No, it's probably just that String :: HList must be String :: HNil

    Second question is, can I somehow rewrite this signature so I don't don't have to specify all the types?

    You can use a trick known as kinda-curried type parameters:

    object buildAction {
       class PartiallyApplied[A, M] {
          def apply[R <: HList]()(implicit ...)
       }
       def apply[A, M] = new PartiallyApplied[A, M]
    }
    

    Used as

    buildAction[String, Witness.`'foo`.T]()
    

    Also, since your code mentions field name, you probably want LabelledGeneric in conjunction with ops.record.Updater