用例类复制方法和摘要优于命名参数

时间:2015-12-04 12:48:49

标签: scala scalaz shapeless

我正在使用scalaz state monad,我有以下问题:
我的州是一个包含不同类型向量的案例类:

case class Repository(instances: Vector[Instance], vpcs: Vector[Vpc], subnets: Vector[Subnet]….)

我想通过状态操作对存储库进行更改。例如,我想基于更新函数更新向量中的一些元素,如下所示:

  val applicative = Applicative[({type f[a] = State[Repository, a]})#f]
  def createTags[T <: {def id : String}](memberSelector: Repository => Vector[T])(updateTags: T => T) =
    for {
      matchingResources <- State.gets((repo: Repository)=> memberSelector(repo).filter(t => resourcesIds.contains(t.id)))
      _ <- applicative.traverse(matchingResources)(matchingResource => State.modify((repo: Repository) => repo.copy(*** = memberSelector(repo).replaceFirst(matchingResource, updateTags(matchingResource)))))
    } yield ()

replaceFirst只是更新pimped隐式类中向量中的元素:

def replaceFirst(oldElem: A, newElem: A): Vector[A] = {
  val i = v.indexOf(oldElem)
  if (i == -1) v else v.updated(i, newElem)
}

问题是我无法抽象调用Repository类的copy方法时使用的named参数(参见上面代码中的***)。
我想也许Shapeless可以帮助我做到这一点:我可以使用case类来HList同构来将Repository案例类视为HList,然后使用HList操作来更新相关的Vector。我无法让它工作。可以通过Shapeless做到吗?还有其他想法吗?

1 个答案:

答案 0 :(得分:1)

您应该可以直接使用镜头库代替案例类复制功能。镜头是在A中获取和设置一些B的机制。您已经以memberSelector

的形式获取了该内容

例如使用monocale,它可以提供漂亮的宏来制作镜头 (我实际上并没有对此进行过类型检查)

object Repository {
  import monocle.Lens
  import monocle.macros.GenLens

  val _instances: Lens[Repository, Vector[Instance]] = GenLens[Repository](_.instances)
  val _vpcs: Lens[Repository, Vector[Vpc]] = GenLens[Repository](_.vpcs)
  ///...

  val applicative = Applicative[({type f[a] = State[Repository, a]})#f]
  def createTags[T <: {def id : String}](lens: Lens[Repository, Vector[T]])(updateTags: T => T) =
    for {
      matchingResources <- State.gets((repo: Repository)=> lens.get(repo).filter(t => resourcesIds.contains(t.id)))
      _ <- applicative.traverse(matchingResources)(matchingResource => State.modify((repo: Repository) => lens.modify(_.replaceFirst(matchingResource, updateTags(matchingResource)))))
    } yield ()
}

下一个版本的monacle还应该包含使用Lens动作获取scalaz.State的好方法。这应该允许与下面的scalaz.Lens版本非常相似的语法。

您也可以使用scalaz.Lens

object Repository {
  import scalaz.Lens

  val _instances: Lens[Repository, Vector[Instance]] = Lens.lensu((r, i) => r.copy(instances = i), _.instances)
  ///...

  import scalaz.std.vector._
  import scalaz.syntax.traverse._
  def createTags[T <: {def id : String}](lens: Lens[Repository, Vector[T]])(updateTags: T => T) =
    for {
      matchingResources <- lens.map(_.filter(t => resourcesIds.contains(t.id)))
      _ <- matchingResources.traverseS_(matchingResource => lens.mods_(_.replaceFirst(matchingResource, updateTags(matchingResource))))
    } yield ()
}