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:
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
esParserStageDef[_ >: Int with String]
, por lo que es elParserStageDef[_ >: 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
aFlows
, necesitamos un enfoque diferente, ya que elmap
paraHList
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 tipoProcessingFlow[_] = 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!