Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Map be performed on a Scala HList

I have done a few implementations of HList now. One based on Daniel Spiewak's High Wizardry in the Land of Scala talk and another based on a post in Apocalisp blog. The goal was to have a heterogenous list of which is not heterogenous in the primary type but rather the higher kind. For example:

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

I would be able to do a map across the list to perform the request and result in a heterogenous list of the higher kind. So:

requests.map(execute) 

should equal

String :: Int :: HNil 

Sadly all my attempts have resulted in an HList of Any. Here is the code from a recent attempt:

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

The other attempt was based on the Apocalisp version which uses fold to create a new HList and again it resulted in a HList of Any types. Any tips would be appreciated.

like image 639
Jesse Eichar Avatar asked Mar 18 '11 08:03

Jesse Eichar


People also ask

What is HList in Scala?

HList is a recursive data structure. In Scala, we can write any pair type like ::[H, T] in a more ergonomic way like H :: T, so the type of our hlist is either Int :: Double :: String :: Boolean :: HNill or ::[Int, ::[Double, ::[String, ::[Boolean, HNill]]]].

What does map in Scala?

Scala map is a collection of key/value pairs. Any value can be retrieved based on its key. Keys are unique in the Map, but values need not be unique. Maps are also called Hash tables. There are two kinds of Maps, the immutable and the mutable.


1 Answers

The HList implementation in shapeless is rich enough to subsume both HList and KList functionality. It provides a map operation which applies a higher-ranked function, possibly with type-specific cases, across it's elements yielding an appropriately typed HList result,

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  

Note that although it's the case in the above example there's no requirement that the HList elements share a common outer type constructor, it just has to be the case that the higher-ranked function mapped with has cases for all of the types involved,

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

Now let's map this across an HList,

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

In this case the result type of the function being mapped is constant: it's an Int no matter what the argument type is. Consequently the resulting HList has elements all of the same type, which means that it can usefully be converted to a vanilla list,

ls.toList == List(1, 1, 3, 10) 
like image 66
Miles Sabin Avatar answered Oct 02 '22 23:10

Miles Sabin