30

現在、HList のいくつかの実装を行っています。1 つは Daniel Spiewak の High Wizardry in the Land of Scala の話に基づいており、もう 1 つは 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]]
}

もう 1 つの試みは、fold を使用して新しい HList を作成する Apocalisp バージョンに基づいていましたが、これも Any 型の HList になりました。ヒントをいただければ幸いです。

4

3 に答える 3

23

shapelessでのHList実装は、と機能の両方を包含するのに十分なほど豊富です。適切に型指定された結果を生成する要素全体で、おそらく型固有のケースで、よりランクの高い関数を適用する操作を提供します。HListKListmapHList

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)
于 2012-01-02T18:19:08.337 に答える
3

必要なのは、コンストラクター型の Klist とRequest自然な変換execute: Request ~> Idです。これらすべては、Apocalisp の素晴らしい型レベル プログラミングの一連の投稿で詳しく説明されています。

  1. 自然変換リテラル
  2. Klistの基本

シリーズ全体のコードは、 Mark Harrah のアップ リポジトリからチェックアウトできます。

あなたの場合、次のようなものが必要になります

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 transfの場合と同じM ~> Idです。mapnat transfM ~> Nと種類 M の Klist から種類 N の KList を生成する、より一般的なものもあります。

于 2011-03-30T10:52:14.177 に答える
0

Mikołaj Koziarkiewicz の最近 (2016 年 10 月、OP から 5 年後) の記事「 Using shapeless' HLists for extra type safe (in Akka Streams)」に Map with HListの例があることに注意してください。

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

ParserStageDefsここで、 からへのマッピングについては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 に答える