与Poly

时间:2016-10-08 01:06:13

标签: scala 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)
  }
}

这给了我一个错误

  

错误:(48,15)找不到参数cse的隐含值:   shapeless.poly.Case [samples.IndexUpdater.updateCopy.type,不成形。:: [samples.IndexedItem,不成形。:: [samples.IndexUpdater.Indexable,shapeless.HNil]]]       updateCopy(现有,项目)

我认为这是合理的,因为Poly2正在处理项目的实例,而不是扩展的副产品类型(即为Item1生成了隐含而不是Indexable

但是,如果我没有将poly2应用于PolyApply重载,而是执行:

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

然后它确实有效。我不确定foldleft正在做什么,以便类型解决。如果这是可接受的方式,我该如何制作这种通用的,以便我可以使用Poly3?还是Poly4?

有没有办法扩展poly中的类型以使其与apply方法一起使用?也许我以错误的方式解决这个问题,我愿意接受建议

1 个答案:

答案 0 :(得分:2)

为了使带有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)

我个人认为这更具可读性 - 您将副产品折叠到更新功能,然后将该功能应用于现有的IndexedItem。不过,这可能主要是风格问题。

可以创建Poly2Case.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并且只使用In,则无形测试会通过})。我会仔细看看,也许会打开一个问题。