Ответ 1
Решение вопросов от одного до трех: одно из основных приложений для HLists
- абстрагирование по arity. Arity обычно статически известна на любом данном участке использования абстракции, но варьируется от сайта к сайту. Возьмите это из бесформенного examples,
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
невозможно было бы иметь одну реализацию, которая могла бы принимать аргументы этих двух очень разных форм и преобразовывать их в безопасный тип.
Возможность абстрагироваться от arity, вероятно, будет интересна везде, где задействованы фиксированные arities: а также кортежи, как указано выше, включая списки параметров метода/функции и классы case. См. здесь для примеров того, как мы можем абстрагироваться от арности произвольных классов 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
(или эквивалентных структур). Конечно, если ваш переносимость для повторяющегося шаблона высока, вы можете получить тот же результат, написав несколько реализаций для каждой формы, о которой вы заботитесь.
В третьем вопросе вы спрашиваете: "... если функция f, отображаемая над 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)
В отношении вашего вопроса четыре, о вводе пользователя, необходимо рассмотреть два случая. Во-первых, это ситуации, когда мы можем динамически установить контекст, который гарантирует получение известного статического условия. В этих сценариях вполне можно применять бесформенные методы, но ясно, что если статическое условие не будет достигнуто во время выполнения, мы должны следовать альтернативному пути. Неудивительно, что это означает, что методы, чувствительные к динамическим условиям, должны давать необязательные результаты. Здесь пример с использованием 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())
Существуют и другие ситуации, когда нам может не заботиться о фактической длине данного списка, кроме того, что он имеет ту же длину, что и в другом списке. Опять же, это бесформенная поддержка, как полностью статическая, так и в смешанном статическом/динамическом контексте, как указано выше. См. здесь для расширенного примера.
Действительно, как вы заметили, все эти механизмы требуют, чтобы информация о статическом типе была доступна, по крайней мере условно, и это, по-видимому, исключало бы использование этих методов в полностью динамичной среде, полностью управляемой внешними средствами нетипизированные данные. Но с появлением поддержки компиляции во время выполнения в качестве компонента отражения Scala в 2.10, даже это уже не непреодолимое препятствие... мы можем использовать компиляцию во время выполнения, чтобы предоставить форму легкая постановка и наша статическая типизация выполняется во время выполнения в ответ на динамические данные: выдержка из предыдущего ниже... следуйте ссылке для полного примера,
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 будет иметь что сказать об этом, учитывая его комментарий sage о зависимых языках программирования на английском языке; -)