可以在 Scala HList 上执行 Map 吗

发布于 2024-10-23 22:13:19 字数 2216 浏览 1 评论 0 原文

我现在已经完成了一些 HList 的实现。一篇基于 Daniel Spiewak 的 High Wizardry in the Land of 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。任何提示将不胜感激。

I have done a few implementations of HList now. One based on Daniel Spiewak's High Wizardry in the Land of Scala talk and another based on a post in Apocalisp blog. The goal was to have a heterogenous list of which is not heterogenous in the primary type but rather the higher kind. For example:

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

I would be able to do a map across the list to perform the request and result in a heterogenous list of the higher kind. So:

requests.map(execute)

should equal

String :: Int :: HNil

Sadly all my attempts have resulted in an HList of Any. Here is the code from a recent attempt:

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]]
}

The other attempt was based on the Apocalisp version which uses fold to create a new HList and again it resulted in a HList of Any types. Any tips would be appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

要走就滚别墨迹 2024-10-30 22:13:19

shapeless 中的 HList 实现足够丰富,可以包含 HList< /code> 和 KList 功能。它提供了一个 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)

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)
审判长 2024-10-30 22:13:19

你需要的是一个带有类型构造函数 Request 的 Klist,以及一个自然转换 execute: Request ~>; id。所有这些都在 Apocalisp 精彩的类型级编程系列文章中进行了详细介绍,特别是:

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

您可以从 Mark Harrah 的 up 存储库

在您的情况下,您需要类似

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 方法,其概念上与 <用于 nat 传输的 code>map M ~> id;你还有更通用的map,它来自nat transf M ~>; N 和类型 M 的 Klist 生成类型 N 的 KList。

what you need is a Klist with type constructor Request, and a natural transformation execute: Request ~> Id. All of this is detailed in the marvelous type-level programming series of posts at Apocalisp, in particular:

  1. Natural transformation literals
  2. Klist basics

you can checkout the code for the whole series from Mark Harrah's up repo

In your case, you'll need something like

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

the down method above is conceptually the same as map for a nat transf M ~> Id; you also have more general map which from a nat transf M ~> N and a Klist of kind M yields a KList of kind N.

亣腦蒛氧 2024-10-30 22:13:19

请注意,最近(2016 年 10 月,OP 发布 5 年后)文章“使用 shapeless 的 HList 实现额外的类型安全(在 Akka Streams 中)”来自 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

解决方案

这就是 HList 的用武之地。因为它们保留了每个元素的完整类型信息,所以可以与我们上次的尝试非常相似地定义我们的流程。

首先,让我们用 HList 替换我们的列表:

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

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

现在,对于从 ParserStageDefsFlows 的映射,我们需要采取不同的方法,就像 HList 的 map 一样 需要称为 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)
}

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

现在,我们的中心声明是:

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

我们都准备好了!

Note that you have an example of Map with HList in the recent (October 2016, 5 years after the OP) article "Using shapeless' HLists for extra type safety (in Akka Streams)" from Mikołaj Koziarkiewicz.

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

The problem lies in the fact that the type information in our specs list is not preserved. Or rather, not preserved the way we want to - the type of the List elements is ParserStageDef[_ >: Int with String], so the lowest common supertype for our decorator and incrementer.

The above implies that, when mapping between the parser and processor elements, the compiler has no way to provide the actual type T that's used within the given spec.

A solution

Here's where HLists come to the rescue. Because they preserve the complete type information for each element, it's possible to define our flow very similarly to our last attempt.

First, let's replace our list with an HList:

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

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

Now, for the mapping from ParserStageDefs into Flows, we need to take a different approach, as the map for HList requires something called P**oly - a polymorphic function value**.

Here's how one would look like in our case:

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

For it to work, we'll also have change ProcessingFlow to type ProcessingFlow[_] = Flow[Data, Data, _], since the polymorphic function above expects a higher-kinded type.

Now, our central statement turns out to be:

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

and we're all set!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文