【问题标题】:Clean up signatures with long implicit parameter lists使用长的隐式参数列表清理签名
【发布时间】:2015-01-04 01:24:01
【问题描述】:

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

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 方法),但签名已经很大了。我担心其他开发人员易于理解 - 是否有“最佳实践”方式来构建这些参数?

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

Edit2:是否有可能以某种方式提醒图书馆的用户,关于违反了哪个约束?例如。要么 HList 中的类型错误,要么没有遵守顺序约束,或者他缺乏适当的“业务逻辑”隐含?

【问题讨论】:

  • 如何将它们全部包装在一个 functionXParameters 案例类中?你可能也可以在那里使用隐式:)
  • @Diego 我已经编辑了这个问题 - 你能尝试更详细地解释你的想法吗?

标签: scala scalaz shapeless implicits


【解决方案1】:

我的建议是使用包含该配置的隐式案例类:

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) 放一些“要求”子句。

【讨论】:

  • 谢谢,看起来不错,我会试一试,告诉你效果如何!
【解决方案2】:

感谢@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"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-29
    • 2018-05-22
    • 1970-01-01
    • 1970-01-01
    • 2020-07-03
    • 2018-10-28
    相关资源
    最近更新 更多