Сумма двух HList одинакового размера во время компиляции?

Учитывая следующую попытку получения рекурсивной суммы двух HList, показанную ниже:

(Пожалуйста, извините за Product в его названии.)

package net

import shapeless._
import shapeless.nat._
import shapeless.ops.nat.{Sum, Prod, Mod}

trait SumZippedProduct[L, M] {
  type S
}
object SumZippedProduct {

  type Aux[L, M, O] = SumZippedProduct[L, M] {
    type S = O
  }

  def apply[L <: HList, M <: HList](implicit ev: SumZippedProduct[L, M]) = ev

  // LH - L's head
  // L  - HList
  // MH - M's head
  // M  - HList
  // RS - Recursive Sum (L + H)
  // CS - Current Sum (LH + RH)
  // E  - RS + CS
  implicit def sumZippedInductiveEq5[LH <: Nat, L <: HList, MH <: Nat, M <: HList, RS <: Nat, CS <: Nat, E <: Nat](
    implicit ev: SumZippedProduct.Aux[L, M, RS],
             curr: Sum.Aux[LH, MH, CS],
             total: Sum.Aux[CS, RS, E]
  ): SumZippedProduct[LH :: L, MH :: M] = new SumZippedProduct[LH :: L, MH :: M] {
    type S = E
  }

  implicit val hnils: SumZippedProduct[HNil, HNil] = new SumZippedProduct[HNil, HNil] {
    type S = _0
  }

}

При проверке это работает, я думаю, для случая HNil, но не для 1-элементного HList:

scala> import net.SumZippedProduct
import net.SumZippedProduct

scala> import shapeless._, nat._
import shapeless._
import nat._

// expecting 0 (0 + 0)
scala> SumZippedProduct[HNil, HNil]
res0: net.SumZippedProduct[shapeless.HNil,shapeless.HNil] = net.SumZippedProduct$$anon$2@794b4359

// expecting _4 (1 + 3)
scala> SumZippedProduct[_1 :: HNil, _3 :: HNil]
<console>:19: error: could not find implicit value for parameter ev: net.SumZippedProduct[shapeless.::[shapeless.nat._1,shapeless.HNil],shapeless.::[shapeless.nat._3,shapeless.HNil]]
       SumZippedProduct[_1 :: HNil, _3 :: HNil]
                       ^

Почему не компилируется при передаче _1 :: HNil и _3 :: HNil?

Кроме того, как я могу получить _0 в res0?

scala> res0.S
<console>:20: error: value S is not a member of net.SumZippedProduct[shapeless.HNil,shapeless.HNil]
       res0.S
            ^

Примечание. Я признателен, если такая реализация уже существует в бесформенном виде, но я задаю этот вопрос, чтобы узнать.


person Kevin Meredith    schedule 08.02.2017    source источник


Ответы (1)


Вы должны использовать Aux в возвращаемом типе ваших имплицитов. В противном случае конкретный тип S будет потерян. Для метода вызова apply вы также должны использовать более точный тип возвращаемого значения по той же причине. Поскольку SumZippedProduct расширяет AnyRef, вы можете просто использовать ev.type; точнее не получится.

scala> :paste
// Entering paste mode (ctrl-D to finish)

import shapeless._
import shapeless.nat._
import shapeless.ops.nat.{Sum, Prod, Mod, ToInt}

trait SumZippedProduct[L, M] {
  type S <: Nat
  final def S(implicit toInt: ToInt[S]): Int = toInt()
}
object SumZippedProduct {

  type Aux[L, M, O] = SumZippedProduct[L, M] {
    type S = O
  }

  def apply[L <: HList, M <: HList](implicit ev: SumZippedProduct[L, M]): ev.type = ev

  implicit def sumZippedInductiveEq5[LH <: Nat, L <: HList, MH <: Nat, M <: HList, RS <: Nat, CS <: Nat, E <: Nat](
    implicit ev: SumZippedProduct.Aux[L, M, RS],
             curr: Sum.Aux[LH, MH, CS],
             total: Sum.Aux[CS, RS, E]
  ): SumZippedProduct.Aux[LH :: L, MH :: M, E] = new SumZippedProduct[LH :: L, MH :: M] {
    type S = E
  }

  implicit val hnils: SumZippedProduct.Aux[HNil, HNil, _0] = new SumZippedProduct[HNil, HNil] {
    type S = _0
  }

}

// Exiting paste mode, now interpreting.

import shapeless._
import shapeless.nat._
import shapeless.ops.nat.{Sum, Prod, Mod}
defined trait SumZippedProduct
defined object SumZippedProduct

scala> SumZippedProduct[HNil, HNil]
res1: SumZippedProduct.<refinement>.type = SumZippedProduct$$anon$2@673bac03

scala> val a: res1.S = _0
a: res1.S = shapeless._0@4f450e01

scala> SumZippedProduct[_1 :: HNil, _3 :: HNil]
res2: SumZippedProduct.Aux[shapeless.::[shapeless.nat._1,shapeless.HNil],shapeless.::[shapeless.nat._3,shapeless.HNil],this.Out] = SumZippedProduct$$anon$1@2a53bcfa

scala> val a: res2.S = _4
a: res2.S = Succ()

scala> val a: res2.S = _5
<console>:26: error: type mismatch;
 found   : shapeless.nat._5
    (which expands to)  shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]]
 required: res2.S
    (which expands to)  shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]
       val a: res2.S = _5
                       ^

scala> res2.S
res3: Int = 4

Я также добавил метод для генерации соответствующего целочисленного значения. На самом деле я не знаю существующего способа вызвать значение Nat из типа Nat (или, может быть, я просто слепой...). Но я думаю, что в большинстве случаев Nat полезен только на уровне типа, и что вы бы предпочли работать с фактическими Int на уровне значения.

Это также не так сложно реализовать самостоятельно, если вам действительно нужен уровень значений Nats. Но если вы посмотрите на их реализацию в бесформенном виде, то на самом деле это просто пустые коробки, так что с ними мало что можно сделать. Полезны только их типы.

person Jasper-M    schedule 08.02.2017
comment
Насколько я понимаю, в чем разница между ev.type и ev в зависимости от возвращаемого типа SumZippedProduct#apply? - person Kevin Meredith; 09.02.2017
comment
Это одноэлементный тип ev, но он не выводится автоматически. Поэтому, если вы явно не укажете, что вам нужен одноэлементный тип, в качестве возвращаемого типа будет выведен SumZippedProduct[L, M], и вы потеряете информацию о типе. - person Jasper-M; 09.02.2017
comment
Вы также можете написать is как def apply[L <: HList, M <: HList](implicit ev: SumZippedProduct[L, M]): SumZippedProduct.Aux[L, M, ev.S] = ev. - person Jasper-M; 09.02.2017