30

我现在已经做了一些 HList 的实现。一个基于 Daniel Spiewak 在 Scala 之地的高级巫术演讲,另一个基于 Apocalisp 博客中的一篇文章。目标是有一个异质列表,其中在主要类型中不是异质的,而是更高类型的。例如:

val requests = Request[String] :: Request[Int] :: HNil

我将能够在列表中进行映射以执行请求并生成更高类型的异构列表。所以:

requests.map(execute)

应该等于

String :: Int :: HNil

可悲的是,我所有的尝试都导致了 Any 的 HList。这是最近尝试的代码:

class Request[+Out](o:Out) {
  type O = Out

  def v:O = o
}

object HList {
  trait Func[-Elem,Out] {
    type Apply[E <: Elem] <: Out
    def apply[N <: Elem](e:N):Apply[N]
  }
  sealed trait HList[Base] {
    type Head <: Base
    type Tail <: HList[Base]
    type Map[Out,F <: Func[Base,Out]] <: HList[Out]
    def head:Head
    def tail:Tail

    def ::[A <: Base](a:A):HList[Base]
    def map[Out,F <: Func[Base,Out]](f:F):Map[Out,F]
  }

  case class HNil[Base]() extends HList[Base] {
    type Head = Nothing
    type Tail = Nothing
    type Map[Out,F <: Func[Base,Out]] = HNil[Out]

    def head = error("Head of an empty HList")
    def tail = error("Head of an empty HList")

    def ::[A <: Base](a:A) = HCons(a,this)
    def map[Out,F <: Func[Base,Out]](f:F) = new HNil[Out]
  }

  case class HCons[Base,A <: Base,B <: HList[Base]](head: A, tail: B) extends HList[Base] {
    type Head = A
    type Tail = B    
    type Map[Out,F <: Func[Base,Out]] = HCons[Out,F#Apply[Head],Tail#Map[Out,F]]

    def ::[C <: Base](c:C) = HCons(c,this)
    def map[Out,F <: Func[Base,Out]](f:F) =
      HCons(f(head),tail.map(f))
  }

  val :: = HCons 
}

object Test extends Application {
  import HList._

  val HNil = new HNil[Request[_]]

  val list = new Request[Int](1) :: new Request[String]("1") :: HNil

  val (a :: b :: HNil) = list
  val y:Request[String] = b

  val results = list.map[Any,Unwrap.type](Unwrap)

  val i:Int = results.head
}

import HList._
object Unwrap extends Func[Request[Any],Any] {
  type Apply[I <: Request[Any]] = I#O
  def apply[N <: Request[Any]](e:N) = null.asInstanceOf[Apply[N]]
}

另一种尝试是基于 Apocalisp 版本,它使用 fold 创建一个新的 HList,它再次产生了一个 Any 类型的 HList。任何提示将不胜感激。

4

3 回答 3

23

The HList implementation in shapeless is rich enough to subsume both HList and KList functionality. It provides a map operation which applies a higher-ranked function, possibly with type-specific cases, across it's elements yielding an appropriately typed HList result,

import shapeless.Poly._
import shapeless.HList._

// Define a higher-ranked function from Sets to Options
object choose extends (Set ~> Option) {
  def default[T](s : Set[T]) = s.headOption 
}

// An HList of Sets
val sets = Set(1) :: Set("foo") :: HNil

// Map our choose function across it ...
val opts = sets map choose

// The resulting value
opts == Option(1) :: Option("foo") :: HNil 

Note that although it's the case in the above example there's no requirement that the HList elements share a common outer type constructor, it just has to be the case that the higher-ranked function mapped with has cases for all of the types involved,

// size is a higher-ranked function from values of arbitrary type to a 'size'
// which is defined as 1 by default but which has type specific cases for
// Strings and tuples
object size extends (Id ~> Const[Int]#λ) {
  def default[T](t : T) = 1
}
implicit def sizeString = size.λ[String](s => s.length)
implicit def sizeTuple[T, U](implicit st : size.λ[T], su : size.λ[U]) =
  size.λ[(T, U)](t => 1+size(t._1)+size(t._2))

size(23) == 1          // Default
size("foo") == 3       // Type specific case for Strings
size((23, "foo")) == 5 // Type specific case for tuples

Now let's map this across an HList,

val l = 23 :: true :: "foo" :: ("bar", "wibble") :: HNil
val ls = l map size

ls == 1 :: 1 :: 3 :: 10 :: HNil

In this case the result type of the function being mapped is constant: it's an Int no matter what the argument type is. Consequently the resulting HList has elements all of the same type, which means that it can usefully be converted to a vanilla list,

ls.toList == List(1, 1, 3, 10)
于 2012-01-02T18:19:08.337 回答
3

您需要的是具有类型构造函数的 KlistRequest和自然转换execute: Request ~> Id。所有这一切都在 Apocalisp 精彩的类型级编程系列文章中有详细说明,特别是:

  1. 自然变换文字
  2. Klist 基础知识

您可以从Mark Harrah 的 up repo中查看整个系列的代码

在你的情况下,你需要类似的东西

val reqList = new Request[Int](1) :^: new Request[String]("1") :^: KNil    
val exec = new (Request ~> Id) { def apply[T](reqs: Request[T]): T = reqs.execute }    
val results = reqList down exec

上述down方法在概念上与mapnat transf相同M ~> Id;您还可以map从 nat M ~> Ntransf 和类型 M 的 Klist 产生更一般的 KList 类型 N。

于 2011-03-30T10:52:14.177 回答
0

请注意,您在最近(2016 年 10 月,OP 后 5 年)文章“使用 shapeless 的 HLists 实现额外的类型安全(在 Akka Streams 中) ”中有一个 Map with HList 的示例,来自Mikołaj Koziarkiewicz

  //glue for the ParserStageDefs
  specs.map(s => Flow[Data].map(s.parser).map(s.processor))
                    .foreach(broadcast ~> _ ~> merge)

问题在于我们的规格列表中​​的类型信息没有被保留。或者更确切地说,没有按照我们想要的方式保留 -List元素的类型是ParserStageDef[_ >: Int with String],因此我们的装饰器和增量器的最低通用超类型。

以上暗示,在解析器和处理器元素之间进行映射时,编译器无法提供T给定规范中使用的实际类型。

一个解法

这就是 HLists 来拯救的地方。因为它们保留了每个元素的完整类型信息,所以可以像上次尝试一样定义我们的流程。

首先,让我们将列表替换为HList

import shapeless.ops.hlist._
import shapeless._
//...

val specs = decorator :: incrementer :: HNil
val specsSize = specs.length.toInt

ParserStageDefs现在,对于 from into的映射Flows,我们需要采用不同的方法,因为mapforHList需要一个叫做 P** oly 的东西——一个多态函数值**。

以下是我们的例子:

import shapeless.PolyDefns.~>
object toFlow extends (ParserStageDef ~> ProcessingFlow) {
  override def apply[T](f: ParserStageDef[T]) = 
                Flow[Data].map(f.parser).map(f.processor)
}

为了让它工作,我们还要改变ProcessingFlowtype ProcessingFlow[_] = Flow[Data, Data, _],因为上面的多态函数需要一个更高种类的类型。

现在,我们的中心陈述结果是:

//we convert to a List[ProcessingFlow[_]] for simplicity
specs.map(toFlow).toList.foreach(broadcast ~> _ ~> merge)

我们都准备好了!

于 2016-10-11T19:11:40.950 回答