My good friends from Scalera asked me if I would want to write a post for them, and they made me an offer I couldn’t refuse, “We’ll get you a beer” they said… and here I am! I can’t say no to a good beer friend. This is the very first post I’ve ever written about Scala, so please bear with me.
What is Shapeless?
Shapeless is a library created by Miles Sabin that aims to improve generic programming in Scala.
To achieve this it makes use of a bunch of macros and type level tricks, sometimes called whitchcraft, that push the language to its limits. There is a lot to talk about Shapeless, but in this first part we are going to focus on the basics, the most popular structure… yes, you’re right, we are talking about HList
`s!!!
HLists
This is probably the most known feature of Shapeless, and the best start point (IMO) to tackle it, so let’s get started introducing what an HList
is.
HList
stands for Heterogeneous List, i.e. a list of elements of possibly different types. That’s it, the concept is real simple, the consecuences are far more interesting. This allows us to have a list containing a String
and an Int
. But you might be thinking… I can do that with a regular Scala List
. Well, let’s take a look:
scala> List("scalera", 3) res0: List[Any] = List(scalera, 3)
Although it seems this is a List
with two elements of different type, the fact is that from a type level point of view these two elements have the same type, Any
. You can’t do anything useful with this List
, in order to do so you must do some ugly castings here and there.
How do HList
s solve this problem? Well, an HList
stores the type of every element in the list. This way we know the type of the first element, the type of the second element, and so forth. Let’s try the same example we saw before, this time using an HList
.
The syntax for creating an HList
is the same as for creating a List
, we just have to replace the constructor name:
scala> HList("scalera", 3) res0: shapeless.::[String,shapeless.::[Int,shapeless.HNil]] = scalera :: 3 :: HNil
The result type is a little bit confusing, but we can use infix notation to make it cleaner, the result type then would be String :: Int :: HNil
. As you can see, we have all the types stored, we are not losing any info! Furthermore, we statically know the size of the list!
So we can say that HList
s are more powerful than List
s, and here is an example to prove it:
val list: List[Any] = "John Doe" :: 26 :: Nil val hlist: String :: Int :: HNil = "John Doe" :: 26 :: HNil list.head.toUpperCase // error: value toUpperCase is not a member of Any hlist.head.toUpperCase // success: "JOHN DOE"
Notice you can use two different syntax in order to build HList
s, similar to what you can do for regular List
s:
HList("scalera", 3) <===> "scalera" :: 3 :: HNil List(1, 2, 3) <===> 1 :: 2 :: 3 :: Nil
Also, you can tell from the example above we have similar methods to work with HList
s
val hlist = 1 :: "two" :: 3 :: "four" :: HNil hlist.head // 1 hlist.tail // "two" :: 3 :: "four" :: HNil hlist take 2 // 1 :: "two" :: HNil
So far we’ve seen that HList
s have similarities with regular List
s, being the formers more powerful, but besides List
s, there are other Scala structures more similar to HList
s. These structures are tuples and case classes.
All these three data structures(hlists, tuples and case classes) have the same shape, they can store the same information, and you can go from one to another without losing any information. Let’s see an example:
case class User(name: String, age: Int) type UserTuple = (String, Int) type UserHList = String :: Int :: HNil val user: User = User("John Doe", 26) val userTuple: UserTuple = ("John Doe", 26) val userHList: UserHList = "John Doe" :: 26 :: HNil def fromUserToUserHList(user: User): UserHList = ??? def fromUserHListToUser(userHList: UserHList): User = ??? ...
It would be straightforward to implement these functions and I encourage you to try to do so. It’s a good exercise to get used to HList
s.
Why would I use HLists then?
So, if an HList
is very similar to a case class or a tuple, what’s the big deal? What can you do with an HList
you can’t with a case class or tuple? Well, the big difference is that HList
s are a more powerful abstraction, it abstracts over tuple arity for instance. You can’t write a method over tuples, because Tuple2
and Tuple3
are not related to each other, but you can create a method for HList
s and run it with HList
s of arbitrary size. This is out of the scope of this post though.
Conclusions
We’ve seen that HList
s are a powerful abstraction that provides us with the advantages of both lists and tuples. This is only the tip of the iceberg though, as its power really shines when we combined with other Shapeless type class, Generic
. But we’ll leave it for another post…