我正在尝试使用无形来创建可以采用副产品的poly2函数:

case class IndexedItem(
  item1: Item1,
  item2: Item2,
  item3: Item3
)

case class Item1(name: Int)

case class Item2()

case class Item3()

object IndexUpdater {
  type Indexable = Item1 :+: Item2 :+: Item3 :+: CNil

  object updateCopy extends Poly2 {
    implicit def caseItem1 = at[IndexedItem, Item1] { (a, b) => a.copy(item1 = b) }

    implicit def caseItem2 = at[IndexedItem, Item2] { (a, b) => a.copy(item2 = b) }

    implicit def caseItem3 = at[IndexedItem, Item3] { (a, b) => a.copy(item3 = b) }
  }

  def mergeWithExisting(existing: IndexedItem, item: Indexable): IndexedItem = {
    updateCopy(existing, item)
  }
}

这给我一个错误



考虑到Poly2正在处理项目的实例,而不是扩展的副产品类型(即隐式是为Item1而不是Indexable生成的),我认为这是有道理的

但是,如果我不将poly2与PolyApply重载一起应用,请执行以下操作:
def mergeWithExisting(existing: IndexedItem, item: Indexable): IndexedItem = {
  item.foldLeft(existing)(updateCopy)
}

然后就可以了。我不确定foldleft在做什么,以便类型解析。如果这是公认的方式,如何使它通用,以便可以使用Poly3?还是Poly4?

有什么办法可以扩大poly的类型,以使其与apply方法一起工作?也许我走错路了,我愿意接受建议

最佳答案

为了用Poly2保留一个副产品,该函数需要提供Case.Aux[A, x, A]类型的情况,其中A是(固定的)累加器类型,而x是该副产品中的每个元素。

您的updateCopy正是针对累加器类型IndexedItem和副产品Indexable做到了这一点,因此您可以将Indexable与初始IndexedItem一起折叠以获得IndexedItem。如果我理解正确,那么这正是您想要的— updateCopy中的唯一适当情况将应用于初始IndexedItem和副产品的值,您将获得更新的IndexedItem

将此操作视为“左折”有点不直观,您也可以将其写为普通折叠,即简单地将副产品折叠为一个值。

object updateCopy extends Poly1 {
  type U = IndexedItem => IndexedItem

  implicit val caseItem1: Case.Aux[Item1, U] = at[Item1](i => _.copy(item1 = i))
  implicit val caseItem2: Case.Aux[Item2, U] = at[Item2](i => _.copy(item2 = i))
  implicit val caseItem3: Case.Aux[Item3, U] = at[Item3](i => _.copy(item3 = i))
}

然后:
def mergeWithExisting(existing: IndexedItem, item: Indexable): IndexedItem =
  item.fold(updateCopy).apply(existing)

我个人觉得这更具可读性-您将联乘分解为一个update函数,然后将该函数应用于现有的IndexedItem。不过,这可能主要是样式问题。

您可以使用一个Poly2案例创建一个Case.Aux[IndexedItem, Indexable, IndexedItem],使您可以直接使用apply,但是比其中一种折叠方法更冗长,而且没有更多习惯用法(在那时,您甚至不需要多态函数值-您可以只使用普通的(IndexedItem, Indexable) => IndexedItem)。

最后,我不确定将fold方法扩展到Poly3等是什么意思,但是如果要提供其他要转换的初始值,则可以使累加器类型为元组(或Tuple3,等等。)。例如:
object updateCopyWithLog extends Poly2 {
  type I = (IndexedItem, List[String])

  implicit val caseItem1: Case.Aux[I, Item1, I] = at {
    case ((a, log), b) => (a.copy(item1 = b), log :+ "1!")
  }

  implicit val caseItem2: Case.Aux[I, Item2, I] = at {
    case ((a, log), b) => (a.copy(item2 = b), log :+ "2!")
  }

  implicit val caseItem3: Case.Aux[I, Item3, I] = at {
    case ((a, log), b) => (a.copy(item3 = b), log :+ "2!")
  }
}

然后:
scala> val example: Indexable = Coproduct(Item1(10))
example: Indexable = Inl(Item1(10))

scala> val existing: IndexedItem = IndexedItem(Item1(0), Item2(), Item3())
existing: IndexedItem = IndexedItem(Item1(0),Item2(),Item3())

scala> example.foldLeft((existing, List.empty[String]))(updateCopyWithLog)
res0: (IndexedItem, List[String]) = (IndexedItem(Item1(10),Item2(),Item3()),List(1!))

如果那不是Poly3部分的意思,我很乐意扩展答案。

作为一个脚注, LeftFolder source建议案例可以具有与累加器类型不同的输出类型,因为tlLeftFolder具有OutH类型参数。这对我来说似乎有点奇怪,因为据我所知OutH一定总是In(如果删除OutH并仅使用In,Shapeless测试就会通过)。我会仔细看一下,也许还会提出一个问题。

关于scala - 与Poly的副产品,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39927666/

10-10 07:11