Можно ли использовать карту в Scala HList? - PullRequest
28 голосов
/ 18 марта 2011

Я сделал несколько реализаций HList сейчас.Один из них основан на выступлении Даниэля Спивака «Высшее волшебство в Стране Скала», а другой - на посте в блоге Apocalisp.Цель состояла в том, чтобы иметь гетерогенный список, который не является гетерогенным по первичному типу, а скорее более высокого вида.Например:

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

Я мог бы сделать карту по списку, чтобы выполнить запрос и получить гетерогенный список более высокого вида.Итак:

requests.map(execute)

должно равняться

String :: Int :: HNil

К сожалению, все мои попытки привели к созданию HList of Any.Вот код из недавней попытки:

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, и снова это привело к HList любого типа.Любые советы будут оценены.

Ответы [ 3 ]

22 голосов
/ 02 января 2012

Реализация HList в бесформенном достаточно богата, чтобы включать функции HList и 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)
3 голосов
/ 30 марта 2011

Вам нужен Klist с конструктором типов Request и естественным преобразованием execute: Request ~> Id. Все это подробно описано в замечательной серии постов программирования на уровне типов в Apocalisp, в частности:

  1. Литералы естественного преобразования
  2. Основы Klist

Вы можете получить код для всей серии из Репо Марка Харры

В вашем случае вам понадобится что-то вроде

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; у вас также есть более общий map, который из nat transf M ~> N и Klist типа M дает KList типа N.

0 голосов
/ 11 октября 2016

Обратите внимание, что у вас есть пример Map с HList в недавней (октябрь 2016 г., 5 лет после OP) статье " Использование бесформенных" HLists для дополнительной безопасности типов (в потоках Akka) "из 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, который используется в данной спецификации.

Решение

Вот где HLists приходят на помощь.Поскольку они сохраняют полную информацию о типе для каждого элемента, можно определить наш поток очень похоже на нашу последнюю попытку.

Сначала давайте заменим наш список на HList:

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

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

Теперь для отображения из ParserStageDefs в Flows нам нужно использовать другой подход, так как map для HList требует чего-то под названием P ** oly - aзначение полиморфной функции **.

Вот как в нашем случае будет выглядеть:

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)

и все готово!

...