用例 class 复制方法并抽象命名参数

Use case class copy method and abstract over named parameters

我正在使用 scalaz state monad,但遇到以下问题:
我的状态是一个案例 class 包含不同类型的向量:

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 简单地更新向量中的一个元素,隐式 class:

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

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

您应该可以为此使用镜头库,而不是直接使用 case class 复制功能。镜头是一种在 B 中获取和设置一些 A 的机制。您已经以 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 ()
}