(依赖?)打字容器

(Dependantly?) typing containers

给定一个类型 constructor/container F[_] 我想制作一个组合器,它可以通过以下规则集将多态类型和 hlist 类型组合到一个新容器中:

在 shapeless 中有什么好的方法可以做到这一点吗?或者有没有更简单的方法来解决这个特定问题 :) 问题是我有组合器产生 F[Int]F[HNil]F[HNil]。例如,组合这些不应产生 F[Int :: HNil :: HNil](但 F[Int :: HNil])。为此,我现在制作了 typeclass:

 trait CodecAppender[F, A, B] {
  type Out
  final type CodecOut = Codec[F, Out]
  def apply(a: Codec[F, A], b: Codec[F, B]): CodecOut
}

object CodecAppender {

  type Aux[F, A, B, C] = CodecAppender[F, A, B] { type Out = C }

  private def make[F, A, B, C](decompose: C => (A, B))(implicit S: Monoid[F], D: DecoderAppender.Aux[F, A, B, C]): Aux[F, A, B, C] = new CodecAppender[F, A, B] {
    type Out = C

    def apply(a: Codec[F, A], b: Codec[F, B]): CodecOut = new Codec[F, C] {
      def encode(in: C) = {
        val (head, tail) = decompose(in)
        a.encode(head) |+| b.encode(tail)
      }

      val decoder = D.append(a.decoder, b.decoder)
    }
  }

  implicit def HNilAndHNil[F, O](implicit M: Monoid[F],
                                 D: DecoderAppender.Aux[F, HNil, HNil, HNil]) =
    make[F, HNil, HNil, HNil](r => HNil -> HNil)

  //other cases omitted, but there are 6 more (which I described above)

}

Scodec 正在做类似的事情:

  def prepend[A, L <: HList](a: Codec[A], l: Codec[L]): Codec[A :: L] = new Codec[A :: L] {
    override def sizeBound = a.sizeBound + l.sizeBound
    override def encode(xs: A :: L) = Codec.encodeBoth(a, l)(xs.head, xs.tail)
    override def decode(buffer: BitVector) = Codec.decodeBothCombine(a, l)(buffer) { _ :: _ }
    override def toString = s"$a :: $l"
  }

https://github.com/scodec/scodec/blob/ac71016dcea61cc6aaabe4f4dff4ab5bf13ac239/shared/src/main/scala/scodec/codecs/HListCodec.scala#L19-L25

有一种很好的方法可以使用一系列 classes 来做到这一点。如果你的 F[_] 有一个 Monad:

的实例,你可以做一些事情
trait Conv[A]{
  type Out <: HList
  def apply(a: A): Out
}

object Conv extends LowPrior{
  implicit def hlist[L <: HList] = new Conv[L]{
    type Out = L
    def apply(hl: L) = hl
  }
}

trait LowPrior{
  implicit def other[A] = new Conv[A]{
    type Out = H :: HNil
    def apply(a: A) = a :: HNil
  }
}

def combineF[F[_], A, B](fa: F[A], fb: F[B])(implicit ca: Conv[A], cb: Conv[B], m: Monad[F]) = m.flatMap(fa){ a: A =>
  m.map(fb){ b: B => ca(a) ::: c(b) }
}

如果你想要真正的 return 类型,你可以在 Conv 伴随对象中定义一个 Aux 类型,然后使用 Prepend 类型 class一个 HList 以获得最终实现的结果类型。 (我实际上相当肯定你可以用 Applicative 甚至 Zip 类型 class 实现同样的事情。这将留给你去发现。)