質問1から3に対処する:の主なアプリケーションの1つは、HLists
アリティを抽象化することです。アリティは通常、抽象化の特定の使用サイトで静的に知られていますが、サイトごとに異なります。形のない例からこれを取りなさい、
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つの非常に異なる形状の引数を受け入れ、タイプセーフな方法で変換できる単一の実装を持つことは不可能です。
アリティを抽象化する機能は、固定アリティが関係するすべての場所で興味深い可能性があります。上記のように、メソッド/関数のパラメーターリストやケースクラスを含むタプルも同様です。型クラスインスタンスをほぼ自動的に取得するために、任意のケースクラスのアリティを抽象化する方法の例については、ここを参照してください。
// 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にマップする関数が非常に一般的で、すべての要素を受け入れる場合... 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はそれについて何か言うことがあると確信しています;-)