与 Poly 的联积

Coproduct with Poly

我正在尝试使用 shapeless 创建一个可以取余积的 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)
  }
}

这给我一个

的错误

Error:(48, 15) could not find implicit value for parameter cse: shapeless.poly.Case[samples.IndexUpdater.updateCopy.type,shapeless.::[samples.IndexedItem,shapeless.::[samples.IndexUpdater.Indexable,shapeless.HNil]]] updateCopy(existing, item)

我认为这是有道理的,因为 Poly2 正在处理项目的实例,而不是扩展的余积类型(即隐式是为 Item1 而不是 Indexable 生成的)

但是,如果我不应用带有 PolyApply 重载的 poly2,而是执行:

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 执行此操作,因此您可以将带有初始 IndexedItemIndexable 左折叠到得到一个 IndexedItem。如果我理解正确,这正是您想要的——updateCopy 中唯一的适当大小写将应用于初始 IndexedItem 和余积的值,您将获得更新后的 IndexedItem

将此操作视为 "left fold" 有点不直观,您也可以将其写成普通折叠,它只是将余积折叠为一个值。

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)

我个人认为这更具可读性 — 您将余积分解为一个更新函数,然后将该函数应用于现有的 IndexedItem。不过,这可能主要是风格问题。


可以 创建一个 Poly2 与单个 Case.Aux[IndexedItem, Indexable, IndexedItem] 案例,这将允许您直接使用 apply,但那将是比其中一种折叠方法更冗长且不那么惯用(同样在这一点上你甚至不需要多态函数值——你可以只使用普通的 (IndexedItem, Indexable) => IndexedItem)。


最后,我不确定将折叠方法扩展到 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 并仅使用 [=39= 则无形测试通过]).我会仔细看看,也许会提出一个问题。