2016-10-08 12 views
5

Я пытаюсь использовать бесформенные создать функцию 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)

Тем не менее, если не применять poly2 с перегрузкой PolyApply, а вместо этого сделать:

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

Затем он работает. Я не уверен, что делает foldleft, чтобы типы разрешались. Если это общепринятый способ, как мне сделать это общее, чтобы я мог использовать Poly3? или Poly4?

Есть ли способ расширить тип в поли, чтобы заставить это работать с методом apply? Может быть, я буду об этом неправильно, я открыт для предложений

ответ

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. Это, вероятно, в основном вопрос стиля.


Вы могли создать Poly2 с одним Case.Aux[IndexedItem, Indexable, IndexedItem] случае, который позволил бы использовать apply непосредственно, но это было бы более многословным и менее идиоматическое, чем один из складчатых подходов (также в этот момент вы Wouldn Даже не нужно значение полиморфной функции - вы можете просто использовать обычный (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 (и тесты Shapeless пройдут, если вы удалите OutH и просто используйте In). Я присмотрюсь и, возможно, открою проблему.