2014-01-29 68 views
5

通用解壓我有以下斯卡拉問題:斯卡拉 - 爲HList

寫,將採取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是否完全可能?

謝謝。

+0

哇,這是一個類型級別的GroupBy。很有意思。將其發佈到無形郵件列表中! – wheaties

回答

5

有很多方法來解決這個問題,並定義自定義類型類(如尼基塔的答案)是一個非常好的答案。不過,我個人發現以下方法更清晰一些。首先讓我們由類羣的異列表爲幺:

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,雖然你可以很容易地編寫你自己的,它只是一個類型都有證人型類帶有標識元素的附加操作。至關重要的是,對於這個例子,列表(任何東西)都是連接下的monoids,空列表作爲標識元素。

下一頁爲包裝無論你給它在列表中一個簡單的多態函數:

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這個列表的總和,我們就完成了。

這是所有使用無形狀2.0,但它看起來在1.2中非常相似。

+0

這個實現有非常有趣的屬性。我可以使用'Monoid [ArrayBuffer]'實例來處理'append'的'+ ='操作,'append'的第二個操作數的大小總是1. – user3250645

+0

我做了2個小改進:unzipN採用單例函數作爲參數,並且unzipN可以在Functor和Foldable的其他參數上工作。 'def unzipN [L <:HList,Out <:HList,FF [_]:Functor:Foldable](f:Poly1)(hlists:FF [L])(隱式 mapper:ops.hlist.Mapper.Aux [f .type,L,Out], monoid:Monoid [Out]):Out = hlists.map(_ map f).suml' – user3250645

+0

酷!注意'.map(...)。suml'可以表示爲'.foldMap(...)',並且要小心混合類型類,比如'Monoid'和副作用 - 關於'Monoid'的好處之一是你可以假設如減少的順序並不重要。 –

3

爲了解決這個問題,你需要一個自定義的類型類:

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 
+0

如何從隱式[Unzipper [list.type]]。unzip(list)'返回到HList? – user3250645

+0

您將需要另一個類型類型,這是一個反例。通過上面的代碼,我相信你有足夠的信息來告訴你如何自己做到這一點。否則,開始一個新的線程,因爲這是另一個問題,你有你的原始答案。 –

+0

哈哈。我寧願使用Travis Brown的實現,原因如下: – user3250645