【问题标题】:Can Map be performed on a Scala HList可以在 Scala HList 上执行 Map
【发布时间】:2011-07-17 23:52:46
【问题描述】:

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

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

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

requests.map(execute)

应该等于

String :: Int :: HNil

遗憾的是,我所有的尝试都导致了一个任意的 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。任何提示将不胜感激。

【问题讨论】:

    标签: scala shapeless hlist


    【解决方案1】:

    shapeless 中的HList 实现足以包含HListKList 功能。它提供了一个 map 操作,该操作在其元素中应用更高级别的函数,可能具有特定于类型的情况,从而产生适当类型的 HList 结果,

    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 
    

    请注意,尽管在上面的示例中是这种情况,但并不要求 HList 元素共享一个公共的外部类型构造函数,它只需要映射的更高级别的函数具有所有涉及的类型的情况,

    // 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
    

    现在让我们将其映射到HList

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

    在这种情况下,被映射函数的结果类型是常量:无论参数类型是什么,它都是一个 Int。因此,生成的 HList 具有所有相同类型的元素,这意味着它可以有效地转换为普通列表,

    ls.toList == List(1, 1, 3, 10)
    

    【讨论】:

    • Shapeless 2.0中size的定义是否有效?我收到一个错误“无法创建对象,因为方法适用于 [T](f: shapeless.Id[T])Int 类型的 trait ~> 未定义”
    • 如果有人想知道,@JamesMoore 报告的错误可以通过将 default 替换为 apply 来解决
    • 有人知道当前的语法是什么吗?
    【解决方案2】:

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

    1. Natural transformation literals
    2. Klist basics

    你可以从Mark Harrah's 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 方法在概念上与map 相同,用于nat 转换M ~&gt; Id;您还有更一般的map,它从 nat transf M ~&gt; N 和类型 M 的 Klist 产生类型 N 的 KList。

    【讨论】:

    • 我注意到 XSBT 中的 KLists,但还没有时间看。感谢您的提示。这个问题让我抓狂
    【解决方案3】:

    请注意,您在 Mikołaj Koziarkiewicz 的最近(2016 年 10 月,OP 5 年后)文章“Using shapeless' HLists for extra type safety (in Akka Streams)”中有一个带有 HList 的 Map 示例。

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

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

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

    解决方案

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

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

    import shapeless.ops.hlist._
    import shapeless._
    //...
    
    val specs = decorator :: incrementer :: HNil
    val specsSize = specs.length.toInt
    

    现在,对于从ParserStageDefsFlows 的映射,我们需要采用不同的方法,因为HListmap 需要名为P**oly - a polymorphic function value** 的东西。

    在我们的例子中是这样的:

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

    为了使其工作,我们还将ProcessingFlow 更改为类型ProcessingFlow[_] = Flow[Data, Data, _],因为上面的多态函数需要更高种类的类型。

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

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

    我们都准备好了!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-20
      • 1970-01-01
      • 2018-05-07
      • 2019-08-04
      • 2018-02-20
      • 2014-02-21
      • 1970-01-01
      • 2018-02-19
      相关资源
      最近更新 更多