What's shapeless, and why should I care ?
Taken from shapeless' README:
Shapeless is a type class and dependent type based generic programming library for Scala.
To me, Shapeless is a toolkit to leverage Scala's type system at your own profit. You may use it to have more "precise" types, like statically sized list (lists which size is known at compile time), you may also use
HList as a better tuple.
More generally, Shapeless can be used to make the compiler work for you, scrape some boilerplate, and gain a little extra typesafety.
Where's the doc ?
There's not much so far. The wiki is a good place to start, specifically here. The community tries to be as inclusive as possible, so you're likely to find help on the gitter channel. Stackoverflow works very well too.
A lot of examples can be found in shapeless' source code itself. Those examples are specifically here for educational purpose, and pretty much everything in Shapeless is demonstrated.
An incomplete guide to Shapeless features:
Here's list of shapeless' features I use the most, along with a short description and down to earth examples. There's obviously a lot more to discover, but those are basically the features I now consider essential in any non-trivial project.
HList is certainly the most popular feature. A
HList is a
List where the type of every element is statically known at compile time. You may see them as "tuples on steroid". The beauty of
HList compared to tuples is that you'll find all the essential
List methods like
zip, etc. plus a bunch of methods specific to
Here's a little demo:
HList are very useful. You may not realize it yet, but believe me, soon you'll see
HList everywhere. I already wrote about practical and "less practical" use cases. Shapeless also provides a way to turn any case class into an
HList (more on that later).
To explain polymorphic functions, let's start by a simple example.
Take our previously defined
What happens if you want to
map over it ?
The first element is an
Int, the second element is a
String, and the third is a
map function would probably look like this:
But having to pass as much functions as there are elements in this
HList is unpractical.
map this way means you need several definitions of
map. One for each
What you want is to pass to
map a function that works on
User, and let the compiler apply it on each element of the
HList. Something like this:
f is a polymorphic function. Interestingly, if you can define such a function, you could define a more generic
map that works for any
h of type
H <: HList.
& does not exist in Scala, I made it up. The language only provides monomorphic functions. You can create a function whose domain (the type of its parameters) is
Int, but you can't create a function whose domain is
User. More generally, you can't create a function whose domain type is
A for some
A. As a trivial exercise, try to define the
identity function (of type
A => A). It's impossible.
Back to our
map function now. Of course
f could be a function that handle the least upper bound of all the elements in this
Hlist. In our example the type of
f would be
Any => Any. Generally, a function of that type is not very useful.
I already mentioned that
map is defined for
HList, which means Shapeless provides polymorphic functions. Here's a simple example:
I think the code is rather easy to understand. Notice that polymorphic functions are perfectly typesafe. Be careful not to forget the
implicit keyword. It's a silly mistake, but I make it from times to times. Sometimes it takes a while to realize why Scalac refuses to map over a
Note that polymorphic function can use implicit parameters:
Generic is a simple way to convert case class and product types (like tuples) to
HList, and vice-versa:
Again, the code is fairly simple.
Generic is often used to automatically derive typeclasses instances for case classes. See my other post Type all the things, for real world examples.
Generic is a great way to avoid writing macros. And that's great! I don't want to maintain my poorly written macros.
Shapeless provide syntax for tuples, so that you can use
HList's methods on tuples.
The code is rather obvious. Most of the
HList methods become available on tuples by simply importing
import shapeless.syntax.std.tuple._. Very nifty!
Shapeless provides a simple lenses implementation. Here's a basic example, directly taken from shapeless' examples:
If you just need a lens from time to time and already have Shapeless in your project, it can be useful. For more advanced usages, consider a dedicated library like monocle.
Abstracting over arity:
Not a specific feature per say, but based on
Generic, Shapeless provides a way to create functions of arbitrary arity.
Let's say you created a class that contains a
You may not want to force
HList on your users. So how do you create instances of
MyClass without using
HList directly ? Well, you can provide a bunch of
But that's rather annoying to write. So instead you can do this:
Note that you're actually passing a tuple to the
apply method. Under stricter compiler options, you'll need an extra pair of parenthesis:
MyClass((1, "Hello", 12.6)).
May the source be with you, always.
If you made it this far into this blog post, you may want to learn more about shapeless. So given the little doc currently available, you'll have to resort to reading the source code to learn more. Luckily, it's very easy to navigate in Shapeless' sources once you've found how it's organized.
Navigating the source
Shapeless sources are divided in 3:
- /core/src/main/scala/shapeless contains all the base data structure definitions, each having it's own file. For example hlist.scala is the definition of
- /core/src/main/scala/shapeless/ops contains all the typeclasses used by those structures. Again, each data structure having it's own file. hlist.scala contains all the typeclasses for
- /core/src/main/scala/shapeless/syntax contains all the methods usable on each data structure. Once again each data structure having it's own file. hlist.scala contains all the methods defined on
HList. If you want to look at the definition of
HList, here it is.
This should be enough to find pretty much everything you need to know by yourself.
Understating the source
Everything in shapeless (apart from macros), pretty much work on the same model.
If you wish to understand how
HList works, I've already written about it in my article Typelevel quicksort in Scala. Once you understand
HList, everything should follow. I'd really suggest to take the time to understand how
HList are built, and how you
map over a
HList, even if you do not plan to use Shapeless.
This article is meant to give you an overview of the basic use cases. If anything is unclear, or just not covered in this article, let me know in the comments or ping me on twitter. I'll try to improve it over time. If you know more resources on Shapeless or type level programming, I'd be happy to link them here.