scala shapeless hlist

Se puede realizar un mapa en una lista de Scala



shapeless hlist (3)

La implementación de HList en HList es lo suficientemente rica como para subsumir tanto la funcionalidad de HList como la de KList . Proporciona una operación de map que aplica una función de clasificación más alta, posiblemente con casos específicos de tipo, a través de sus elementos que producen un resultado HList correctamente tipificado,

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

Tenga en cuenta que aunque es el caso en el ejemplo anterior, no es necesario que los elementos HList compartan un constructor de tipo externo común, solo tiene que ser el caso de que la función de mayor rango asignada tenga casos para todos los tipos involucrados,

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

Ahora vamos a mapear esto a través de un HList ,

val l = 23 :: true :: "foo" :: ("bar", "wibble") :: HNil val ls = l map size ls == 1 :: 1 :: 3 :: 10 :: HNil

En este caso, el tipo de resultado de la función que se está asignando es constante: es un Int, independientemente del tipo de argumento. En consecuencia, el HList resultante tiene elementos del mismo tipo, lo que significa que puede convertirse de manera útil en una lista de vainilla,

ls.toList == List(1, 1, 3, 10)

He hecho algunas implementaciones de HList ahora. Una basada en la charla de la Alta Hechicería de Daniel Spiewak en la Tierra de Scala y otra basada en una publicación en el blog Apocalisp. El objetivo era tener una lista heterogénea de la cual no es heterogénea en el tipo primario, sino en el tipo superior. Por ejemplo:

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

Sería capaz de hacer un mapa a través de la lista para realizar la solicitud y dar como resultado una lista heterogénea del tipo superior. Asi que:

requests.map(execute)

debe ser igual

String :: Int :: HNil

Lamentablemente todos mis intentos han resultado en una lista de todos. Aquí está el código de un intento reciente:

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]] }

El otro intento se basó en la versión Apocalisp, que utiliza fold para crear una nueva lista HL y, de nuevo, dio como resultado una lista HL de cualquier tipo. Cualquier consejo sería apreciado.


Lo que necesita es una Klist con Request constructor de tipo y una transformación natural execute: Request ~> Id . Todo esto se detalla en la maravillosa serie de publicaciones de programación a nivel de tipo en Apocalisp, en particular:

  1. Literales de transformación natural.
  2. Conceptos básicos de Klist

Puede verificar el código de toda la serie desde el repositorio de Mark Harrah.

En tu caso, necesitarás algo como

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

el método de down arriba es conceptualmente el mismo que el map para un nat transf M ~> Id ; también tiene un map más general que, desde un nat transf M ~> N y una Klist de tipo M, produce un KList de kind N.


Tenga en cuenta que tiene un ejemplo de Mapa con HList en el artículo reciente (en octubre de 2016, 5 años después del OP) " Uso de HLists sin forma para seguridad de tipo adicional (en Akka Streams) " de MikoĊ‚aj Koziarkiewicz .

//glue for the ParserStageDefs specs.map(s => Flow[Data].map(s.parser).map(s.processor)) .foreach(broadcast ~> _ ~> merge)

El problema radica en el hecho de que la información de tipo en nuestra lista de especificaciones no se conserva. O, mejor dicho, no se conserva como queremos, el tipo de los elementos de la List es ParserStageDef[_ >: Int with String] , por lo que es el ParserStageDef[_ >: Int with String] común más bajo para nuestro decorador e incrementador.

Lo anterior implica que, al asignar el analizador y los elementos del procesador, el compilador no tiene forma de proporcionar el tipo T real que se usa dentro de la especificación dada.

Una solución

Aquí es donde los HLists vienen al rescate. Debido a que conservan la información de tipo completa para cada elemento, es posible definir nuestro flujo de manera muy similar a nuestro último intento.

Primero, reemplacemos nuestra lista con una HList :

import shapeless.ops.hlist._ import shapeless._ //... val specs = decorator :: incrementer :: HNil val specsSize = specs.length.toInt

Ahora, para el mapeo de ParserStageDefs a Flows , necesitamos un enfoque diferente, ya que el map para HList requiere algo llamado P ** oly - un valor de función polimórfica **.

Así es como se vería uno en nuestro caso:

import shapeless.PolyDefns.~> object toFlow extends (ParserStageDef ~> ProcessingFlow) { override def apply[T](f: ParserStageDef[T]) = Flow[Data].map(f.parser).map(f.processor) }

Para que funcione, también tendremos que cambiar ProcessingFlow al tipo ProcessingFlow[_] = Flow[Data, Data, _] , ya que la función polimórfica anterior espera un tipo de tipo más alto.

Ahora, nuestra declaración central resulta ser:

//we convert to a List[ProcessingFlow[_]] for simplicity specs.map(toFlow).toList.foreach(broadcast ~> _ ~> merge)

y estamos listos!