Shapeless Monads

Shapeless Monads

Shapeless Monads

Small retrospection

In my previous blog post we talked about shapeless and how it could be applied to enhance how you work with Futures. Even though we were focused on Futures, our goal wasn’t to provide the best and ultimate util to deal with them, but to demonstrate how shapeless can help us build functions that are more flexible than almost everything we are used to work with.

So in last post we have created a kind of varargs function that is able to adjust its return type to the arguments passed in. Today we’ll take it much farther by adding scalaz, ApplicativeBuilder and Monads into the soup. Let’s talk shapeless Monads!

Scalaz? Applicative Builder?

Yes, Scalaz, Applicative Builder… Scalaz is library that, to quote the definition the authors have given, “provides purely functional data structures to complement those from the Scala standard library. It defines a set of foundational type classes (e.g. Functor, Monad) and corresponding instances for a large number of data structures”.

Most people associate it with cryptic operators to work with these “purely functional data structures”: |@| (so called Macaulay Culkin), <*><| etc. and it’s what scares the hell out of many people. I find it unfair. In fact, scalaz provides many very useful utils (some of which are actually very basic), type classes and other concepts and I highly encourage you to get familiar with it. This is probably one of the best training resources on the web.

We won’t even try to walk through every aspect of it (is there even a man who would be able to do it?). I’m mentioning it, because some people pointed out that the same or similar effect could be obtained using scalaz’s ApplicativeBuilder pattern. This is an excerpt from comment by @caente (an engineer at x.ai, a blogger and a guy always willing to help:) ) aiming to support this opinion:

https://gist.github.com/tomaszperek/b5bb02332bf566e0c00b

Well, my answer for this claim is “yes, but no” :) It’s not exactly the same thing, and it’s more verbose from one point of view, yet more flexible from the other, as it allows you to apply a function right away. Important thing is that this comment actually gave me an idea for this post. We’ll focus on ApplicativeBuilder to prove some thesis.

Thesis

So here’s the thesis: With shapeless you can create code, that is much more flexible, and much more compact than it would be if you didn’t use shapeless. It won’t introduce new design patterns or paradigms nor will make any old ones obsolete, yet there will be big benefit in much less lines of code and much bigger flexibility. In order to advocate for this thesis we will try to implement our own version of ApplicativeBuilder

First steps

Before we start, let’s say what’s wrong with the code from last post. It’s only for Futures. The advantage ApplicativeBuilder has over our hsequence / zip functions is that it works with any class belonging to Applicative type class. So it will work with Options, Lists and Futures, while zip is only for Futures. So let’s remove this constraint.

Our base trait will now look as follows:

https://gist.github.com/tomaszperek/adc5dfbcc41026fe3818

What has changed? Besides the name, which now reflects that the code is no longer intended to work only with Futures, the trait has one more type parameter, M[_], a type constructor.

Also the return value of hsequencemethod has changed from Future[Out] to M[Out]. Let’s ignore ToMonadOps for a while. It’s an useful tool providing implicit conversions for instances of M, but these details aren’t needed in this discussion. Now, as a base case, implementation for HNil:

https://gist.github.com/tomaszperek/136404741c38c27b9986

First of all, since objects can’t have type parameters, we must use def, but that’s not a big deal. The other thing new is evidence m: Monad[M]. This puts a constraint on M, we want it to belong to Monad type class.

Why Monad? It’s because we need access to bindpure, and map functions. That’s making our life a bit easier, as original ApplicativeBuilder works with Applicatives, but not having access to flatMap would disperse our focus. In the code above we use m.pure(HNil) to construct M[HNil]. We couldn’t use constructor, because we simply don’t know what exact type M will be.

What about longer HLists? Here’s the code:

https://gist.github.com/tomaszperek/6e06f8aa1cf63a54e0c2

It’s getting a bit crowded in type parameters section, and in implicit params too, but there’s only one new thing: M[_] parameter and evidence that it is a Monad. The implementation of hsequence isn’t really that much different from what we had for Futures. We use flatMap (again, that’s why we needed M to be a Monad, not just Applicative) and map just as we had with Futures. Let’s put it together:

https://gist.github.com/tomaszperek/4765f33adae56250c5e1

All in all it’s just a little bit different than it was before. The same goes for implementations of hsequence and zip functions. We are introducing one more type parameter, M[_] and providing one more evidence, that M is a Monad:

https://gist.github.com/tomaszperek/7917f660173a37c0950b

But now we won’t be limited to Futures, as following ScalaTest spec shows:

https://gist.github.com/tomaszperek/4ebeafb121bc8e8ead56

It may be a bit surprising how it works with Lists, but’s exactly the same as if you used |@| from scalaz. One challenge solved, we have our zip/hsequence working with everything that belongs to Monad type class.

Our Own Applicative Builder

We are now just one step away from giving life to our own ApplicativeBuilder. But before we do it, let’s recall what it actually is. It’s a trait, that allows you to combine Applicatives using |@| operator and then either return tuple with combined applicative or execute some function on it, just as it was demonstrated in @caente’s comment.

Now, why are we reimplementing it?. This is why: its implementation. The code is ~170 lines long and it’s a pyramid that is just begging to be simplified. You have just one mean to do it, and it’s type-level programming. I won’t make you wait any longer – here’s the code:

https://gist.github.com/tomaszperek/994cced1161d826d24a1

We just only need an implicit def to convert Monads to single-element ScalacApplicativeBuilder:

https://gist.github.com/tomaszperek/c66359b1341767e7ef94

and that’s actually it.How does it work? First important thing is that it maintains a HList of Monads in values field. Method :@: just adds one more item this list. asTuple is calling our old friend hsequece on values and turns the result into Tupleapply transforms given function argument into HListequivalent and then maps results of hsequence with that equivalent.

All these ‘shapeless magic tricks’ are nothing new, as they were discussed in our last post. And that’s that, there’s nothing else.

Now, did we prove our thesis? Together with imports, the full and standalone implementation of our ScalacApplicativeBuilder is only 42 lines long:

https://gist.github.com/tomaszperek/fc4b0de272da095c3da4

It allows doing the same things as original Applicative Builder and is not limited to 12 elements. Let’s just take a quick look on specs:

https://gist.github.com/tomaszperek/9e6630029699982180be

As we can see, we can do what we could with original ApplicativeBuilder and more.So in my humble opinion that’s a Q.E.D. :) I hope you enjoyed reading this post as much as I did writing it, but what I hope even more, is that I have managed to convince at least some people to look kindly on shapeless.

Thank you!

Do you like this post? Want to stay updated? Follow us on Twitter or subscribe to our Feed.

See also

Download e-book:

Scalac Case Study Book

Download now

Authors

Tomasz Perek

Latest Blogposts

17.04.2024 / By  Michał Szajkowski

Mocking Libraries can be your doom

Test Automations

Test automation is great. Nowadays, it’s become a crucial part of basically any software development process. And at the unit test level it is often a necessity to mimic a foreign service or other dependencies you want to isolate from. So in such a case, using a mock library should be an obvious choice that […]

04.04.2024 / By  Aleksander Rainko

Scala 3 Data Transformation Library: ducktape 0.2.0.

Scala 3 Data Transformation Library: Ducktape 2.0

Introduction: Is ducktape still all duct tape under the hood? Or, why are macros so cool that I’m basically rewriting it for the third time? Before I go off talking about the insides of the library, let’s first touch base on what ducktape actually is, its Github page describes it as this: Automatic and customizable […]

28.03.2024 / By  Matylda Kamińska

Scalendar April 2024

scala conferences april 2024

Event-driven Newsletter Another month full of packed events, not only around Scala conferences in April 2024 but also Frontend Development, and Software Architecture—all set to give you a treasure trove of learning and networking opportunities. There’re online and real-world events that you can join in order to meet colleagues and experts from all over the […]

software product development

Need a successful project?

Estimate project