Coproduct with Poly

I am trying to use formless to create a poly2 function that can take coproduct:

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) } } 

It gives me an error

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

What I think is appropriate, given that Poly2 works with instances of elements, and not with an extended type of coprocess (i.e., implications are generated for Item1 , not Indexable )

However, if I do not apply poly2 to PolyApply overload, but instead:

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

Then it will work. I'm not sure what foldleft does to make types resolve. If this is a common way, how can I do this in general so that I can use Poly3? or Poly4?

Is there a way to extend a type in poly to make this work with the apply method? Maybe I'll go about it wrong, I'm open to suggestions

+5
source share
1 answer

In order for the left to reset coproduction with Poly2 , the function must provide cases like Case.Aux[A, x, A] , where A is the type (fixed) of the battery, and x is each element in the coproduction.

Your updateCopy does just that for the battery type IndexedItem and coproduct Indexable , so you can collapse Indexable with the initial IndexedItem to get IndexedItem . If I understand correctly, this is exactly what you want - the unique source case in updateCopy will be applied to the initial IndexedItem and the coproduct value, and you will get the updated IndexedItem .

It’s a little unreasonable to think of this operation as a “left fold”, and you can alternatively write it as a normal fold that simply reduces the co-product to a value.

 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)) } 

And then:

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

I personally find this a bit more readable - you roll up the co-product to an update function, and then apply this function to an existing IndexedItem . This is probably mostly a matter of style.


You can create Poly2 with a single Case.Aux[IndexedItem, Indexable, IndexedItem] that allows you to use apply directly, but it will be more verbose and less idiomatic than one of the bending approaches (also at that moment you wouldn’t even you need the value of the polymorphic function - you can just use the regular (IndexedItem, Indexable) => IndexedItem ).


Finally, I don’t quite understand what you mean by extending the addition approach to Poly3 , etc., but if you want to provide additional initial values ​​that need to be converted, then you can make the battery type a tuple (or Tuple3 , etc. ) For instance:

 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!") } } 

And then:

 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!)) 

If this is not what you meant by the Poly3 part, I would be glad to expand the answer.


As a note, the LeftFolder source indicates that cases may have output types that do not match the type of battery, since tlLeftFolder has an OutH type OutH . This seems a little strange to me, because, as far as I can tell, OutH will always be In (and Shapeless tests will pass if you remove OutH and just use In ). I will consider in more detail and possibly open the problem.

+2
source

Source: https://habr.com/ru/post/1257878/


All Articles