Cleaning up signatures with long implicit parameter lists

Is there an elegant solution to clean up the implicit parameter lists somehow, making the signatures more concise? I have code like this:

import shapeless._
import shapeless.HList._
import shapeless.ops.hlist._
import shapeless.poly._

trait T[I, O] extends (I => O)

trait Validator[P]

object Validator{
  def apply[P] = new Validator[P]{}
}

object valid extends Poly1 {
  implicit def caseFunction[In, Out] = at[T[In, Out]](f => Validator[In])
}

object isValid extends Poly2 {
  implicit def caseFolder[Last, New] = at[Validator[Last], T[Last, New]]{(v, n) => Validator[New]}
}

object mkTask extends Poly1 {
  implicit def caseT[In, Out] = at[T[In, Out]](x => x)
  implicit def caseFunction[In, Out] = at[In => Out](f => T[In, Out](f))
}

object Pipeline {

  def apply[H <: HList, Head, Res, MapRes <: HList](steps: H)
       (implicit
        mapper: Mapper.Aux[mkTask.type,H, MapRes],
        isCons: IsHCons.Aux[MapRes, Head, _],
        cse: Case.Aux[valid.type, Head :: HNil, Res],
        folder: LeftFolder[MapRes, Res, isValid.type]
         ): MapRes = {
    val wrapped = (steps map mkTask)
    wrapped.foldLeft(valid(wrapped.head))(isValid)
    wrapped
  }
}

// just for sugar
def T[I, O](f: I => O) = new T[I, O] {
  override def apply(v1: I): O = f(v1)
}

Pipeline(T((x:Int) => "a") :: T((x:String) => 5) :: HNil) // compiles OK
Pipeline(((x:Int) => "a") :: ((x:String) => 5) :: HNil) // compiles OK

// Pipeline("abc" :: "5" :: HNil) // doesn't compile
// can we show an error like "Parameters are not of shape ( _ => _ ) or T[_,_]"?

//  Pipeline(T((x: Int) => "a") :: T((x: Long) => 4) :: HNil) // doesn't compile
// can we show an error like "Sequentiality constraint failed"?

      

And I also want to add a couple of implicit parameters required for the library functionality (to the Pipeline.apply method), but the signature is already huge. I am concerned about the ease of understanding for other developers - is there a "best practice" way to structure these parameters?

Edit: I mean, implicit parameters fall into different categories. In this example: mapper

provide the right types of content isCons

, cse

and folder

ensures consistent input limit, and I would like to add implitsity representing the "feasibility" of business logic. How to group them, is it possible to do it in a readable format?

Edit2: Is it possible to somehow notify the library user which constraint is violated? For example. is either the types in the HList wrong, or the sequence constraint is not being held, or is it missing the correct "business logic" implying?

+3


source to share


2 answers


My suggestion was to use the case implict class that contains this configuration:

case class PipelineArgs(mapper: Mapper.Aux[mkTask.type,H, MapRes] = DEFAULTMAPPER,
  isCons: IsHCons.Aux[MapRes, Head, _] = DEFAULTISCON,
  cse: Case.Aux[valid.type, Head :: HNil, Res] = DEFAULTCSE,
  folder: LeftFolder[MapRes, Res, isValid.type] = DEFAULTFOLDER) {
    require (YOUR TESTING LOGIC, YOUR ERROR MESSAGE)
  } 

object Pipeline {
  def apply[H <: HList, Head, Res, MapRes <: HList](steps: H)
  (implicit args:PipelineArgs)  = {
     val wrapped = (steps map mkTask)
     wrapped.foldLeft(valid(wrapped.head))(isValid)
     wrapped
  }

      



This doesn't help wrt much (but don't worry, I've seen worse), but it does help notify the user it messed up when instantiating args, as you can: a) put defaults on missing arguments in the CClass constructor; b) put a number of "required" proposals.

+3


source


Thanks to @ Diego's answer, I came up with the following code that works pretty well:



import scala.annotation.implicitNotFound
import shapeless._
import shapeless.HList._
import shapeless.ops.hlist._
import shapeless.poly._

trait T[I, O] extends (I => O)

trait Validator[P]

object Validator{
  def apply[P] = new Validator[P]{}
}

object valid extends Poly1 {
  implicit def caseFunction[In, Out] = at[T[In, Out]](f => Validator[In])
}

object isValid extends Poly2 {
  implicit def caseFolder[Last, New] = at[Validator[Last], T[Last, New]]{(v, n) => Validator[New]}
}

object mkTask extends Poly1 {
  implicit def caseT[In, Out] = at[T[In, Out]](x => x)
  implicit def caseFunction[In, Out] = at[In => Out](f => T[In, Out](f))
}

@implicitNotFound("Type constraint violated, elements must be of shape: (_ => _) or  T[_, _]")
case class PipelineTypeConstraint[X, H <: HList, MapRes <: HList]
(
  mapper: Mapper.Aux[X,H, MapRes]
)
implicit def mkPipelineTypeConstraint[X, H <: HList, MapRes <: HList]
  (implicit mapper: Mapper.Aux[X,H, MapRes]) = PipelineTypeConstraint(mapper)

@implicitNotFound("Sequentiality violated, elements must follow: _[A, B] :: _[B, C] :: _[C, D] :: ... :: HNil")
case class PipelineSequentialityConstraint[Head, CRes, MapRes<: HList, ValidT, IsValidT]
(
  isCons: IsHCons.Aux[MapRes, Head, _ <: HList],
  cse: Case.Aux[ValidT, Head :: HNil, CRes],
  folder: LeftFolder[MapRes, CRes, IsValidT]
)
implicit def mkPipelineSequentialityConstraint[Head, CRes, MapRes <: HList, ValidT, IsValidT]
  (implicit isCons: IsHCons.Aux[MapRes, Head, _ <: HList],
    cse: Case.Aux[ValidT, Head :: HNil, CRes],
    folder: LeftFolder[MapRes, CRes, IsValidT]) = PipelineSequentialityConstraint(isCons, cse, folder)

object Pipeline {

  def apply[H <: HList, Head, CRes, MapRes <: HList](steps: H)
       (implicit
       typeConstraint: PipelineTypeConstraint[mkTask.type, H, MapRes],
       sequentialityConstraint: PipelineSequentialityConstraint[Head, CRes, MapRes, valid.type, isValid.type]
         ): MapRes = {
    implicit val mapper = typeConstraint.mapper
    implicit val isCons = sequentialityConstraint.isCons
    implicit val cse = sequentialityConstraint.cse
    implicit val folder = sequentialityConstraint.folder
    val wrapped = (steps map mkTask)
    wrapped.foldLeft(valid(wrapped.head))(isValid)
    wrapped
  }
}

// just for sugar
def T[I, O](f: I => O) = new T[I, O] {
  override def apply(v1: I): O = f(v1)
}

Pipeline(T((x:Int) => "a") :: T((x:String) => 5) :: HNil) // compiles OK
Pipeline(((x:Int) => "a") :: ((x:String) => 5) :: HNil) // compiles OK

Pipeline(5 :: "abc" :: HNil)
// error = "Type constraint violated, elements must be of shape: (_ => _) or T[_, _]

Pipeline(T((x: Int) => "a") :: T((x: Long) => 4) :: HNil)
// error = "Sequentiality violated, elements must follow: (_[A, B] :: _[B, C] :: _[C, D] :: ... :: HNil"

      

0


source







All Articles