创建一个不丢失类型的泛化方法
Create a generalize method without losing types
我们有
private def insertUpdateDeleteFormDsList(dsList : List[FormDefinitionDataSourceRequestModel])(implicit formDefId:Int,subject:Subject,session: Session) : (List[(Int,Int)],Seq[FormDefinitionDataSourceRequestModel],Seq[FormDefinitionDataSourceRequestModel]) = {
val incomingIds = dsList.map( ds => (ds.dataSourceId,ds.dsTypeId) )
val existingIds = formDefinitionDatasources.filter(_.tenantId === subject.tenantId).filter(_.formDefId === formDefId).map( ds => (ds.dataSourceId,ds.dataSourceTypeId) ).list
val idsForDeletion = existingIds diff incomingIds
val idsForInsertion = incomingIds diff existingIds
val idsForUpdate = existingIds diff idsForDeletion
val insertList = dsList.flatMap{ t => idsForInsertion collectFirst{ case (dsId,dsType) if(dsId == t.dataSourceId && dsType == t.dsTypeId)=> t} }
val updateList = dsList.flatMap{t=>idsForUpdate collectFirst {case (dsId,dsType) if(dsId == t.dataSourceId && dsType == t.dsTypeId)=> t}}
(idsForDeletion,updateList,insertList)
}
和其他类似的方法
private def insertUpdateDelDataInstances(instances: List[Instance])(implicit subject: Subject, session: Session): (Seq[Instance], Seq[Instance], Seq[Instance]) = {
val incomingIds = instances.map(_.id)
val existingIds = dataSourceInstanceNew.filter(_.tenantId === subject.tenantId).map(_.id).list
val idsForDeletion = existingIds diff incomingIds
val idsForInsertion = incomingIds diff existingIds
val idsForUpdate = existingIds diff idsForDeletion
val deleteList = instances.flatMap{ t => idsForDeletion collectFirst{ case id if(id == t.id)=> t} }
val insertList = instances.flatMap{ t => idsForInsertion collectFirst{ case id if(id == t.id)=> t} }
val updateList = instances.flatMap{t=>idsForUpdate collectFirst {case id if(id === t.id)=> t}}
(deleteList,updateList,insertList)
}
其他地方也有类似的方法出现。每次 List[T]
将作为方法参数传递,其中 T
始终是 case class
。现在如何构建 val incomingIds
取决于特定的 case class
属性。
我们想创建一个通用函数,它可以接受 List[T]
并且可能 incomingIds
和 return 一个所需的元组,以避免每次都编写看起来相似的样板文件。
如果说逻辑是 "always" 使用 T
case class
的 id
属性,那么我可以轻松地创建父 trait
和 id
并让所有 case class
es 混合特征 - 但这里不是这种情况。准备 val incomingIds
取决于不同的 case class
属性,具体取决于它们在代码中的调用位置。
如下图
def generalizedInsertUpdateDeleteList[T](data:List[T],incomingIds:List[Int], existingIds:List[Int] )(implicit subject: Subject, session:Session) = {
val idsForDeletion = existingIds diff incomingIds
val idsForInsertion = incomingIds diff existingIds
val idsForUpdate = existingIds diff idsForDeletion
/*
//what's the best way to generalize comparison inside collectFirst?
//to use case class attribute names from `T`. Was thinking if Structural type can help but not sure if that
//can quite work unless there is a way to pass in arguments in a structural type?
val deleteList = data.flatMap{ t => idsForDeletion collectFirst{ case id if(id == t.id)=> t} }
val insertList = data.flatMap{ t => idsForInsertion collectFirst{ case id if(id == t.id)=> t} }
val updateList = data.flatMap{ t => idsForUpdate collectFirst {case id if(id === t.id)=> t}}
*/
如果没有其他更简洁的方法使用标准 scala/scalaz API 来实现此目的,shapeless 能否在这方面提供帮助?
Shapeless 的记录提供了一种类型安全的方法来抽象具有特定成员名称的大小写 类。例如:
import shapeless._, ops.record.Selector
def getId[A, R <: HList](a: A)(implicit
gen: LabelledGeneric.Aux[A, R],
sel: Selector[R, Witness.`'id`.T]
): sel.Out = sel(gen.to(a))
然后:
scala> case class Foo(id: String)
defined class Foo
scala> case class Bar(id: Int, name: String)
defined class Bar
scala> getId(Foo("12345"))
res0: String = 12345
scala> getId(Bar(123, "bar"))
res1: Int = 123
如果需要约束id
成员的类型,可以使用Selector.Aux
:
def getIntId[A, R <: HList](a: A)(implicit
gen: LabelledGeneric.Aux[A, R],
sel: Selector.Aux[R, Witness.`'id`.T, Int]
): Int = sel(gen.to(a))
现在 getIntId(Bar(123, "bar"))
可以编译,但是 getIntId(Foo("12345"))
不会。
您可以创建一个带有 PartialFunction
的类型 class,它可以在 collectFirst
.
中使用
trait IUD[T, IdType] {
// returns a partial function which will be used in collectFirst
def collectId(t: T): PartialFunction[IdType, T]
}
我们可以为您的两种方法创建 IUD
个实例:
// I chose (Long, Long) as type of (ds.dataSourceId,ds.dsTypeId)
type FormModel = FormDefinitionDataSourceRequestModel
implicit object FormModelIUD extends IUD[FormModel, (Long, Long)] {
def collectId(t: FormModel): PartialFunction[(Long, Long), FormModel] = {
case (dsId,dsType) if(dsId == t.dataSourceId && dsType == t.dsTypeId) => t
}
}
implicit object InstanceIUD extends IUD[Instance, Int] {
def collectId(t: Instance): PartialFunction[Int, Instance] = {
case id if id == t.id => t
}
}
我们可以在您的 generalizedInsertUpdateDeleteList
方法中使用 IUD
类型 class :
def generalizedIUDList[T, IdType](
data: List[T], incomingIds: List[IdType], existingIds: List[IdType]
)(implicit
subject: Subject, session: Session, iud: IUD[T, IdType]
) = {
val idsForDeletion = existingIds diff incomingIds
val idsForInsertion = incomingIds diff existingIds
val idsForUpdate = existingIds diff idsForDeletion
def filterIds(ids: List[IdType]) =
data.flatMap(instance => ids collectFirst(iud.collectId(instance)) )
val deleteList = filterIds(idsForDeletion)
val insertList = filterIds(idsForInsertion)
val updateList = filterIds(idsForUpdate)
(deleteList,updateList,insertList)
}
collectFirst
接受 PartialFunction
,在你的情况下 PartialFunction[Int, T]
我想?
您可以将部分函数作为参数传递给您的 generalizedInsertUpdateDeleteList
方法,这样每次只需要定义这些。
我们有
private def insertUpdateDeleteFormDsList(dsList : List[FormDefinitionDataSourceRequestModel])(implicit formDefId:Int,subject:Subject,session: Session) : (List[(Int,Int)],Seq[FormDefinitionDataSourceRequestModel],Seq[FormDefinitionDataSourceRequestModel]) = {
val incomingIds = dsList.map( ds => (ds.dataSourceId,ds.dsTypeId) )
val existingIds = formDefinitionDatasources.filter(_.tenantId === subject.tenantId).filter(_.formDefId === formDefId).map( ds => (ds.dataSourceId,ds.dataSourceTypeId) ).list
val idsForDeletion = existingIds diff incomingIds
val idsForInsertion = incomingIds diff existingIds
val idsForUpdate = existingIds diff idsForDeletion
val insertList = dsList.flatMap{ t => idsForInsertion collectFirst{ case (dsId,dsType) if(dsId == t.dataSourceId && dsType == t.dsTypeId)=> t} }
val updateList = dsList.flatMap{t=>idsForUpdate collectFirst {case (dsId,dsType) if(dsId == t.dataSourceId && dsType == t.dsTypeId)=> t}}
(idsForDeletion,updateList,insertList)
}
和其他类似的方法
private def insertUpdateDelDataInstances(instances: List[Instance])(implicit subject: Subject, session: Session): (Seq[Instance], Seq[Instance], Seq[Instance]) = {
val incomingIds = instances.map(_.id)
val existingIds = dataSourceInstanceNew.filter(_.tenantId === subject.tenantId).map(_.id).list
val idsForDeletion = existingIds diff incomingIds
val idsForInsertion = incomingIds diff existingIds
val idsForUpdate = existingIds diff idsForDeletion
val deleteList = instances.flatMap{ t => idsForDeletion collectFirst{ case id if(id == t.id)=> t} }
val insertList = instances.flatMap{ t => idsForInsertion collectFirst{ case id if(id == t.id)=> t} }
val updateList = instances.flatMap{t=>idsForUpdate collectFirst {case id if(id === t.id)=> t}}
(deleteList,updateList,insertList)
}
其他地方也有类似的方法出现。每次 List[T]
将作为方法参数传递,其中 T
始终是 case class
。现在如何构建 val incomingIds
取决于特定的 case class
属性。
我们想创建一个通用函数,它可以接受 List[T]
并且可能 incomingIds
和 return 一个所需的元组,以避免每次都编写看起来相似的样板文件。
如果说逻辑是 "always" 使用 T
case class
的 id
属性,那么我可以轻松地创建父 trait
和 id
并让所有 case class
es 混合特征 - 但这里不是这种情况。准备 val incomingIds
取决于不同的 case class
属性,具体取决于它们在代码中的调用位置。
如下图
def generalizedInsertUpdateDeleteList[T](data:List[T],incomingIds:List[Int], existingIds:List[Int] )(implicit subject: Subject, session:Session) = {
val idsForDeletion = existingIds diff incomingIds
val idsForInsertion = incomingIds diff existingIds
val idsForUpdate = existingIds diff idsForDeletion
/*
//what's the best way to generalize comparison inside collectFirst?
//to use case class attribute names from `T`. Was thinking if Structural type can help but not sure if that
//can quite work unless there is a way to pass in arguments in a structural type?
val deleteList = data.flatMap{ t => idsForDeletion collectFirst{ case id if(id == t.id)=> t} }
val insertList = data.flatMap{ t => idsForInsertion collectFirst{ case id if(id == t.id)=> t} }
val updateList = data.flatMap{ t => idsForUpdate collectFirst {case id if(id === t.id)=> t}}
*/
如果没有其他更简洁的方法使用标准 scala/scalaz API 来实现此目的,shapeless 能否在这方面提供帮助?
Shapeless 的记录提供了一种类型安全的方法来抽象具有特定成员名称的大小写 类。例如:
import shapeless._, ops.record.Selector
def getId[A, R <: HList](a: A)(implicit
gen: LabelledGeneric.Aux[A, R],
sel: Selector[R, Witness.`'id`.T]
): sel.Out = sel(gen.to(a))
然后:
scala> case class Foo(id: String)
defined class Foo
scala> case class Bar(id: Int, name: String)
defined class Bar
scala> getId(Foo("12345"))
res0: String = 12345
scala> getId(Bar(123, "bar"))
res1: Int = 123
如果需要约束id
成员的类型,可以使用Selector.Aux
:
def getIntId[A, R <: HList](a: A)(implicit
gen: LabelledGeneric.Aux[A, R],
sel: Selector.Aux[R, Witness.`'id`.T, Int]
): Int = sel(gen.to(a))
现在 getIntId(Bar(123, "bar"))
可以编译,但是 getIntId(Foo("12345"))
不会。
您可以创建一个带有 PartialFunction
的类型 class,它可以在 collectFirst
.
trait IUD[T, IdType] {
// returns a partial function which will be used in collectFirst
def collectId(t: T): PartialFunction[IdType, T]
}
我们可以为您的两种方法创建 IUD
个实例:
// I chose (Long, Long) as type of (ds.dataSourceId,ds.dsTypeId)
type FormModel = FormDefinitionDataSourceRequestModel
implicit object FormModelIUD extends IUD[FormModel, (Long, Long)] {
def collectId(t: FormModel): PartialFunction[(Long, Long), FormModel] = {
case (dsId,dsType) if(dsId == t.dataSourceId && dsType == t.dsTypeId) => t
}
}
implicit object InstanceIUD extends IUD[Instance, Int] {
def collectId(t: Instance): PartialFunction[Int, Instance] = {
case id if id == t.id => t
}
}
我们可以在您的 generalizedInsertUpdateDeleteList
方法中使用 IUD
类型 class :
def generalizedIUDList[T, IdType](
data: List[T], incomingIds: List[IdType], existingIds: List[IdType]
)(implicit
subject: Subject, session: Session, iud: IUD[T, IdType]
) = {
val idsForDeletion = existingIds diff incomingIds
val idsForInsertion = incomingIds diff existingIds
val idsForUpdate = existingIds diff idsForDeletion
def filterIds(ids: List[IdType]) =
data.flatMap(instance => ids collectFirst(iud.collectId(instance)) )
val deleteList = filterIds(idsForDeletion)
val insertList = filterIds(idsForInsertion)
val updateList = filterIds(idsForUpdate)
(deleteList,updateList,insertList)
}
collectFirst
接受 PartialFunction
,在你的情况下 PartialFunction[Int, T]
我想?
您可以将部分函数作为参数传递给您的 generalizedInsertUpdateDeleteList
方法,这样每次只需要定义这些。