使用长隐式参数列表清除签名

时间:2014-11-07 09:30:55

标签: scala scalaz shapeless implicits

是否有一个优雅的解决方案以某种方式清理隐式参数列表使签名更简洁? 我有这样的代码:

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"?

我还想添加一些库函数所需的隐式参数(对于Pipeline.apply方法),但签名已经很大了。我担心其他开发人员易于理解 - 是否有一个&#34;最佳实践&#34; 结构的方式这些参数?

编辑:我的意思是隐含的参数属于不同的类别。在此示例中:mapper确保正确的内容类型,isConscsefolder确保对输入的顺序约束,并且我想添加表示&#34; doability&#34;业务逻辑。它们应该如何分组,是否可以以可读格式进行?

Edit2:是否有可能以某种方式提醒图书馆用户,哪个约束被违反?例如。 HList中的类型是错误的,或者没有保持顺序性约束,或者他缺乏正确的业务逻辑&#34; implicits?

2 个答案:

答案 0 :(得分:3)

我的建议是使用包含该配置的implict case类:

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
  }

这对w.r.t没有多大帮助。清晰度(但不要担心,我已经看到更糟),但它有助于通知用户他正在搞乱args实例的创建,因为你可以a)将默认值放在CClass构造函数中的缺失参数中b)put一些“要求”条款。

答案 1 :(得分:0)

感谢@ Diego的回答,我提出了以下代码,它非常适用:

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"