質問1から3に対処HLists
します。主な用途の1つは、アリティの抽象化です。アリティは通常、抽象化の特定の使用サイトで静的に知られていますが、サイトによって異なります。これを、無形の例から見てみましょう。
def flatten[T <: Product, L <: HList](t : T)
(implicit hl : HListerAux[T, L], flatten : Flatten[L]) : flatten.Out =
flatten(hl(t))
val t1 = (1, ((2, 3), 4))
val f1 = flatten(t1) // Inferred type is Int :: Int :: Int :: Int :: HNil
val l1 = f1.toList // Inferred type is List[Int]
val t2 = (23, ((true, 2.0, "foo"), "bar"), (13, false))
val f2 = flatten(t2)
val t2b = f2.tupled
// Inferred type of t2b is (Int, Boolean, Double, String, String, Int, Boolean)
HLists
タプル引数のアリティを抽象化するために(または同等のもの)を使用せずにflatten
、これら2つの非常に異なる形状の引数を受け入れ、タイプセーフな方法で変換できる単一の実装を持つことは不可能です。
アリティを抽象化する機能は、固定されたアリティが関与している場所ならどこでも興味を持つ可能性があります。メソッド/関数のパラメーターリストやケースクラスを含む上記のタプルと同様です。タイプクラスインスタンスをほぼ自動的に取得するために、任意のcaseクラスのアリティを抽象化する方法の例については、ここを参照してください。
// A pair of arbitrary case classes
case class Foo(i : Int, s : String)
case class Bar(b : Boolean, s : String, d : Double)
// Publish their `HListIso`'s
implicit def fooIso = Iso.hlist(Foo.apply _, Foo.unapply _)
implicit def barIso = Iso.hlist(Bar.apply _, Bar.unapply _)
// And now they're monoids ...
implicitly[Monoid[Foo]]
val f = Foo(13, "foo") |+| Foo(23, "bar")
assert(f == Foo(36, "foobar"))
implicitly[Monoid[Bar]]
val b = Bar(true, "foo", 1.0) |+| Bar(false, "bar", 3.0)
assert(b == Bar(true, "foobar", 4.0))
何のランタイムありません反復はここではなく、そこにある重複の使用、HLists
(または同等の構造)を排除することができますが。もちろん、繰り返しボイラープレートの許容度が高い場合は、気になるすべての形状に対して複数の実装を記述することで同じ結果を得ることができます。
質問3では、「... hlistにマップする関数fが非常に一般的で、すべての要素を受け入れる場合...なぜproductIterator.mapを介してそれを使用しないのですか?」と質問します。HListにマッピングする関数が実際に次の形式である場合Any => T
、マッピングproductIterator
は完全にうまく機能します。しかし、フォームの関数はAny => T
通常、それほど興味深いものではありません(少なくとも、内部で型キャストしない限り、興味深いものではありません)。shapelessは、ポリモーフィック関数値の形式を提供します。これにより、コンパイラーは、疑わしい方法で型固有のケースを正確に選択できます。例えば、
// size is a function from values of arbitrary type to a 'size' which is
// defined via type specific cases
object size extends Poly1 {
implicit def default[T] = at[T](t => 1)
implicit def caseString = at[String](_.length)
implicit def caseList[T] = at[List[T]](_.length)
}
scala> val l = 23 :: "foo" :: List('a', 'b') :: true :: HNil
l: Int :: String :: List[Char] :: Boolean :: HNil =
23 :: foo :: List(a, b) :: true :: HNil
scala> (l map size).toList
res1: List[Int] = List(1, 3, 2, 1)
質問4に関して、ユーザー入力については、考慮すべき2つのケースがあります。1つ目は、既知の静的条件が取得されることを保証するコンテキストを動的に確立できる状況です。これらの種類のシナリオでは、シェイプレステクニックを適用することは完全に可能ですが、静的条件が実行時に取得されない場合、代替パスをたどる必要があることを条件として、明らかにします。当然のことながら、これは動的条件に敏感なメソッドはオプションの結果を生成する必要があることを意味します。これはHList
s を使用した例です。
trait Fruit
case class Apple() extends Fruit
case class Pear() extends Fruit
type FFFF = Fruit :: Fruit :: Fruit :: Fruit :: HNil
type APAP = Apple :: Pear :: Apple :: Pear :: HNil
val a : Apple = Apple()
val p : Pear = Pear()
val l = List(a, p, a, p) // Inferred type is List[Fruit]
のタイプはl
、リストの長さ、またはその要素の正確なタイプをキャプチャしません。ただし、特定の形式であることが予想される場合(つまり、既知の固定スキーマに準拠する必要がある場合)、その事実を確立し、それに応じて行動することができます。
scala> import Traversables._
import Traversables._
scala> val apap = l.toHList[Apple :: Pear :: Apple :: Pear :: HNil]
res0: Option[Apple :: Pear :: Apple :: Pear :: HNil] =
Some(Apple() :: Pear() :: Apple() :: Pear() :: HNil)
scala> apap.map(_.tail.head)
res1: Option[Pear] = Some(Pear())
他のリストと同じ長さであるということを除いて、特定のリストの実際の長さを気にしない他の状況があります。繰り返しますが、これは完全に静的に、また上記のように静的/動的混合のコンテキストでも、形のないサポートです。詳細な例については、こちらをご覧ください。
ご覧のとおり、これらのメカニズムはすべて、少なくとも条件付きで静的な型情報が利用可能である必要があり、外部から提供された型指定されていないデータによって完全に駆動される完全に動的な環境でこれらの手法を使用できないように思われます。しかし、2.10でのScalaリフレクションのコンポーネントとしてのランタイムコンパイルのサポートの出現により、これはもはやスーパーバリアの障害ではなくなりました...ランタイムコンパイルを使用して、軽量ステージングの形式を提供し、実行時に静的型付けを実行できます。動的データへの応答:上記の下記からの抜粋...完全な例のリンクをたどる、
val t1 : (Any, Any) = (23, "foo") // Specific element types erased
val t2 : (Any, Any) = (true, 2.0) // Specific element types erased
// Type class instances selected on static type at runtime!
val c1 = stagedConsumeTuple(t1) // Uses intString instance
assert(c1 == "23foo")
val c2 = stagedConsumeTuple(t2) // Uses booleanDouble instance
assert(c2 == "+2.0")
依存して型付けされたプログラミング言語に関する賢人のコメントを考えると、@ PLT_Boratはそれについて何か言いたいことがあると確信しています ;-)