【问题标题】:scala - generic unzip for HListscala - HList 的通用解压缩
【发布时间】:2014-02-21 22:17:00
【问题描述】:

我有以下 Scala 问题:

编写一个函数来获取 HLists 列表

List(23 :: “a” :: 1.0d :: HNil, 24 :: “b” :: 2.0d :: HNil)    # this is list of hlists

并返回列表的 HList

List[Int](23, 24) :: List[String](“a”, “b") :: List[Double](1.0d, 2.0d) :: HNil # this is hlist of lists

这有点像通用的 unzipN。任意HList是否有可能?

谢谢。

【问题讨论】:

  • 哇,这是类型级别的GroupBy。很有意思。将其发布到 Shapeless 邮件列表!

标签: scala shapeless hlist


【解决方案1】:

有很多方法可以解决这个问题,定义一个自定义类型类(如 Nikita 的回答)是一个非常好的方法。不过,我个人认为以下方法更清晰一些。首先让我们将任何由幺半群组成的异构列表变成一个幺半群:

import shapeless._
import scalaz._, Scalaz._

implicit object hnilMonoid extends Monoid[HNil] {
  val zero = HNil
  def append(f1: HNil, f2: => HNil) = HNil
}

implicit def hconsMonoid[H: Monoid, T <: HList: Monoid] = new Monoid[H :: T] {
  val zero = Monoid[H].zero :: Monoid[T].zero
  def append(f1: H :: T, f2: => H :: T) =
    (f1.head |+| f2.head) :: (f1.tail |+| f2.tail)
}

我正在使用ScalazMonoid,尽管你可以很容易地编写自己的——它只是一个类型类,它见证了一个类型对一个标识元素具有类似加法的操作。对这个例子来说至关重要的是,列表(任何东西)是串联下的幺半群,空列表作为标识元素。

接下来是一个简单的多态函数,它将你给它的任何东西包装在一个列表中:

object singleton extends Poly1 { implicit def anything[A] = at[A](List(_)) }

然后我们将它们捆绑在一起:

def unzipN[L <: HList, Out <: HList](hlists: List[L])(implicit
  mapper: ops.hlist.Mapper.Aux[singleton.type, L, Out],
  monoid: Monoid[Out]
): Out = hlists.map(_ map singleton).suml

现在我们可以定义我们的例子了:

val myList = List(23 :: "a" :: 1.0d :: HNil, 24 :: "b" :: 2.0d :: HNil)

我们完成了:

scala> println(unzipN(myList))
List(23, 24) :: List(a, b) :: List(1.0, 2.0) :: HNil

使用这种方法,大多数机器都非常通用,并且很容易直观地了解每个步骤的作用。考虑以下简化示例:

val simple = List(1 :: "a" :: HNil, 2 :: "b" :: HNil)

现在simple.map(_ map singleton) 如下:

List(List(1) :: List("a") :: HNil, List(2) :: List("b") :: HNil)

但是我们知道如何“添加”List[Int] :: List[String] :: HNil 类型的东西,这要归功于我们在顶部的幺半群机器。所以我们可以使用 Scalaz 的suml 来获取列表的总和,我们就完成了。

这都是使用 Shapeless 2.0,但在 1.2 中看起来非常相似。

【讨论】:

  • 这个实现有非常有趣的特性。我可以让它与Monoid[ArrayBuffer] 实例一起使用,对append 进行可变+= 操作,append 的第二个操作数始终为 1。
  • 我做了两个小改进:unzipN 现在将单例函数作为参数,并且 unzipN 可以处理 Functor 和 Foldable 的其他参数。 def unzipN[L &lt;: HList, Out &lt;: HList, FF[_] : Functor : Foldable](f: Poly1)(hlists: FF[L])(implicit mapper: ops.hlist.Mapper.Aux[f.type, L, Out], monoid: Monoid[Out]): Out = hlists.map(_ map f).suml
  • 酷!请注意.map(...).suml 可以表示为.foldMap(...),并且要小心混合类型类,如Monoid 和副作用——Monoid 的好处之一是你可以假设例如减少的顺序无关紧要。
【解决方案2】:

要解决这个问题,您需要一个自定义类型类:

import shapeless._

trait Unzipper[ -input ]{
  type Output
  def unzip( input: input ): Output
}
object Unzipper {
  implicit def headTail
    [ head, tail <: HList ]
    ( implicit tailUnzipper: Unzipper[ List[ tail ] ]{ type Output <: HList } )
    =
    new Unzipper[ List[ head :: tail ] ]{
      type Output = List[ head ] :: tailUnzipper.Output
      def unzip( list: List[ head :: tail ] ) = 
        list.map(_.head) :: tailUnzipper.unzip(list.map(_.tail))
    }
  implicit val nil =
    new Unzipper[ List[ HNil ] ]{
      type Output = HNil
      def unzip( list: List[ HNil ] ) = HNil
    }
}

val list = List(23 :: "a" :: 1.0d :: HNil, 24 :: "b" :: 2.0d :: HNil)
println( implicitly[Unzipper[list.type]].unzip(list) )

输出:

List(23, 24) :: List(a, b) :: List(1.0, 2.0) :: HNil

【讨论】:

  • 如何从implicitly[Unzipper[list.type]].unzip(list)返回HList?
  • 你需要另一个类型类,它是一个逆类型。有了上面的代码,我相信你有足够的信息来说明如何自己做。否则,请启动一个新线程,因为这是另一个问题,而您对原来的问题有答案。
  • 哈哈。我宁愿使用 Travis Brown 的实现,原因如下:
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-13
相关资源
最近更新 更多